Develop (#55)
feat: optimize dashboard/search UX and simplify extension architecture Redesign Dashboard with compact tool grid and recently used tools Improve TopBar search UX with Cmd/Ctrl+K shortcut and better history navigation Reorganize project structure into src/ Migrate i18n from react-i18next to chrome.i18n Remove runtime language switch and settings page Remove HTML/Markdown conversion tools Clean up unused code, dead animations, redundant comments, and imports Improve component consistency with shadcn/ui patterns Replace hardcoded strings/colors with i18n tokens and theme tokens Add comprehensive project documentation and coding standards Fix CI artifact upload workflow and multiple TypeScript/test issues Includes various refactors, UI polish, i18n cleanup, CI improvements, and maintenance updates across the codebase.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Check, Copy } from 'lucide-react';
|
||||
import { copyTextToClipboard } from '@/utils/clipboard';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { buttonVariants, type ButtonProps } from '@/components/ui/button';
|
||||
import { toast } from 'sonner';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
|
||||
interface CopyButtonProps extends Omit<ButtonProps, 'children' | 'onClick'> {
|
||||
text: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const CopyButton: React.FC<CopyButtonProps> = ({
|
||||
text,
|
||||
tooltip,
|
||||
variant = 'ghost',
|
||||
size = 'icon',
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useI18n('common');
|
||||
const [copied, setCopied] = useState(false);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!text) {
|
||||
toast.error(t('messages.copyEmpty'));
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await copyTextToClipboard(text);
|
||||
if (success) {
|
||||
toast.success(t('messages.copySuccess'));
|
||||
setCopied(true);
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
timerRef.current = setTimeout(() => setCopied(false), 1500);
|
||||
} else {
|
||||
toast.error(t('messages.copyError'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
title={tooltip ?? t('buttons.copy')}
|
||||
className={cn(
|
||||
buttonVariants({ variant, size }),
|
||||
copied &&
|
||||
'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/10',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-[1.2em] w-[1.2em] animate-in fade-in zoom-in-75 duration-200" />
|
||||
) : (
|
||||
<Copy className="h-[1.2em] w-[1.2em]" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyButton;
|
||||
Reference in New Issue
Block a user