From 8633611cf75a8f98d6e28a3e4739d5d653bc3737 Mon Sep 17 00:00:00 2001 From: sunny Date: Wed, 25 Dec 2024 11:02:37 +0800 Subject: [PATCH] feat: Add copy button to the create dialog and column layout --- app/components/emails/create-dialog.tsx | 25 ++++++++++-- app/components/emails/three-column-layout.tsx | 37 ++++++++++++------ app/hooks/use-copy.ts | 39 +++++++++++++++++++ 3 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 app/hooks/use-copy.ts diff --git a/app/components/emails/create-dialog.tsx b/app/components/emails/create-dialog.tsx index 8e678fa..2e87943 100644 --- a/app/components/emails/create-dialog.tsx +++ b/app/components/emails/create-dialog.tsx @@ -4,13 +4,14 @@ import { useEffect, useState } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" -import { Plus, RefreshCw } from "lucide-react" +import { Copy, Plus, RefreshCw } from "lucide-react" import { useToast } from "@/components/ui/use-toast" import { nanoid } from "nanoid" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { EXPIRY_OPTIONS } from "@/types/email" +import { useCopy } from "@/hooks/use-copy" interface CreateDialogProps { onEmailCreated: () => void @@ -28,9 +29,14 @@ export function CreateDialog({ onEmailCreated }: CreateDialogProps) { const [currentDomain, setCurrentDomain] = useState("") const [expiryTime, setExpiryTime] = useState(EXPIRY_OPTIONS[1].value.toString()) const { toast } = useToast() + const { copyToClipboard } = useCopy() const generateRandomName = () => setEmailName(nanoid(8)) + const copyEmailAddress = () => { + copyToClipboard(`${emailName}@${currentDomain}`) + } + const createEmail = async () => { if (!emailName.trim()) { toast({ @@ -152,8 +158,21 @@ export function CreateDialog({ onEmailCreated }: CreateDialogProps) { -
- 完整邮箱地址将为: {emailName ? `${emailName}@${currentDomain}` : "..."} +
+ 完整邮箱地址将为: + {emailName ? ( +
+ {`${emailName}@${currentDomain}`} +
+ +
+
+ ) : ( + ... + )}
diff --git a/app/components/emails/three-column-layout.tsx b/app/components/emails/three-column-layout.tsx index fc5b32a..eb2a54e 100644 --- a/app/components/emails/three-column-layout.tsx +++ b/app/components/emails/three-column-layout.tsx @@ -5,6 +5,8 @@ import { EmailList } from "./email-list" import { MessageList } from "./message-list" import { MessageView } from "./message-view" import { cn } from "@/lib/utils" +import { useCopy } from "@/hooks/use-copy" +import { Copy } from "lucide-react" interface Email { id: string @@ -14,10 +16,11 @@ interface Email { export function ThreeColumnLayout() { const [selectedEmail, setSelectedEmail] = useState(null) const [selectedMessageId, setSelectedMessageId] = useState(null) + const { copyToClipboard } = useCopy() const columnClass = "border-2 border-primary/20 bg-background rounded-lg overflow-hidden flex flex-col" const headerClass = "p-2 border-b-2 border-primary/20 flex items-center justify-between shrink-0" - const titleClass = "text-sm font-bold px-2" + const titleClass = "text-sm font-bold px-2 w-full overflow-hidden" // 移动端视图逻辑 const getMobileView = () => { @@ -28,6 +31,10 @@ export function ThreeColumnLayout() { const mobileView = getMobileView() + const copyEmailAddress = () => { + copyToClipboard(selectedEmail?.address || "") + } + return (
{/* 桌面端三栏布局 */} @@ -37,7 +44,7 @@ export function ThreeColumnLayout() {

我的邮箱

- @@ -48,7 +55,12 @@ export function ThreeColumnLayout() {

{selectedEmail ? ( - {selectedEmail.address} +
+ {selectedEmail.address} +
+ +
+
) : ( "选择邮箱查看消息" )} @@ -92,7 +104,7 @@ export function ThreeColumnLayout() {

我的邮箱

- { setSelectedEmail(email) }} @@ -101,21 +113,24 @@ export function ThreeColumnLayout() {
)} - + {mobileView === "emails" && selectedEmail && (
-
+
- - {selectedEmail.address} - +
+ {selectedEmail.address} +
+ +
+
)} - + {mobileView === "message" && selectedEmail && selectedMessageId && (
diff --git a/app/hooks/use-copy.ts b/app/hooks/use-copy.ts new file mode 100644 index 0000000..aff50b5 --- /dev/null +++ b/app/hooks/use-copy.ts @@ -0,0 +1,39 @@ +"use client" + +import { useCallback } from "react" +import { useToast } from "@/components/ui/use-toast" + +interface UseCopyOptions { + successMessage?: string + errorMessage?: string +} + +export function useCopy(options: UseCopyOptions = {}) { + const { toast } = useToast() + const { + successMessage = "已复制到剪贴板", + errorMessage = "复制失败" + } = options + + const copyToClipboard = useCallback(async (text: string) => { + try { + await navigator.clipboard.writeText(text) + toast({ + title: "成功", + description: successMessage + }) + return true + } catch { + toast({ + title: "错误", + description: errorMessage, + variant: "destructive" + }) + return false + } + }, [successMessage, errorMessage, toast]) + + return { + copyToClipboard + } +} \ No newline at end of file