diff --git a/src/components/compose/compose-form.tsx b/src/components/compose/compose-form.tsx index a243cca..230fe06 100644 --- a/src/components/compose/compose-form.tsx +++ b/src/components/compose/compose-form.tsx @@ -1,7 +1,8 @@ "use client"; import { useEffect, useMemo, useRef, useState } from "react"; -import { FileText, Forward, Minimize2, Paperclip, Reply, Trash2, X } from "lucide-react"; +import type { DragEvent } from "react"; +import { FileText, Forward, Maximize2, Minimize2, Paperclip, Reply, Trash2, X } from "lucide-react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -54,6 +55,8 @@ export function ComposeForm({ const [attachments, setAttachments] = useState([]); // Attachments the draft already holds server-side (a forwarded message's files). const [storedAttachments, setStoredAttachments] = useState([]); + const [draggingFiles, setDraggingFiles] = useState(false); + const [modalMode, setModalMode] = useState(false); const [toast, setToast] = useState(null); const [loading, setLoading] = useState(false); const [loadingDraft, setLoadingDraft] = useState(false); @@ -65,6 +68,7 @@ export function ComposeForm({ const saveTimer = useRef | null>(null); const draftGeneration = useRef(0); const attachmentInput = useRef(null); + const fileDragDepth = useRef(0); const previousSignature = useRef(""); useEffect(() => { @@ -124,10 +128,10 @@ export function ComposeForm({ setThreading( draft.inReplyTo || draft.threadId ? { - inReplyTo: draft.inReplyTo ?? null, - references: draft.references ?? null, - threadId: draft.threadId ?? null, - } + inReplyTo: draft.inReplyTo ?? null, + references: draft.references ?? null, + threadId: draft.threadId ?? null, + } : null, ); setSubject(draft.subject ?? ""); @@ -357,6 +361,32 @@ export function ComposeForm({ if (attachmentInput.current) attachmentInput.current.value = ""; } + function onFileDragEnter(event: DragEvent) { + if (!event.dataTransfer.types.includes("Files")) return; + fileDragDepth.current += 1; + if (!loading && !loadingDraft) setDraggingFiles(true); + } + + function onFileDragOver(event: DragEvent) { + if (!event.dataTransfer.types.includes("Files")) return; + event.preventDefault(); + event.dataTransfer.dropEffect = loading || loadingDraft ? "none" : "copy"; + } + + function onFileDragLeave(event: DragEvent) { + if (!event.dataTransfer.types.includes("Files")) return; + fileDragDepth.current = Math.max(0, fileDragDepth.current - 1); + if (fileDragDepth.current === 0) setDraggingFiles(false); + } + + function onFileDrop(event: DragEvent) { + if (!event.dataTransfer.types.includes("Files")) return; + event.preventDefault(); + fileDragDepth.current = 0; + setDraggingFiles(false); + if (!loading && !loadingDraft) addAttachments(event.dataTransfer.files); + } + function selectSender(value: string) { const option = senderOptions.find((item) => `${item.mailbox.id}|${item.address}` === value); if (!option) return; @@ -364,24 +394,80 @@ export function ComposeForm({ if (selectedMailbox?.id !== option.mailbox.id) setSelectedMailbox(option.mailbox); } + const attachmentContent = (attachments.length > 0 || storedAttachments.length > 0) && ( +
+ {storedAttachments.map((attachment) => ( +
+ + {attachment.filename} + {formatAttachmentSize(attachment.size)} + +
+ ))} + {attachments.map((attachment) => ( +
+ + {attachment.file.name} + + {formatAttachmentSize(attachment.file.size)} + + +
+ ))} +
+ ); + const frameClass = mode === "popup" - ? "fixed bottom-4 right-4 z-40 flex h-[min(520px,calc(100vh-88px))] w-[min(560px,calc(100vw-32px))] flex-col overflow-hidden rounded-lg border border-neutral-200 bg-white shadow-2xl" - : "flex h-full min-h-[720px] w-full max-w-4xl flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white shadow-sm"; + ? modalMode + ? "fixed left-1/2 top-1/2 z-50 flex h-[86vh] w-[min(860px,calc(100vw-32px))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg bg-white shadow-2xl" + : "fixed bottom-4 right-4 z-40 flex h-[min(520px,calc(100vh-88px))] w-[min(560px,calc(100vw-32px))] flex-col overflow-hidden rounded-lg border border-neutral-200 bg-white shadow-2xl" + : "relative flex h-full min-h-[720px] w-full max-w-4xl flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white shadow-sm"; return ( <> + {mode === "popup" && modalMode &&