feat: Add copy button to the create dialog and column layout

This commit is contained in:
sunny
2024-12-25 11:02:37 +08:00
parent ee8d284a73
commit 8633611cf7
3 changed files with 87 additions and 14 deletions
+22 -3
View File
@@ -4,13 +4,14 @@ import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" 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 { useToast } from "@/components/ui/use-toast"
import { nanoid } from "nanoid" import { nanoid } from "nanoid"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { EXPIRY_OPTIONS } from "@/types/email" import { EXPIRY_OPTIONS } from "@/types/email"
import { useCopy } from "@/hooks/use-copy"
interface CreateDialogProps { interface CreateDialogProps {
onEmailCreated: () => void onEmailCreated: () => void
@@ -28,9 +29,14 @@ export function CreateDialog({ onEmailCreated }: CreateDialogProps) {
const [currentDomain, setCurrentDomain] = useState("") const [currentDomain, setCurrentDomain] = useState("")
const [expiryTime, setExpiryTime] = useState(EXPIRY_OPTIONS[1].value.toString()) const [expiryTime, setExpiryTime] = useState(EXPIRY_OPTIONS[1].value.toString())
const { toast } = useToast() const { toast } = useToast()
const { copyToClipboard } = useCopy()
const generateRandomName = () => setEmailName(nanoid(8)) const generateRandomName = () => setEmailName(nanoid(8))
const copyEmailAddress = () => {
copyToClipboard(`${emailName}@${currentDomain}`)
}
const createEmail = async () => { const createEmail = async () => {
if (!emailName.trim()) { if (!emailName.trim()) {
toast({ toast({
@@ -152,8 +158,21 @@ export function CreateDialog({ onEmailCreated }: CreateDialogProps) {
</RadioGroup> </RadioGroup>
</div> </div>
<div className="text-sm text-gray-500"> <div className="flex items-center gap-2 text-sm text-muted-foreground">
: {emailName ? `${emailName}@${currentDomain}` : "..."} <span className="shrink-0">:</span>
{emailName ? (
<div className="flex items-center gap-2 min-w-0">
<span className="truncate">{`${emailName}@${currentDomain}`}</span>
<div
className="shrink-0 cursor-pointer hover:text-primary transition-colors"
onClick={copyEmailAddress}
>
<Copy className="size-4" />
</div>
</div>
) : (
<span className="text-gray-400">...</span>
)}
</div> </div>
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
+26 -11
View File
@@ -5,6 +5,8 @@ import { EmailList } from "./email-list"
import { MessageList } from "./message-list" import { MessageList } from "./message-list"
import { MessageView } from "./message-view" import { MessageView } from "./message-view"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { useCopy } from "@/hooks/use-copy"
import { Copy } from "lucide-react"
interface Email { interface Email {
id: string id: string
@@ -14,10 +16,11 @@ interface Email {
export function ThreeColumnLayout() { export function ThreeColumnLayout() {
const [selectedEmail, setSelectedEmail] = useState<Email | null>(null) const [selectedEmail, setSelectedEmail] = useState<Email | null>(null)
const [selectedMessageId, setSelectedMessageId] = useState<string | null>(null) const [selectedMessageId, setSelectedMessageId] = useState<string | null>(null)
const { copyToClipboard } = useCopy()
const columnClass = "border-2 border-primary/20 bg-background rounded-lg overflow-hidden flex flex-col" 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 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 = () => { const getMobileView = () => {
@@ -28,6 +31,10 @@ export function ThreeColumnLayout() {
const mobileView = getMobileView() const mobileView = getMobileView()
const copyEmailAddress = () => {
copyToClipboard(selectedEmail?.address || "")
}
return ( return (
<div className="pb-5 pt-20 h-full flex flex-col"> <div className="pb-5 pt-20 h-full flex flex-col">
{/* 桌面端三栏布局 */} {/* 桌面端三栏布局 */}
@@ -37,7 +44,7 @@ export function ThreeColumnLayout() {
<h2 className={titleClass}></h2> <h2 className={titleClass}></h2>
</div> </div>
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<EmailList <EmailList
onEmailSelect={setSelectedEmail} onEmailSelect={setSelectedEmail}
selectedEmailId={selectedEmail?.id} selectedEmailId={selectedEmail?.id}
/> />
@@ -48,7 +55,12 @@ export function ThreeColumnLayout() {
<div className={headerClass}> <div className={headerClass}>
<h2 className={titleClass}> <h2 className={titleClass}>
{selectedEmail ? ( {selectedEmail ? (
<span className="truncate block">{selectedEmail.address}</span> <div className="w-full flex items-center gap-2">
<span className="truncate min-w-0">{selectedEmail.address}</span>
<div className="shrink-0 cursor-pointer text-primary" onClick={copyEmailAddress}>
<Copy className="size-4" />
</div>
</div>
) : ( ) : (
"选择邮箱查看消息" "选择邮箱查看消息"
)} )}
@@ -92,7 +104,7 @@ export function ThreeColumnLayout() {
<h2 className={titleClass}></h2> <h2 className={titleClass}></h2>
</div> </div>
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<EmailList <EmailList
onEmailSelect={(email) => { onEmailSelect={(email) => {
setSelectedEmail(email) setSelectedEmail(email)
}} }}
@@ -101,21 +113,24 @@ export function ThreeColumnLayout() {
</div> </div>
</> </>
)} )}
{mobileView === "emails" && selectedEmail && ( {mobileView === "emails" && selectedEmail && (
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className={headerClass}> <div className={cn(headerClass, "gap-2")}>
<button <button
onClick={() => { onClick={() => {
setSelectedEmail(null) setSelectedEmail(null)
}} }}
className="text-sm text-primary" className="text-sm text-primary shrink-0"
> >
</button> </button>
<span className="text-sm font-medium truncate"> <div className="flex-1 flex items-center gap-2 min-w-0">
{selectedEmail.address} <span className="truncate min-w-0 flex-1 text-right">{selectedEmail.address}</span>
</span> <div className="shrink-0 cursor-pointer text-primary" onClick={copyEmailAddress}>
<Copy className="size-4" />
</div>
</div>
</div> </div>
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<MessageList <MessageList
@@ -126,7 +141,7 @@ export function ThreeColumnLayout() {
</div> </div>
</div> </div>
)} )}
{mobileView === "message" && selectedEmail && selectedMessageId && ( {mobileView === "message" && selectedEmail && selectedMessageId && (
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className={headerClass}> <div className={headerClass}>
+39
View File
@@ -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
}
}