{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mailbox",
  "title": "Mailbox",
  "description": "A responsive mailbox block with folders, message lists, compose flow, confirmations, and toast feedback.",
  "dependencies": [
    "lucide-react",
    "sonner"
  ],
  "registryDependencies": [
    "alert-dialog",
    "avatar",
    "badge",
    "button",
    "card",
    "combobox",
    "context-menu",
    "dialog",
    "dropdown-menu",
    "https://ui.corr.sh/r/file-upload-dropzone.json",
    "input",
    "menubar",
    "resizable",
    "https://ui.corr.sh/r/scroll-blur.json",
    "separator",
    "sidebar",
    "sonner",
    "textarea",
    "toggle-group"
  ],
  "files": [
    {
      "path": "registry/corr/components/mailbox.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { toast } from \"sonner\"\nimport {\n  ArchiveIcon,\n  ArrowLeftIcon,\n  BoldIcon,\n  Edit3Icon,\n  ForwardIcon,\n  FolderIcon,\n  InboxIcon,\n  ItalicIcon,\n  MailIcon,\n  MoreHorizontalIcon,\n  PaperclipIcon,\n  PanelLeftIcon,\n  ReplyIcon,\n  RefreshCcwIcon,\n  SearchIcon,\n  SendIcon,\n  StarIcon,\n  Trash2Icon,\n  UnderlineIcon,\n  XIcon,\n} from \"lucide-react\"\n\nimport { FileUploadDropzone, type FileUploadDropzoneFile } from \"./file-upload-dropzone\"\nimport { ScrollBlur } from \"./scroll-blur\"\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport { Card } from \"@/components/ui/card\"\nimport {\n  Combobox,\n  ComboboxChip,\n  ComboboxChips,\n  ComboboxChipsInput,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxItem,\n  ComboboxList,\n  ComboboxTrigger,\n  ComboboxValue,\n  useComboboxAnchor,\n} from \"@/components/ui/combobox\"\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuSeparator,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuTrigger,\n} from \"@/components/ui/context-menu\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Menubar,\n  MenubarContent,\n  MenubarItem,\n  MenubarMenu,\n  MenubarSeparator,\n  MenubarTrigger,\n} from \"@/components/ui/menubar\"\nimport {\n  ResizableHandle,\n  ResizablePanel,\n  ResizablePanelGroup,\n} from \"@/components/ui/resizable\"\nimport { Separator } from \"@/components/ui/separator\"\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarProvider,\n} from \"@/components/ui/sidebar\"\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\"\nimport { cn } from \"@/lib/utils\"\n\nexport type MailboxFolder = {\n  id: string\n  label: string\n  count?: number\n  icon?: React.ComponentType<{ className?: string }>\n}\n\nexport type MailboxContact = {\n  id: string\n  name: string\n  email: string\n  avatar?: string\n}\n\nexport type MailboxMessage = {\n  id: string\n  folderId: string\n  from: MailboxContact\n  to?: MailboxContact[]\n  cc?: MailboxContact[]\n  bcc?: MailboxContact[]\n  subject: string\n  preview: string\n  body: string\n  timestamp: string\n  unread?: boolean\n  starred?: boolean\n  attachments?: number\n  thread?: MailboxThreadReply[]\n}\n\nexport type MailboxThreadReply = {\n  id: string\n  from: MailboxContact\n  to?: MailboxContact[]\n  cc?: MailboxContact[]\n  body: string\n  timestamp: string\n}\n\ntype MailboxPane = \"folders\" | \"list\" | \"detail\" | \"compose\"\nconst STARRED_FOLDER_ID = \"starred\"\n\ntype MailboxComposeDraft = {\n  to?: string[]\n  cc?: string[]\n  bcc?: string[]\n  subject?: string\n  body?: string\n}\n\nexport const defaultMailboxFolders: MailboxFolder[] = [\n  { id: \"inbox\", label: \"Inbox\", count: 6, icon: InboxIcon },\n  { id: \"drafts\", label: \"Drafts\", count: 2, icon: Edit3Icon },\n  { id: \"sent\", label: \"Sent\", icon: SendIcon },\n  { id: \"archive\", label: \"Archive\", icon: ArchiveIcon },\n  { id: \"trash\", label: \"Trash\", icon: Trash2Icon },\n]\n\nexport const defaultMailboxContacts: MailboxContact[] = [\n  { id: \"mina\", name: \"Mina Patel\", email: \"mina@corr.sh\" },\n  { id: \"avery\", name: \"Avery Stone\", email: \"avery@corr.sh\" },\n  { id: \"nora\", name: \"Nora West\", email: \"nora@corr.sh\" },\n  { id: \"theo\", name: \"Theo Brooks\", email: \"theo@corr.sh\" },\n]\n\nexport const defaultMailboxMessages: MailboxMessage[] = [\n  {\n    id: \"msg-1\",\n    folderId: \"inbox\",\n    from: defaultMailboxContacts[0],\n    to: [defaultMailboxContacts[1]],\n    cc: [defaultMailboxContacts[2]],\n    subject: \"Registry publish checklist\",\n    preview: \"The install payloads are ready for review before release.\",\n    body: \"I checked the registry payloads, preview screenshots, and install commands. The only remaining item is confirming the chart state examples before we publish.\",\n    timestamp: \"09:42\",\n    unread: true,\n    starred: true,\n    attachments: 2,\n    thread: [\n      {\n        id: \"msg-1-thread-1\",\n        from: defaultMailboxContacts[1],\n        to: [defaultMailboxContacts[0]],\n        body: \"The install command examples are good. Can we include one screenshot of the generated JSON as well?\",\n        timestamp: \"08:58\",\n      },\n      {\n        id: \"msg-1-thread-2\",\n        from: defaultMailboxContacts[0],\n        to: [defaultMailboxContacts[1]],\n        cc: [defaultMailboxContacts[2]],\n        body: \"Yes, I added the JSON preview and checked the responsive shell captures. I am just doing one last pass on chart states.\",\n        timestamp: \"09:17\",\n      },\n      {\n        id: \"msg-1-thread-3\",\n        from: defaultMailboxContacts[2],\n        to: [defaultMailboxContacts[0], defaultMailboxContacts[1]],\n        body: \"Chart states are in. Loading, empty, and error examples now all use the shared wrapper.\",\n        timestamp: \"09:31\",\n      },\n    ],\n  },\n  {\n    id: \"msg-2\",\n    folderId: \"inbox\",\n    from: defaultMailboxContacts[1],\n    subject: \"Preview shell screenshots\",\n    preview: \"Mobile and tablet frames are looking cleaner after the shell update.\",\n    body: \"The responsive preview shell now gives us a much better way to inspect the same component across breakpoints without duplicating examples.\",\n    timestamp: \"Yesterday\",\n  },\n  {\n    id: \"msg-3\",\n    folderId: \"inbox\",\n    from: defaultMailboxContacts[2],\n    subject: \"Component metrics\",\n    preview: \"Copy and install events are now visible in the dashboard.\",\n    body: \"The component metrics are flowing through the install card. We can use this to compare page views, source copies, and registry JSON requests.\",\n    timestamp: \"May 20\",\n    unread: true,\n  },\n  {\n    id: \"msg-4\",\n    folderId: \"inbox\",\n    from: defaultMailboxContacts[3],\n    subject: \"Form builder drag preview\",\n    preview: \"The drag overlay now follows the cursor more closely.\",\n    body: \"The form builder drag preview is behaving much better after moving the overlay into a body-level portal and using the grip handle for drag activation.\",\n    timestamp: \"May 19\",\n    attachments: 1,\n  },\n  {\n    id: \"msg-5\",\n    folderId: \"inbox\",\n    from: defaultMailboxContacts[1],\n    subject: \"Timeline mobile agenda\",\n    preview: \"The mobile agenda can expose slot creation between items.\",\n    body: \"The timeline mobile mode should keep the agenda readable while still making slot creation available through the inline add affordance.\",\n    timestamp: \"May 18\",\n  },\n  {\n    id: \"msg-6\",\n    folderId: \"inbox\",\n    from: defaultMailboxContacts[2],\n    subject: \"Kanban tab behaviour\",\n    preview: \"Tablet and mobile now use bucket tabs instead of squeezing columns.\",\n    body: \"The kanban board should adapt into bucket tabs at constrained widths and keep move actions available from each card menu.\",\n    timestamp: \"May 17\",\n    unread: true,\n  },\n  {\n    id: \"msg-7\",\n    folderId: \"inbox\",\n    from: defaultMailboxContacts[0],\n    subject: \"Mailbox block polish\",\n    preview: \"The toolbar, compose flow, and scroll areas are getting closer.\",\n    body: \"The mailbox block now has a top toolbar, independent scroll areas, context menus, and a compose workflow with attachments.\",\n    timestamp: \"May 16\",\n  },\n  {\n    id: \"msg-8\",\n    folderId: \"drafts\",\n    from: defaultMailboxContacts[3],\n    subject: \"Draft: launch notes\",\n    preview: \"Short release notes for the latest registry additions.\",\n    body: \"Need to mention the mailbox block, chart state utility, and responsive previews in the release notes.\",\n    timestamp: \"Draft\",\n  },\n  {\n    id: \"msg-9\",\n    folderId: \"sent\",\n    from: defaultMailboxContacts[0],\n    subject: \"Re: Design review\",\n    preview: \"Sent over the final screenshots and implementation notes.\",\n    body: \"Thanks, I sent the final screenshots and noted the mobile behaviour in the implementation summary.\",\n    timestamp: \"May 18\",\n  },\n]\n\nexport function MailboxBlock({\n  folders = defaultMailboxFolders,\n  messages: initialMessages = defaultMailboxMessages,\n  contacts = defaultMailboxContacts,\n  initialFolderId = \"inbox\",\n  initialMessageId,\n  onSend,\n  onDelete,\n  onArchive,\n  onMarkRead,\n  className,\n}: {\n  folders?: MailboxFolder[]\n  messages?: MailboxMessage[]\n  contacts?: MailboxContact[]\n  initialFolderId?: string\n  initialMessageId?: string\n  onSend?: (message: MailboxMessage) => void\n  onDelete?: (message: MailboxMessage) => void\n  onArchive?: (message: MailboxMessage) => void\n  onMarkRead?: (message: MailboxMessage) => void\n  className?: string\n}) {\n  const rootRef = React.useRef<HTMLDivElement | null>(null)\n  const [width, setWidth] = React.useState(1024)\n  const [messages, setMessages] = React.useState(initialMessages)\n  const [folderId, setFolderId] = React.useState(initialFolderId)\n  const [selectedId, setSelectedId] = React.useState(\n    initialMessageId ?? initialMessages.find((message) => message.folderId === initialFolderId)?.id\n  )\n  const [pane, setPane] = React.useState<MailboxPane>(\"list\")\n  const [deleteTarget, setDeleteTarget] = React.useState<MailboxMessage | null>(null)\n  const [query, setQuery] = React.useState(\"\")\n  const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false)\n  const [composeDraft, setComposeDraft] = React.useState<MailboxComposeDraft | undefined>()\n  const [refreshing, setRefreshing] = React.useState(false)\n  const [unreadFirst, setUnreadFirst] = React.useState(false)\n  const [compactList, setCompactList] = React.useState(false)\n  const compact = width < 760\n\n  React.useEffect(() => {\n    const element = rootRef.current\n    if (!element) return\n\n    const observer = new ResizeObserver(([entry]) => {\n      setWidth(entry.contentRect.width)\n    })\n    observer.observe(element)\n    return () => observer.disconnect()\n  }, [])\n\n  const selectedFolder =\n    folderId === STARRED_FOLDER_ID\n      ? { id: STARRED_FOLDER_ID, label: \"Starred\", icon: StarIcon }\n      : folders.find((folder) => folder.id === folderId) ?? folders[0]\n  const folderMessages = messages\n    .filter(\n      (message) =>\n        (folderId === STARRED_FOLDER_ID ? message.starred : message.folderId === folderId) &&\n        `${message.subject} ${message.from.name} ${message.preview}`\n          .toLowerCase()\n          .includes(query.toLowerCase())\n    )\n    .sort((first, second) => {\n      if (!unreadFirst) return 0\n      return Number(Boolean(second.unread)) - Number(Boolean(first.unread))\n    })\n  const selectedMessage =\n    refreshing ? undefined : messages.find((message) => message.id === selectedId) ?? folderMessages[0]\n\n  React.useEffect(() => {\n    if (refreshing) return\n    if (selectedId || !folderMessages[0]) return\n    setSelectedId(folderMessages[0].id)\n  }, [folderMessages, refreshing, selectedId])\n\n  function selectFolder(nextFolderId: string) {\n    setFolderId(nextFolderId)\n    const nextMessage =\n      nextFolderId === STARRED_FOLDER_ID\n        ? messages.find((message) => message.starred)\n        : messages.find((message) => message.folderId === nextFolderId)\n    setSelectedId(nextMessage?.id)\n    setPane(\"list\")\n  }\n\n  function markAllAsRead() {\n    setMessages((current) =>\n      current.map((message) =>\n        folderId === STARRED_FOLDER_ID\n          ? message.starred\n            ? { ...message, unread: false }\n            : message\n          : message.folderId === folderId\n            ? { ...message, unread: false }\n            : message\n      )\n    )\n    toast.success(\"Mailbox marked as read\", {\n      description: `${selectedFolder.label} has no unread messages.`,\n    })\n  }\n\n  function refreshMailbox() {\n    setRefreshing(true)\n    setSelectedId(undefined)\n    window.setTimeout(() => {\n      setRefreshing(false)\n      setSelectedId(folderMessages[0]?.id)\n      toast.success(\"Mailbox refreshed\", {\n        description: \"Latest messages are up to date.\",\n      })\n    }, 900)\n  }\n\n  function toggleUnreadFirst() {\n    setUnreadFirst((current) => !current)\n  }\n\n  function toggleCompactList() {\n    setCompactList((current) => !current)\n  }\n\n  function selectMessage(message: MailboxMessage) {\n    setSelectedId(message.id)\n    if (message.unread) {\n      const readMessage = { ...message, unread: false }\n      setMessages((current) =>\n        current.map((item) => (item.id === message.id ? readMessage : item))\n      )\n      onMarkRead?.(readMessage)\n    }\n    if (compact) setPane(\"detail\")\n  }\n\n  function archiveMessage(message: MailboxMessage) {\n    const archived = { ...message, folderId: \"archive\", unread: false }\n    setMessages((current) =>\n      current.map((item) => (item.id === message.id ? archived : item))\n    )\n    onArchive?.(archived)\n    toast.success(\"Email archived\", { description: message.subject })\n    if (compact) setPane(\"list\")\n  }\n\n  function deleteMessage(message: MailboxMessage) {\n    setMessages((current) => current.filter((item) => item.id !== message.id))\n    onDelete?.(message)\n    setDeleteTarget(null)\n    setSelectedId(folderMessages.find((item) => item.id !== message.id)?.id)\n    toast.error(\"Email deleted\", { description: message.subject })\n    if (compact) setPane(\"list\")\n  }\n\n  function moveMessage(message: MailboxMessage, nextFolderId: string) {\n    const moved = { ...message, folderId: nextFolderId, unread: false }\n    setMessages((current) =>\n      current.map((item) => (item.id === message.id ? moved : item))\n    )\n    setSelectedId(folderMessages.find((item) => item.id !== message.id)?.id)\n    const nextFolder = folders.find((folder) => folder.id === nextFolderId)\n    toast.success(\"Email moved\", {\n      description: `${message.subject} moved to ${nextFolder?.label ?? \"folder\"}.`,\n    })\n  }\n\n  function toggleStarMessage(message: MailboxMessage) {\n    const nextMessage = { ...message, starred: !message.starred }\n    setMessages((current) =>\n      current.map((item) => (item.id === message.id ? nextMessage : item))\n    )\n    if (folderId === STARRED_FOLDER_ID && message.starred) {\n      setSelectedId(messages.find((item) => item.id !== message.id && item.starred)?.id)\n    }\n  }\n\n  function openCompose(draft?: MailboxComposeDraft) {\n    setComposeDraft(draft)\n    setPane(\"compose\")\n  }\n\n  function replyToMessage(message: MailboxMessage) {\n    setSelectedId(message.id)\n    openCompose({\n      to: [message.from.email],\n      subject: withSubjectPrefix(message.subject, \"Re:\"),\n      body: buildThreadQuote(message, \"reply\"),\n    })\n  }\n\n  function replyAllToMessage(message: MailboxMessage) {\n    const currentUserEmail = contacts[0]?.email\n    const emails = uniqueEmails([\n      message.from.email,\n      ...(message.to ?? []).map((contact) => contact.email),\n    ]).filter((email) => email !== currentUserEmail)\n    const ccEmails = uniqueEmails((message.cc ?? []).map((contact) => contact.email)).filter(\n      (email) => email !== currentUserEmail && !emails.includes(email)\n    )\n\n    setSelectedId(message.id)\n    openCompose({\n      to: emails,\n      cc: ccEmails,\n      subject: withSubjectPrefix(message.subject, \"Re:\"),\n      body: buildThreadQuote(message, \"reply\"),\n    })\n  }\n\n  function forwardMessage(message: MailboxMessage) {\n    setSelectedId(message.id)\n    openCompose({\n      subject: withSubjectPrefix(message.subject, \"Fwd:\"),\n      body: buildThreadQuote(message, \"forward\"),\n    })\n  }\n\n  return (\n    <Card\n      ref={rootRef}\n      data-slot=\"mailbox-block\"\n      className={cn(\"relative h-[34rem] min-w-0 overflow-hidden p-0\", className)}\n    >\n      <SidebarProvider\n        className=\"h-full min-h-0\"\n        style={\n          {\n            \"--sidebar-width\": compact ? \"15rem\" : sidebarCollapsed ? \"4.25rem\" : \"15rem\",\n          } as React.CSSProperties\n        }\n      >\n        {compact ? (\n          <div className=\"flex h-full min-w-0 flex-1 flex-col\">\n            <MailboxToolbar\n              compact\n              pane={pane}\n              query={query}\n              onQueryChange={setQuery}\n              showSearch={width > 560}\n              iconOnlyCompose={width < 640}\n              selectedMessage={selectedMessage}\n              unreadFirst={unreadFirst}\n              compactList={compactList}\n              onMarkAllRead={markAllAsRead}\n              onRefresh={refreshMailbox}\n              onShowArchived={() => selectFolder(\"archive\")}\n              onReply={() => selectedMessage && replyToMessage(selectedMessage)}\n              onForward={() => selectedMessage && forwardMessage(selectedMessage)}\n              onDelete={() => selectedMessage && setDeleteTarget(selectedMessage)}\n              onShowStarred={() => selectFolder(STARRED_FOLDER_ID)}\n              onToggleUnreadFirst={toggleUnreadFirst}\n              onToggleCompactList={toggleCompactList}\n              onBack={() => setPane(pane === \"detail\" || pane === \"compose\" ? \"list\" : \"folders\")}\n              onFolders={() => setPane(\"folders\")}\n              onCompose={() => openCompose()}\n            />\n            <div className=\"grid min-h-0 flex-1 grid-cols-1\">\n              {pane === \"folders\" ? (\n                <FolderPane\n                  folders={folders}\n                  messages={messages}\n                  folderId={folderId}\n                  onSelect={selectFolder}\n                  onCompose={() => openCompose()}\n                />\n              ) : null}\n              {pane === \"list\" ? (\n                <MessageListPane\n                  folder={selectedFolder}\n                  folders={folders}\n                  messages={folderMessages}\n                  selectedId={selectedMessage?.id}\n                  refreshing={refreshing}\n                  compactList={compactList}\n                  query={query}\n                  onQueryChange={setQuery}\n                  onSelect={selectMessage}\n                  onReply={replyToMessage}\n                  onReplyAll={replyAllToMessage}\n                  onForward={forwardMessage}\n                  onArchive={archiveMessage}\n                  onDelete={setDeleteTarget}\n                  onMove={moveMessage}\n                  showHeader={width <= 560}\n                />\n              ) : null}\n              {pane === \"detail\" && selectedMessage ? (\n                <MessageDetailPane\n                  message={selectedMessage}\n                  onReply={() => replyToMessage(selectedMessage)}\n                  onReplyAll={() => replyAllToMessage(selectedMessage)}\n                  onForward={() => forwardMessage(selectedMessage)}\n                  onToggleStar={() => toggleStarMessage(selectedMessage)}\n                  onArchive={() => archiveMessage(selectedMessage)}\n                  onDelete={() => setDeleteTarget(selectedMessage)}\n                />\n              ) : null}\n              {pane === \"compose\" ? (\n                <ComposePane\n                  contacts={contacts}\n                  draft={composeDraft}\n                  onCancel={() => setPane(selectedMessage ? \"detail\" : \"list\")}\n                  onSend={(message) => {\n                    setMessages((current) => [message, ...current])\n                    onSend?.(message)\n                    setComposeDraft(undefined)\n                    toast.success(\"Email sent\", { description: message.subject })\n                    selectFolder(\"sent\")\n                  }}\n                />\n              ) : null}\n            </div>\n          </div>\n        ) : (\n          <>\n            <MailboxDesktopToolbar\n              onCompose={() => openCompose()}\n              query={query}\n              onQueryChange={setQuery}\n              selectedMessage={selectedMessage}\n              unreadFirst={unreadFirst}\n              compactList={compactList}\n              onMarkAllRead={markAllAsRead}\n              onRefresh={refreshMailbox}\n              onShowArchived={() => selectFolder(\"archive\")}\n              onReply={() => selectedMessage && replyToMessage(selectedMessage)}\n              onForward={() => selectedMessage && forwardMessage(selectedMessage)}\n              onDelete={() => selectedMessage && setDeleteTarget(selectedMessage)}\n              onShowStarred={() => selectFolder(STARRED_FOLDER_ID)}\n              onToggleUnreadFirst={toggleUnreadFirst}\n              onToggleCompactList={toggleCompactList}\n            />\n            <Sidebar collapsible=\"none\" className=\"min-h-0 border-r pt-10\">\n              <FolderPane\n                folders={folders}\n                messages={messages}\n                folderId={folderId}\n                onSelect={selectFolder}\n                onCompose={() => openCompose()}\n                collapsed={sidebarCollapsed}\n                onToggleCollapsed={() => setSidebarCollapsed((collapsed) => !collapsed)}\n              />\n            </Sidebar>\n            <SidebarInset className=\"min-w-0 bg-background pt-10\">\n              {pane === \"compose\" ? (\n                <ComposePane\n                  contacts={contacts}\n                  draft={composeDraft}\n                  onCancel={() => setPane(selectedMessage ? \"detail\" : \"list\")}\n                  onSend={(message) => {\n                    setMessages((current) => [message, ...current])\n                    onSend?.(message)\n                    setComposeDraft(undefined)\n                    toast.success(\"Email sent\", { description: message.subject })\n                    selectFolder(\"sent\")\n                  }}\n                />\n              ) : (\n                <ResizablePanelGroup orientation=\"horizontal\" className=\"min-h-0 min-w-0 flex-1\">\n                  <ResizablePanel defaultSize=\"42%\" minSize=\"5rem\" maxSize=\"82%\" className=\"min-w-0 overflow-hidden\">\n                    <MessageListPane\n                      folder={selectedFolder}\n                      folders={folders}\n                      messages={folderMessages}\n                      selectedId={selectedMessage?.id}\n                      refreshing={refreshing}\n                      compactList={compactList}\n                      query={query}\n                      onQueryChange={setQuery}\n                      onSelect={selectMessage}\n                      onReply={replyToMessage}\n                      onReplyAll={replyAllToMessage}\n                      onForward={forwardMessage}\n                      onArchive={archiveMessage}\n                      onDelete={setDeleteTarget}\n                      onMove={moveMessage}\n                      showHeader={false}\n                    />\n                  </ResizablePanel>\n                  <ResizableHandle withHandle />\n                  <ResizablePanel defaultSize=\"58%\" minSize=\"7rem\" className=\"min-w-0 overflow-hidden\">\n                    {selectedMessage ? (\n                      <MessageDetailPane\n                        message={selectedMessage}\n                        onReply={() => replyToMessage(selectedMessage)}\n                        onReplyAll={() => replyAllToMessage(selectedMessage)}\n                        onForward={() => forwardMessage(selectedMessage)}\n                        onToggleStar={() => toggleStarMessage(selectedMessage)}\n                        onArchive={() => archiveMessage(selectedMessage)}\n                        onDelete={() => setDeleteTarget(selectedMessage)}\n                      />\n                    ) : null}\n                  </ResizablePanel>\n                </ResizablePanelGroup>\n              )}\n            </SidebarInset>\n          </>\n        )}\n      </SidebarProvider>\n      <AlertDialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>\n        <AlertDialogContent>\n          <AlertDialogHeader>\n            <AlertDialogTitle>Delete this email?</AlertDialogTitle>\n            <AlertDialogDescription>\n              This removes “{deleteTarget?.subject}” from the mailbox demo.\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <AlertDialogFooter>\n            <AlertDialogCancel>Cancel</AlertDialogCancel>\n            <AlertDialogAction\n              className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n              onClick={() => deleteTarget && deleteMessage(deleteTarget)}\n            >\n              Delete\n            </AlertDialogAction>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n    </Card>\n  )\n}\n\nfunction MailboxDesktopToolbar({\n  query,\n  onQueryChange,\n  onCompose,\n  selectedMessage,\n  unreadFirst,\n  compactList,\n  onMarkAllRead,\n  onRefresh,\n  onShowArchived,\n  onReply,\n  onForward,\n  onDelete,\n  onShowStarred,\n  onToggleUnreadFirst,\n  onToggleCompactList,\n}: {\n  query: string\n  onQueryChange: (query: string) => void\n  onCompose: () => void\n  selectedMessage?: MailboxMessage\n  unreadFirst: boolean\n  compactList: boolean\n  onMarkAllRead: () => void\n  onRefresh: () => void\n  onShowArchived: () => void\n  onReply: () => void\n  onForward: () => void\n  onDelete: () => void\n  onShowStarred: () => void\n  onToggleUnreadFirst: () => void\n  onToggleCompactList: () => void\n}) {\n  return (\n    <div className=\"absolute inset-x-0 top-0 z-20 flex h-10 items-center gap-2 border-b bg-background/95 px-3 backdrop-blur\">\n      <Menubar className=\"h-8 border-0 bg-transparent p-0 shadow-none\">\n        <MenubarMenu>\n          <MenubarTrigger>Mailbox</MenubarTrigger>\n          <MenubarContent>\n            <MenubarItem onSelect={onMarkAllRead}>Mark all as read</MenubarItem>\n            <MenubarItem onSelect={onRefresh}>\n              <RefreshCcwIcon />\n              Refresh mailbox\n            </MenubarItem>\n            <MenubarSeparator />\n            <MenubarItem onSelect={onShowArchived}>Show archived</MenubarItem>\n          </MenubarContent>\n        </MenubarMenu>\n        <MenubarMenu>\n          <MenubarTrigger>Message</MenubarTrigger>\n          <MenubarContent>\n            <MenubarItem onSelect={onCompose}>\n              <Edit3Icon />\n              Compose\n            </MenubarItem>\n            <MenubarItem disabled={!selectedMessage} onSelect={onReply}>\n              <ReplyIcon />\n              Reply\n            </MenubarItem>\n            <MenubarItem disabled={!selectedMessage} onSelect={onForward}>\n              <ForwardIcon />\n              Forward\n            </MenubarItem>\n            <MenubarSeparator />\n            <MenubarItem disabled={!selectedMessage} variant=\"destructive\" onSelect={onDelete}>\n              <Trash2Icon />\n              Delete\n            </MenubarItem>\n          </MenubarContent>\n        </MenubarMenu>\n        <MenubarMenu>\n          <MenubarTrigger>View</MenubarTrigger>\n          <MenubarContent>\n            <MenubarItem onSelect={onToggleUnreadFirst}>\n              {unreadFirst ? \"Default order\" : \"Unread first\"}\n            </MenubarItem>\n            <MenubarItem onSelect={onShowStarred}>Show starred</MenubarItem>\n            <MenubarItem onSelect={onToggleCompactList}>\n              {compactList ? \"Comfortable list\" : \"Compact list\"}\n            </MenubarItem>\n          </MenubarContent>\n        </MenubarMenu>\n      </Menubar>\n      <div className=\"relative ml-auto w-52 shrink-0\">\n        <SearchIcon className=\"absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n        <Input\n          value={query}\n          onChange={(event) => onQueryChange(event.target.value)}\n          placeholder=\"Search mail\"\n          className=\"h-8 pl-7\"\n        />\n      </div>\n      <Button size=\"sm\" onClick={onCompose}>\n        <Edit3Icon className=\"size-3.5\" />\n        Compose\n      </Button>\n    </div>\n  )\n}\n\nfunction MailboxToolbar({\n  compact,\n  pane,\n  query,\n  onQueryChange,\n  showSearch,\n  onBack,\n  onFolders,\n  onCompose,\n  iconOnlyCompose = false,\n  selectedMessage,\n  unreadFirst,\n  compactList,\n  onMarkAllRead,\n  onRefresh,\n  onShowArchived,\n  onReply,\n  onForward,\n  onDelete,\n  onShowStarred,\n  onToggleUnreadFirst,\n  onToggleCompactList,\n}: {\n  compact: boolean\n  pane: MailboxPane\n  query: string\n  onQueryChange: (query: string) => void\n  showSearch: boolean\n  onBack: () => void\n  onFolders: () => void\n  onCompose: () => void\n  iconOnlyCompose?: boolean\n  selectedMessage?: MailboxMessage\n  unreadFirst: boolean\n  compactList: boolean\n  onMarkAllRead: () => void\n  onRefresh: () => void\n  onShowArchived: () => void\n  onReply: () => void\n  onForward: () => void\n  onDelete: () => void\n  onShowStarred: () => void\n  onToggleUnreadFirst: () => void\n  onToggleCompactList: () => void\n}) {\n  return (\n    <div className=\"scrollbar-none flex h-10 shrink-0 items-center gap-2 overflow-x-auto border-b px-2\">\n      {compact && pane !== \"folders\" ? (\n        <Button\n          variant=\"ghost\"\n          size=\"icon-sm\"\n          onClick={onBack}\n          aria-label=\"Back\"\n          className=\"shrink-0\"\n        >\n          <ArrowLeftIcon />\n        </Button>\n      ) : null}\n      {compact ? (\n        <Button\n          variant=\"outline\"\n          size={compact ? \"icon-sm\" : \"sm\"}\n          onClick={onFolders}\n          aria-label=\"Folders\"\n          className=\"h-8 shrink-0\"\n        >\n          <FolderIcon className=\"size-3.5\" />\n          {!compact ? <span>Folders</span> : null}\n        </Button>\n      ) : null}\n      <Menubar className=\"h-8 shrink-0 border-0 bg-transparent p-0 shadow-none\">\n        <MenubarMenu>\n          <MenubarTrigger\n            aria-label=\"Mailbox actions\"\n            className={cn(compact && \"h-8 w-8 justify-center px-0\")}\n          >\n            {compact ? (\n              <>\n                <InboxIcon className=\"size-3.5\" />\n                <span className=\"sr-only\">Mailbox</span>\n              </>\n            ) : (\n              \"Mailbox\"\n            )}\n          </MenubarTrigger>\n          <MenubarContent>\n            <MenubarItem onSelect={onMarkAllRead}>Mark all as read</MenubarItem>\n            <MenubarItem onSelect={onRefresh}>\n              <RefreshCcwIcon />\n              Refresh mailbox\n            </MenubarItem>\n            <MenubarSeparator />\n            <MenubarItem onSelect={onShowArchived}>Show archived</MenubarItem>\n          </MenubarContent>\n        </MenubarMenu>\n        <MenubarMenu>\n          <MenubarTrigger\n            aria-label=\"Message actions\"\n            className={cn(compact && \"h-8 w-8 justify-center px-0\")}\n          >\n            {compact ? (\n              <>\n                <MailIcon className=\"size-3.5\" />\n                <span className=\"sr-only\">Message</span>\n              </>\n            ) : (\n              \"Message\"\n            )}\n          </MenubarTrigger>\n          <MenubarContent>\n            <MenubarItem onSelect={onCompose}>\n              <Edit3Icon />\n              Compose\n            </MenubarItem>\n            <MenubarItem disabled={!selectedMessage} onSelect={onReply}>\n              <ReplyIcon />\n              Reply\n            </MenubarItem>\n            <MenubarItem disabled={!selectedMessage} onSelect={onForward}>\n              <ForwardIcon />\n              Forward\n            </MenubarItem>\n            <MenubarSeparator />\n            <MenubarItem disabled={!selectedMessage} variant=\"destructive\" onSelect={onDelete}>\n              <Trash2Icon />\n              Delete\n            </MenubarItem>\n          </MenubarContent>\n        </MenubarMenu>\n        <MenubarMenu>\n          <MenubarTrigger\n            aria-label=\"View options\"\n            className={cn(compact && \"h-8 w-8 justify-center px-0\")}\n          >\n            {compact ? (\n              <>\n                <PanelLeftIcon className=\"size-3.5\" />\n                <span className=\"sr-only\">View</span>\n              </>\n            ) : (\n              \"View\"\n            )}\n          </MenubarTrigger>\n          <MenubarContent>\n            <MenubarItem onSelect={onToggleUnreadFirst}>\n              {unreadFirst ? \"Default order\" : \"Unread first\"}\n            </MenubarItem>\n            <MenubarItem onSelect={onShowStarred}>Show starred</MenubarItem>\n            <MenubarItem onSelect={onToggleCompactList}>\n              {compactList ? \"Comfortable list\" : \"Compact list\"}\n            </MenubarItem>\n          </MenubarContent>\n        </MenubarMenu>\n      </Menubar>\n      {showSearch ? (\n        <div className=\"relative ml-auto w-44 shrink-0\">\n          <SearchIcon className=\"absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n          <Input\n            value={query}\n            onChange={(event) => onQueryChange(event.target.value)}\n            placeholder=\"Search mail\"\n            className=\"h-8 pl-7\"\n          />\n        </div>\n      ) : <span className=\"min-w-2 flex-1\" />}\n      <Button\n        size=\"icon-sm\"\n        onClick={onCompose}\n        aria-label=\"Compose\"\n        className={cn(\n          \"h-8 shrink-0\",\n          compact || iconOnlyCompose ? \"w-8\" : \"w-auto px-3\"\n        )}\n      >\n        <Edit3Icon className=\"size-3.5\" />\n        <span className={compact || iconOnlyCompose ? \"sr-only\" : undefined}>Compose</span>\n      </Button>\n    </div>\n  )\n}\n\nfunction FolderPane({\n  folders,\n  messages,\n  folderId,\n  onSelect,\n  onCompose,\n  collapsed = false,\n  onToggleCollapsed,\n}: {\n  folders: MailboxFolder[]\n  messages: MailboxMessage[]\n  folderId: string\n  onSelect: (folderId: string) => void\n  onCompose: () => void\n  collapsed?: boolean\n  onToggleCollapsed?: () => void\n}) {\n  return (\n    <>\n      <SidebarHeader className=\"border-b p-3\">\n        <SidebarMenu>\n          <SidebarMenuItem>\n            <div className={cn(\"flex items-center gap-3\", collapsed && \"justify-center\")}>\n              {!collapsed ? (\n                <SidebarMenuButton size=\"lg\" className=\"min-w-0 flex-1 gap-3\">\n                  <span className=\"flex size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\">\n                    <MailIcon className=\"size-4\" />\n                  </span>\n                  <span className=\"grid min-w-0 flex-1 text-left text-sm leading-tight\">\n                    <span className=\"truncate font-semibold\">Acme Mail</span>\n                    <span className=\"truncate text-xs text-muted-foreground\">Shared inbox</span>\n                  </span>\n                </SidebarMenuButton>\n              ) : null}\n              {onToggleCollapsed ? (\n                <Button\n                  type=\"button\"\n                  variant=\"ghost\"\n                  size=\"icon-sm\"\n                  className=\"shrink-0\"\n                  onClick={onToggleCollapsed}\n                  aria-label={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n                >\n                  <PanelLeftIcon className=\"size-4\" />\n                </Button>\n              ) : null}\n            </div>\n          </SidebarMenuItem>\n        </SidebarMenu>\n      </SidebarHeader>\n      <SidebarContent className=\"min-h-0 flex-1 overflow-hidden\">\n        <ScrollBlur className=\"h-full min-h-0\" edgeSize={24}>\n          <SidebarGroup>\n            {!collapsed ? <SidebarGroupLabel>Mailboxes</SidebarGroupLabel> : null}\n            <SidebarGroupContent>\n              <SidebarMenu>\n                {folders.map((folder) => {\n                  const Icon = folder.icon ?? MailIcon\n                  const count = messages.filter((message) => message.folderId === folder.id).length\n                  return (\n                    <SidebarMenuItem key={folder.id}>\n                      <SidebarMenuButton\n                        isActive={folderId === folder.id}\n                        onClick={() => onSelect(folder.id)}\n                        tooltip={collapsed ? folder.label : undefined}\n                        className={cn(collapsed && \"justify-center px-0\")}\n                      >\n                        <Icon />\n                        {!collapsed ? <span>{folder.label}</span> : null}\n                        {count && !collapsed ? (\n                          <Badge\n                            variant=\"secondary\"\n                            className=\"ml-auto h-5 rounded-full px-1.5 text-[10px]\"\n                          >\n                            {count}\n                          </Badge>\n                        ) : null}\n                      </SidebarMenuButton>\n                    </SidebarMenuItem>\n                  )\n                })}\n              </SidebarMenu>\n            </SidebarGroupContent>\n          </SidebarGroup>\n          <SidebarGroup>\n            {!collapsed ? <SidebarGroupLabel>Actions</SidebarGroupLabel> : null}\n            <SidebarGroupContent>\n              <SidebarMenu>\n                <SidebarMenuItem>\n                  <SidebarMenuButton\n                    onClick={onCompose}\n                    tooltip={collapsed ? \"Compose\" : undefined}\n                    className={cn(collapsed && \"justify-center px-0\")}\n                  >\n                    <Edit3Icon />\n                    {!collapsed ? <span>Compose</span> : null}\n                  </SidebarMenuButton>\n                </SidebarMenuItem>\n                <SidebarMenuItem>\n                  <SidebarMenuButton\n                    isActive={folderId === STARRED_FOLDER_ID}\n                    onClick={() => onSelect(STARRED_FOLDER_ID)}\n                    tooltip={collapsed ? \"Starred\" : undefined}\n                    className={cn(collapsed && \"justify-center px-0\")}\n                  >\n                    <StarIcon />\n                    {!collapsed ? <span>Starred</span> : null}\n                    {messages.filter((message) => message.starred).length && !collapsed ? (\n                      <Badge\n                        variant=\"secondary\"\n                        className=\"ml-auto h-5 rounded-full px-1.5 text-[10px]\"\n                      >\n                        {messages.filter((message) => message.starred).length}\n                      </Badge>\n                    ) : null}\n                  </SidebarMenuButton>\n                </SidebarMenuItem>\n              </SidebarMenu>\n            </SidebarGroupContent>\n          </SidebarGroup>\n        </ScrollBlur>\n      </SidebarContent>\n      {!collapsed ? (\n        <SidebarFooter className=\"border-t\">\n        <SidebarMenu>\n          <SidebarMenuItem>\n            <SidebarMenuButton size=\"lg\" className=\"gap-3\">\n              <Avatar className=\"size-8 rounded-lg after:hidden\">\n                <AvatarImage className=\"rounded-lg\" alt=\"\" />\n                <AvatarFallback className=\"rounded-lg\">LC</AvatarFallback>\n              </Avatar>\n              <span className=\"grid min-w-0 flex-1 text-left text-sm leading-tight\">\n                <span className=\"truncate font-medium\">Liam Corrigan</span>\n                <span className=\"truncate text-xs text-muted-foreground\">liam@corr.sh</span>\n              </span>\n            </SidebarMenuButton>\n          </SidebarMenuItem>\n        </SidebarMenu>\n        </SidebarFooter>\n      ) : null}\n    </>\n  )\n}\n\nfunction MessageListPane({\n  folder,\n  folders,\n  messages,\n  selectedId,\n  refreshing = false,\n  compactList = false,\n  query,\n  onQueryChange,\n  onSelect,\n  onReply,\n  onReplyAll,\n  onForward,\n  onArchive,\n  onDelete,\n  onMove,\n  showHeader = true,\n}: {\n  folder: MailboxFolder\n  folders: MailboxFolder[]\n  messages: MailboxMessage[]\n  selectedId?: string\n  refreshing?: boolean\n  compactList?: boolean\n  query: string\n  onQueryChange: (query: string) => void\n  onSelect: (message: MailboxMessage) => void\n  onReply: (message: MailboxMessage) => void\n  onReplyAll: (message: MailboxMessage) => void\n  onForward: (message: MailboxMessage) => void\n  onArchive: (message: MailboxMessage) => void\n  onDelete: (message: MailboxMessage) => void\n  onMove: (message: MailboxMessage, folderId: string) => void\n  showHeader?: boolean\n}) {\n  return (\n    <section className=\"flex h-full min-h-0 min-w-0 flex-col overflow-hidden\">\n      {showHeader ? (\n        <div className=\"h-14 min-w-0 overflow-hidden border-b p-3\">\n          <div className=\"relative\">\n            <SearchIcon className=\"absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n            <Input\n              value={query}\n              onChange={(event) => onQueryChange(event.target.value)}\n              placeholder={`Search ${folder.label.toLowerCase()}`}\n              className=\"h-8 pl-7\"\n            />\n          </div>\n        </div>\n      ) : null}\n      <ScrollBlur className=\"min-h-0 flex-1\" edgeSize={28}>\n        <div className=\"space-y-1 p-2\">\n          {refreshing ? (\n            <MessageListSkeleton />\n          ) : (\n            messages.map((message) => (\n              <ContextMenu key={message.id}>\n                <ContextMenuTrigger asChild>\n                  <button\n                    type=\"button\"\n                    onClick={() => onSelect(message)}\n                    className={cn(\n                      \"w-full rounded-md border text-left transition-colors hover:bg-muted/40\",\n                      compactList ? \"p-2\" : \"p-3\",\n                      selectedId === message.id && \"border-foreground/20 bg-muted/30\"\n                    )}\n                  >\n                    <MessageListItem message={message} compact={compactList} />\n                  </button>\n                </ContextMenuTrigger>\n                <MessageContextMenu\n                  message={message}\n                  folders={folders}\n                  currentFolderId={folder.id}\n                  onReply={onReply}\n                  onReplyAll={onReplyAll}\n                  onForward={onForward}\n                  onArchive={onArchive}\n                  onDelete={onDelete}\n                  onMove={onMove}\n                />\n              </ContextMenu>\n            ))\n          )}\n        </div>\n      </ScrollBlur>\n      <div className=\"flex h-8 shrink-0 items-center border-t px-3 text-xs text-muted-foreground\">\n        {refreshing ? \"Refreshing...\" : `${messages.length} message${messages.length === 1 ? \"\" : \"s\"}`}\n      </div>\n    </section>\n  )\n}\n\nfunction MessageListItem({ message, compact = false }: { message: MailboxMessage; compact?: boolean }) {\n  return (\n    <div className=\"flex min-w-0 items-start gap-2\">\n      <Avatar className={cn(\"shrink-0 rounded-md after:hidden\", compact ? \"size-7\" : \"size-8\")}>\n        <AvatarImage src={message.from.avatar} alt=\"\" className=\"rounded-md\" />\n        <AvatarFallback className=\"rounded-md\">\n          {initials(message.from.name)}\n        </AvatarFallback>\n      </Avatar>\n      <span className=\"min-w-0 flex-1 overflow-hidden\">\n        <span className=\"flex items-center gap-2\">\n          <span className={cn(\"truncate text-sm\", message.unread && \"font-semibold\")}>\n            {message.from.name}\n          </span>\n          <span className=\"ml-auto shrink-0 text-[11px] text-muted-foreground\">\n            {message.timestamp}\n          </span>\n        </span>\n        <span className={cn(\"block truncate text-xs font-medium\", compact ? \"mt-0.5\" : \"mt-1\")}>\n          {message.subject}\n        </span>\n        {!compact ? (\n          <span className=\"mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground\">\n            {message.preview}\n          </span>\n        ) : null}\n      </span>\n    </div>\n  )\n}\n\nfunction MessageListSkeleton() {\n  return (\n    <>\n      {Array.from({ length: 4 }).map((_, index) => (\n        <div key={index} className=\"flex gap-2 rounded-md border p-3\">\n          <div className=\"size-8 shrink-0 animate-pulse rounded-md bg-muted\" />\n          <div className=\"min-w-0 flex-1 space-y-2\">\n            <div className=\"h-3 w-2/5 animate-pulse rounded bg-muted\" />\n            <div className=\"h-3 w-3/5 animate-pulse rounded bg-muted\" />\n            <div className=\"h-3 w-4/5 animate-pulse rounded bg-muted\" />\n          </div>\n        </div>\n      ))}\n    </>\n  )\n}\n\nfunction MessageContextMenu({\n  message,\n  folders,\n  currentFolderId,\n  onReply,\n  onReplyAll,\n  onForward,\n  onArchive,\n  onDelete,\n  onMove,\n}: {\n  message: MailboxMessage\n  folders: MailboxFolder[]\n  currentFolderId: string\n  onReply: (message: MailboxMessage) => void\n  onReplyAll: (message: MailboxMessage) => void\n  onForward: (message: MailboxMessage) => void\n  onArchive: (message: MailboxMessage) => void\n  onDelete: (message: MailboxMessage) => void\n  onMove: (message: MailboxMessage, folderId: string) => void\n}) {\n  return (\n    <ContextMenuContent className=\"w-52\">\n      <ContextMenuItem onSelect={() => onReply(message)}>\n        <ReplyIcon />\n        Reply\n      </ContextMenuItem>\n      <ContextMenuItem onSelect={() => onReplyAll(message)}>\n        <ReplyIcon />\n        Reply all\n      </ContextMenuItem>\n      <ContextMenuItem onSelect={() => onForward(message)}>\n        <ForwardIcon />\n        Forward\n      </ContextMenuItem>\n      <ContextMenuSeparator />\n      <ContextMenuSub>\n        <ContextMenuSubTrigger>\n          <FolderIcon />\n          Move to folder\n        </ContextMenuSubTrigger>\n        <ContextMenuSubContent className=\"w-40\">\n          {folders.map((folder) => {\n            const Icon = folder.icon ?? FolderIcon\n            return (\n              <ContextMenuItem\n                key={folder.id}\n                disabled={folder.id === currentFolderId}\n                onSelect={() => onMove(message, folder.id)}\n              >\n                <Icon />\n                {folder.label}\n              </ContextMenuItem>\n            )\n          })}\n        </ContextMenuSubContent>\n      </ContextMenuSub>\n      <ContextMenuItem onSelect={() => onArchive(message)}>\n        <ArchiveIcon />\n        Archive\n      </ContextMenuItem>\n      <ContextMenuSeparator />\n      <ContextMenuItem variant=\"destructive\" onSelect={() => onDelete(message)}>\n        <Trash2Icon />\n        Delete\n      </ContextMenuItem>\n    </ContextMenuContent>\n  )\n}\n\nfunction MessageDetailPane({\n  message,\n  onReply,\n  onReplyAll,\n  onForward,\n  onToggleStar,\n  onArchive,\n  onDelete,\n}: {\n  message: MailboxMessage\n  onReply: () => void\n  onReplyAll: () => void\n  onForward: () => void\n  onToggleStar: () => void\n  onArchive: () => void\n  onDelete: () => void\n}) {\n  return (\n    <section className=\"min-h-0 min-w-0 flex-1 overflow-hidden\">\n      <ScrollBlur className=\"h-full min-h-0\" edgeSize={32}>\n        <article className=\"min-w-0 space-y-4 p-4\">\n          <div className=\"flex items-start gap-3\">\n            <Avatar className=\"size-10 rounded-md after:hidden\">\n              <AvatarImage src={message.from.avatar} alt=\"\" className=\"rounded-md\" />\n              <AvatarFallback className=\"rounded-md\">\n                {initials(message.from.name)}\n              </AvatarFallback>\n            </Avatar>\n            <div className=\"min-w-0 flex-1\">\n              <div className=\"flex items-start justify-between gap-3\">\n                <div className=\"min-w-0\">\n                  <h2 className=\"break-words text-lg font-semibold tracking-tight\">\n                    {message.subject}\n                  </h2>\n                  <p className=\"break-words text-sm text-muted-foreground\">\n                    {message.from.name} · {message.from.email}\n                  </p>\n                </div>\n                <DropdownMenu>\n                  <DropdownMenuTrigger asChild>\n                    <Button variant=\"ghost\" size=\"icon-sm\">\n                      <MoreHorizontalIcon />\n                    </Button>\n                  </DropdownMenuTrigger>\n                  <DropdownMenuContent align=\"end\">\n                    <DropdownMenuItem onClick={onArchive}>\n                      <ArchiveIcon />\n                      Archive\n                    </DropdownMenuItem>\n                    <DropdownMenuItem variant=\"destructive\" onClick={onDelete}>\n                      <Trash2Icon />\n                      Delete\n                    </DropdownMenuItem>\n                  </DropdownMenuContent>\n                </DropdownMenu>\n              </div>\n            </div>\n          </div>\n          <Separator />\n          <p className=\"break-words text-sm leading-7 text-muted-foreground\">{message.body}</p>\n          {message.thread?.length ? (\n            <div className=\"space-y-2 border-t pt-4\">\n              <div className=\"text-xs font-medium text-muted-foreground\">\n                {message.thread.length} earlier repl{message.thread.length === 1 ? \"y\" : \"ies\"}\n              </div>\n              {message.thread.map((reply) => (\n                <ThreadReplyCard key={reply.id} reply={reply} />\n              ))}\n            </div>\n          ) : null}\n          <div className=\"flex flex-wrap items-center gap-2\">\n            <Button size=\"sm\" onClick={onReply}>\n              <ReplyIcon className=\"size-3.5\" />\n              Reply\n            </Button>\n            <Button size=\"sm\" variant=\"outline\" onClick={onReplyAll}>\n              <ReplyIcon className=\"size-3.5\" />\n              Reply all\n            </Button>\n            <Button size=\"sm\" variant=\"outline\" onClick={onForward}>\n              <ForwardIcon className=\"size-3.5\" />\n              Forward\n            </Button>\n            <Button\n              size=\"icon-sm\"\n              variant={message.starred ? \"default\" : \"outline\"}\n              aria-label={message.starred ? \"Remove star\" : \"Star\"}\n              aria-pressed={message.starred}\n              onClick={onToggleStar}\n            >\n              <StarIcon className={cn(message.starred && \"fill-current\")} />\n            </Button>\n          </div>\n        </article>\n      </ScrollBlur>\n    </section>\n  )\n}\n\nfunction ThreadReplyCard({ reply }: { reply: MailboxThreadReply }) {\n  return (\n    <section className=\"rounded-lg border bg-muted/20 p-3\">\n      <div className=\"flex items-start gap-3\">\n        <Avatar className=\"size-8 rounded-md after:hidden\">\n          <AvatarImage src={reply.from.avatar} alt=\"\" className=\"rounded-md\" />\n          <AvatarFallback className=\"rounded-md\">\n            {initials(reply.from.name)}\n          </AvatarFallback>\n        </Avatar>\n        <div className=\"min-w-0 flex-1\">\n          <div className=\"flex items-start justify-between gap-3\">\n            <div className=\"min-w-0\">\n              <div className=\"truncate text-sm font-medium\">{reply.from.name}</div>\n              <div className=\"truncate text-xs text-muted-foreground\">\n                to {formatReplyRecipients(reply.to)}\n                {reply.cc?.length ? ` · cc ${formatReplyRecipients(reply.cc)}` : \"\"}\n              </div>\n            </div>\n            <span className=\"shrink-0 text-xs text-muted-foreground\">{reply.timestamp}</span>\n          </div>\n          <p className=\"mt-3 text-sm leading-6 text-muted-foreground\">{reply.body}</p>\n        </div>\n      </div>\n    </section>\n  )\n}\n\nfunction ComposePane({\n  contacts,\n  draft,\n  onCancel,\n  onSend,\n}: {\n  contacts: MailboxContact[]\n  draft?: MailboxComposeDraft\n  onCancel: () => void\n  onSend: (message: MailboxMessage) => void\n}) {\n  const [recipients, setRecipients] = React.useState<string[]>(draft?.to ?? [])\n  const [cc, setCc] = React.useState<string[]>(draft?.cc ?? [])\n  const [bcc, setBcc] = React.useState<string[]>(draft?.bcc ?? [])\n  const [subject, setSubject] = React.useState(draft?.subject ?? \"\")\n  const [body, setBody] = React.useState(draft?.body ?? \"\")\n  const [attachments, setAttachments] = React.useState<FileUploadDropzoneFile[]>([])\n  const [attachmentDialogOpen, setAttachmentDialogOpen] = React.useState(false)\n  const editorRef = React.useRef<HTMLDivElement | null>(null)\n\n  React.useEffect(() => {\n    const nextBody = draft?.body ?? \"\"\n\n    setRecipients(draft?.to ?? [])\n    setCc(draft?.cc ?? [])\n    setBcc(draft?.bcc ?? [])\n    setSubject(draft?.subject ?? \"\")\n    setBody(nextBody)\n    setAttachments([])\n    setAttachmentDialogOpen(false)\n\n    window.requestAnimationFrame(() => {\n      if (editorRef.current) {\n        editorRef.current.innerHTML = formatComposerBody(nextBody)\n      }\n    })\n  }, [draft])\n\n  React.useEffect(() => {\n    if (!attachmentDialogOpen) return\n    if (!attachments.length) return\n    if (attachments.some((item) => item.status === \"uploading\")) return\n    if (!attachments.every((item) => item.status === \"success\")) return\n\n    const timer = window.setTimeout(() => {\n      setAttachmentDialogOpen(false)\n      toast.success(\"Attachments ready\", {\n        description: `${attachments.length} file${attachments.length === 1 ? \"\" : \"s\"} attached.`,\n      })\n    }, 250)\n\n    return () => window.clearTimeout(timer)\n  }, [attachmentDialogOpen, attachments])\n\n  function syncBodyFromEditor() {\n    setBody(editorRef.current?.innerText ?? \"\")\n  }\n\n  function getComposerBody() {\n    return editorRef.current?.innerText.trim() ?? body.trim()\n  }\n\n  function applyFormat(command: \"bold\" | \"italic\" | \"underline\") {\n    editorRef.current?.focus()\n    document.execCommand(command)\n    syncBodyFromEditor()\n  }\n\n  return (\n    <section className=\"min-h-0 min-w-0 flex-1 overflow-hidden bg-background\">\n      <div className=\"flex h-full min-h-0 flex-col\">\n        <ScrollBlur className=\"min-h-0 flex-1\" edgeSize={32}>\n          <div className=\"space-y-3 border-b p-4\">\n          <div className=\"flex items-center justify-between gap-3\">\n            <div>\n              <h2 className=\"text-sm font-semibold\">Compose</h2>\n              <p className=\"text-xs text-muted-foreground\">Draft a new email</p>\n            </div>\n            <div className=\"flex items-center gap-2\">\n              <Button variant=\"outline\" size=\"sm\" onClick={onCancel}>\n                Cancel\n              </Button>\n              <Button\n                size=\"sm\"\n                onClick={() => {\n                  const nextBody = getComposerBody()\n\n                  onSend({\n                    id: `sent-${Date.now()}`,\n                    folderId: \"sent\",\n                    from: contacts[0],\n                    to: contacts.filter((contact) => recipients.includes(contact.email)),\n                    cc: contacts.filter((contact) => cc.includes(contact.email)),\n                    bcc: contacts.filter((contact) => bcc.includes(contact.email)),\n                    subject,\n                    preview: nextBody,\n                    body: nextBody,\n                    timestamp: \"Now\",\n                  })\n                }}\n              >\n                <SendIcon className=\"size-3.5\" />\n                Send\n              </Button>\n            </div>\n          </div>\n          <div className=\"grid gap-2\">\n            <RecipientCombobox\n              label=\"To\"\n              contacts={contacts}\n              value={recipients}\n              onValueChange={setRecipients}\n            />\n            <RecipientCombobox\n              label=\"Cc\"\n              contacts={contacts}\n              value={cc}\n              onValueChange={setCc}\n            />\n            <RecipientCombobox\n              label=\"Bcc\"\n              contacts={contacts}\n              value={bcc}\n              onValueChange={setBcc}\n            />\n          </div>\n          <Input value={subject} onChange={(event) => setSubject(event.target.value)} placeholder=\"Subject\" />\n          <div className=\"flex items-center justify-between gap-2\">\n            <ToggleGroup type=\"multiple\" size=\"sm\" variant=\"outline\">\n              <ToggleGroupItem\n                value=\"bold\"\n                aria-label=\"Bold\"\n                onMouseDown={(event) => event.preventDefault()}\n                onClick={() => applyFormat(\"bold\")}\n              >\n                <BoldIcon className=\"size-3.5\" />\n              </ToggleGroupItem>\n              <ToggleGroupItem\n                value=\"italic\"\n                aria-label=\"Italic\"\n                onMouseDown={(event) => event.preventDefault()}\n                onClick={() => applyFormat(\"italic\")}\n              >\n                <ItalicIcon className=\"size-3.5\" />\n              </ToggleGroupItem>\n              <ToggleGroupItem\n                value=\"underline\"\n                aria-label=\"Underline\"\n                onMouseDown={(event) => event.preventDefault()}\n                onClick={() => applyFormat(\"underline\")}\n              >\n                <UnderlineIcon className=\"size-3.5\" />\n              </ToggleGroupItem>\n            </ToggleGroup>\n            <Dialog open={attachmentDialogOpen} onOpenChange={setAttachmentDialogOpen}>\n              <DialogTrigger asChild>\n                <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Attach files\">\n                  <PaperclipIcon />\n                  {attachments.length ? (\n                    <span className=\"absolute -right-1 -top-1 flex size-4 items-center justify-center rounded-full bg-foreground text-[10px] font-medium text-background\">\n                      {attachments.length}\n                    </span>\n                  ) : null}\n                </Button>\n              </DialogTrigger>\n              <DialogContent className=\"sm:max-w-lg\">\n                <DialogHeader>\n                  <DialogTitle>Attach files</DialogTitle>\n                  <DialogDescription>\n                    Add supporting files before sending this email.\n                  </DialogDescription>\n                </DialogHeader>\n                <FileUploadDropzone\n                  files={attachments}\n                  onFilesChange={setAttachments}\n                  maxFiles={4}\n                  label=\"Drop email attachments here\"\n                  description=\"or click to browse files\"\n                  simulateUpload\n                />\n                <DialogFooter showCloseButton>\n                  <Button\n                    type=\"button\"\n                    onClick={() =>\n                      toast.success(\"Attachments ready\", {\n                        description: `${attachments.length} file${attachments.length === 1 ? \"\" : \"s\"} attached.`,\n                      })\n                    }\n                    disabled={!attachments.length}\n                  >\n                    Done\n                  </Button>\n                </DialogFooter>\n              </DialogContent>\n            </Dialog>\n          </div>\n          </div>\n          {attachments.length ? (\n            <div className=\"flex flex-wrap gap-2 border-b px-4 py-2\">\n              {attachments.map((item) => (\n                <Badge key={item.id} variant=\"secondary\" className=\"rounded-md pr-1\">\n                  <PaperclipIcon className=\"size-3\" />\n                  {item.file.name}\n                  <button\n                    type=\"button\"\n                    onClick={() =>\n                      setAttachments((current) =>\n                        current.filter((attachment) => attachment.id !== item.id)\n                      )\n                    }\n                    className=\"ml-1 inline-flex size-4 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30\"\n                    aria-label={`Remove ${item.file.name}`}\n                  >\n                    <XIcon className=\"size-3\" />\n                  </button>\n                </Badge>\n              ))}\n            </div>\n          ) : null}\n          <div\n            ref={editorRef}\n            contentEditable\n            suppressContentEditableWarning\n            role=\"textbox\"\n            aria-multiline=\"true\"\n            data-placeholder=\"Write a message...\"\n            onInput={syncBodyFromEditor}\n            className=\"min-h-80 bg-background p-4 text-sm leading-7 outline-none empty:before:text-muted-foreground empty:before:content-[attr(data-placeholder)] [&_hr]:my-4 [&_hr]:border-border\"\n          />\n        </ScrollBlur>\n      </div>\n    </section>\n  )\n}\n\nfunction RecipientCombobox({\n  contacts,\n  label,\n  value,\n  onValueChange,\n}: {\n  contacts: MailboxContact[]\n  label: string\n  value: string[]\n  onValueChange: (value: string[]) => void\n}) {\n  const anchor = useComboboxAnchor()\n  const [open, setOpen] = React.useState(false)\n  const [portalContainer, setPortalContainer] = React.useState<HTMLElement | null>(null)\n  const items = contacts.map((contact) => contact.email)\n\n  React.useEffect(() => {\n    setPortalContainer(document.body)\n  }, [])\n\n  function openCombobox() {\n    setOpen(true)\n  }\n\n  return (\n    <div className=\"grid grid-cols-[2.25rem_minmax(0,1fr)] items-start gap-2\">\n      <div className=\"pt-1.5 text-xs font-medium text-muted-foreground\">{label}</div>\n      <Combobox\n        multiple\n        open={open}\n        onOpenChange={setOpen}\n        autoHighlight\n        openOnInputClick\n        items={items}\n        value={value}\n        onValueChange={(nextValue) => {\n          onValueChange(nextValue as string[])\n          window.requestAnimationFrame(() => setOpen(true))\n        }}\n      >\n        <ComboboxChips\n          ref={anchor}\n          className=\"relative min-h-8 w-full pr-8\"\n          onFocusCapture={openCombobox}\n          onPointerDownCapture={openCombobox}\n        >\n          <ComboboxValue>\n            {(values) => (\n              <>\n                {(values as string[]).map((item) => (\n                  <ComboboxChip key={item}>{item}</ComboboxChip>\n                ))}\n                <ComboboxChipsInput\n                  placeholder={`Search ${label.toLowerCase()} contacts`}\n                  className=\"min-w-32\"\n                  onFocus={openCombobox}\n                  onClick={openCombobox}\n                />\n              </>\n            )}\n          </ComboboxValue>\n          <ComboboxTrigger\n            className=\"absolute right-2 top-1.5 inline-flex size-5 items-center justify-center rounded-sm hover:bg-muted\"\n            onClick={openCombobox}\n          />\n        </ComboboxChips>\n        <ComboboxContent\n          anchor={anchor}\n          container={portalContainer ?? undefined}\n          sideOffset={4}\n          className=\"z-[90]\"\n        >\n          <ComboboxEmpty>No contacts found.</ComboboxEmpty>\n          <ComboboxList>\n            {(item: string) => (\n              <ComboboxItem key={item} value={item}>\n                <span className=\"grid min-w-0\">\n                  <span className=\"truncate font-medium\">\n                    {contacts.find((contact) => contact.email === item)?.name ?? item}\n                  </span>\n                  <span className=\"truncate text-muted-foreground\">{item}</span>\n                </span>\n              </ComboboxItem>\n            )}\n          </ComboboxList>\n        </ComboboxContent>\n      </Combobox>\n    </div>\n  )\n}\n\nfunction initials(name: string) {\n  return name\n    .split(\" \")\n    .map((part) => part[0])\n    .join(\"\")\n    .slice(0, 2)\n    .toUpperCase()\n}\n\nfunction withSubjectPrefix(subject: string, prefix: \"Re:\" | \"Fwd:\") {\n  return subject.toLowerCase().startsWith(prefix.toLowerCase())\n    ? subject\n    : `${prefix} ${subject}`\n}\n\nfunction uniqueEmails(emails: string[]) {\n  return Array.from(new Set(emails.filter(Boolean)))\n}\n\nfunction formatReplyRecipients(recipients: MailboxContact[] = []) {\n  if (!recipients.length) return \"me\"\n\n  return recipients.map((contact) => contact.name).join(\", \")\n}\n\nfunction buildThreadQuote(message: MailboxMessage, mode: \"reply\" | \"forward\") {\n  const replies = message.thread ?? []\n  const quotedMessages = [\n    ...replies.map((reply) => ({\n      from: reply.from,\n      to: reply.to,\n      cc: reply.cc,\n      timestamp: reply.timestamp,\n      subject: message.subject,\n      body: reply.body,\n    })),\n    {\n      from: message.from,\n      to: message.to,\n      cc: message.cc,\n      timestamp: message.timestamp,\n      subject: message.subject,\n      body: message.body,\n    },\n  ]\n\n  return quotedMessages\n    .map((item, index) => {\n      const header =\n        mode === \"forward\"\n          ? [\n              \"Forwarded message:\",\n              `From: ${item.from.name} <${item.from.email}>`,\n              item.to?.length ? `To: ${item.to.map((contact) => contact.email).join(\", \")}` : null,\n              item.cc?.length ? `Cc: ${item.cc.map((contact) => contact.email).join(\", \")}` : null,\n              `Subject: ${item.subject}`,\n              `Date: ${item.timestamp}`,\n            ]\n              .filter(Boolean)\n              .join(\"\\n\")\n          : `${formatQuoteTimestamp(item.timestamp)}, ${item.from.name} wrote:`\n\n      return `${index === 0 ? \"\\n\\n\" : \"\\n\"}---\\n${header}\\n${item.body}`\n    })\n    .join(\"\\n\")\n}\n\nfunction formatQuoteTimestamp(timestamp: string) {\n  if (/^\\d{1,2}:\\d{2}$/.test(timestamp)) {\n    return `Today @ ${timestamp}`\n  }\n\n  if (timestamp.toLowerCase() === \"yesterday\") {\n    return \"Yesterday\"\n  }\n\n  if (timestamp.toLowerCase() === \"now\") {\n    return \"Just now\"\n  }\n\n  if (timestamp.toLowerCase() === \"draft\") {\n    return \"Draft\"\n  }\n\n  return `On ${timestamp}`\n}\n\nfunction formatComposerBody(body: string) {\n  return body\n    .split(\"\\n\")\n    .map((line) => {\n      if (line.trim() === \"---\") {\n        return '<hr aria-hidden=\"true\" />'\n      }\n\n      if (!line) {\n        return \"<div><br /></div>\"\n      }\n\n      return `<div>${escapeHtml(line)}</div>`\n    })\n    .join(\"\")\n}\n\nfunction escapeHtml(value: string) {\n  return value\n    .replaceAll(\"&\", \"&amp;\")\n    .replaceAll(\"<\", \"&lt;\")\n    .replaceAll(\">\", \"&gt;\")\n    .replaceAll('\"', \"&quot;\")\n    .replaceAll(\"'\", \"&#039;\")\n}\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}