refactor: 重构 CopyButton 与 RouterContainer 组件,移除 GlobalSnackbar 并统一使用 sonner toast 和 cn 工具函数

This commit is contained in:
雨霖铃
2026-05-22 20:51:13 +08:00
parent 2ced38769b
commit b3dd7146ad
4 changed files with 81 additions and 69 deletions
+48 -60
View File
@@ -1,43 +1,25 @@
import React, { useEffect, useRef, useState } from 'react';
import { Copy, Check } from 'lucide-react';
import { Check, Copy } from 'lucide-react';
import { copyTextToClipboard } from '@/utils/clipboard';
import type { SnackbarOptions } from '@/components/GlobalSnackbar';
import { cn } from '@/lib/utils'; // 1. 必须使用 cn 工具函数
import { toast } from 'sonner'; // 2. 推荐使用 shadcn 默认的全局 toast
/**
* 复制按钮组件属性
* @param text 要复制的文本
* @param tooltip 提示信息
* @param size 按钮大小
* @param color 按钮颜色
* @param style 自定义样式
* @param showMessage 消息提示函数,用于显示复制成功或失败的消息
*/
interface CopyButtonProps {
// 3. 继承原生按钮属性,允许外部自由扩展 className、variant 等
interface CopyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string;
tooltip?: string;
size?: 'small' | 'medium' | 'large';
color?: 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning' | string;
style?: React.CSSProperties;
showMessage?: (message: string, options?: SnackbarOptions) => void;
// 移除复杂的自定义颜色变体,交由 Tailwind 类名或 shadcn 的 variant 解决
variant?: 'default' | 'secondary' | 'ghost' | 'outline';
}
/**
* 复制按钮组件
* @param text 要复制的文本
* @param tooltip 提示信息
* @param size 按钮大小
* @param color 按钮颜色
* @param style 自定义样式
* @param showMessage 消息提示函数,用于显示复制成功或失败的消息
* @returns 复制按钮组件
*/
export const CopyButton: React.FC<CopyButtonProps> = ({
text,
tooltip = '复制',
size = 'small',
color = 'primary',
style,
showMessage,
variant = 'ghost',
className,
...props
}) => {
const [copied, setCopied] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -48,55 +30,61 @@ export const CopyButton: React.FC<CopyButtonProps> = ({
};
}, []);
const handleCopy = async () => {
if (text) {
const success = await copyTextToClipboard(text);
if (success) {
showMessage?.('复制成功', { severity: 'success' });
setCopied(true);
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => setCopied(false), 1500);
} else {
showMessage?.('复制失败', { severity: 'error' });
}
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation(); // 基础组件防冒泡,避免触发父级点击事件
if (!text) {
toast.error('无内容可复制');
return;
}
const success = await copyTextToClipboard(text);
if (success) {
toast.success('复制成功');
setCopied(true);
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => setCopied(false), 1500);
} else {
showMessage?.('无内容可复制', { severity: 'error' });
toast.error('复制失败');
}
};
// 4. 将控制尺寸的类名标准化
const sizeClasses = {
small: 'h-8 w-8',
medium: 'h-10 w-10',
large: 'h-12 w-12',
small: 'h-8 w-8 text-xs',
medium: 'h-10 w-10 text-sm',
large: 'h-12 w-12 text-base',
};
const iconSize = size === 'small' ? 14 : size === 'medium' ? 16 : 18;
const colorClasses: Record<string, string> = {
primary: 'text-primary hover:bg-primary/10',
secondary: 'text-foreground hover:bg-muted',
success: 'text-green-600 hover:bg-green-500/10',
error: 'text-red-600 hover:bg-red-500/10',
info: 'text-primary hover:bg-primary/10',
warning: 'text-amber-600 hover:bg-amber-50',
// 5. 映射 shadcn 的底层通用 Variant 类名
const variantClasses = {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
};
const colorClass = colorClasses[color] || `text-[${color}] hover:bg-muted`;
return (
<button
type="button"
onClick={handleCopy}
title={tooltip}
style={style}
className={`${sizeClasses[size]} rounded-md flex items-center justify-center transition-all ${
copied ? 'text-green-600 bg-green-50' : colorClass
} bg-background shadow-sm hover:shadow-md`}
// 6. 使用 cn() 合并类名,并完美支持暗黑模式的语义化变量 (destructive/muted等)
className={cn(
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
sizeClasses[size],
copied
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400' // 兼顾暗黑模式的成功色
: variantClasses[variant],
className, // 允许外部直接传入 text-red-500 等覆盖样式
)}
{...props}
>
{copied ? (
<Check style={{ width: iconSize, height: iconSize }} />
<Check className="h-[1.2em] w-[1.2em] animate-in fade-in zoom-in-75 duration-200" />
) : (
<Copy style={{ width: iconSize, height: iconSize }} />
<Copy className="h-[1.2em] w-[1.2em]" />
)}
</button>
);