{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "inbox",
  "title": "Inbox",
  "type": "registry:block",
  "description": "Three-pane mail surface. Left rail: Compose button, folders (Inbox/Starred/Sent/Drafts/Spam/Trash) with unread counts, colour-coded labels with totals. Middle: searchable message list with sender, subject, preview, labels, attachment glyphs, star toggle, smart timestamps. Right: reader pane with subject, from/to, body, attachment chip, reply/forward/archive actions. Stateful demo — swap `mails` for your data source.",
  "categories": [
    "communication",
    "layout"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/inbox/inbox.ts",
      "content": "import { LitElement, css, html } from 'lit'\nimport {\n  AlertOctagon,\n  Archive,\n  ArrowLeft,\n  ArrowRight,\n  AtSign,\n  Bookmark,\n  FileText,\n  Forward,\n  Inbox as InboxIcon,\n  MoreVertical,\n  Paperclip,\n  Pencil,\n  Reply,\n  ReplyAll,\n  Search,\n  Send,\n  Smile,\n  Star,\n  Tag,\n  Trash2,\n} from 'lucide'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/avatar/avatar'\nimport '../../components/button/button'\nimport '../../components/input/input'\nimport type { UipInput } from '../../components/input/input'\n\ntype InboxReplyMode = 'reply' | 'reply-all' | 'forward'\ntype InboxFolderId = 'inbox' | 'starred' | 'sent' | 'drafts' | 'spam' | 'trash'\n\ninterface InboxFolder {\n  id: InboxFolderId\n  label: string\n}\n\ninterface InboxLabel {\n  id: string\n  label: string\n  color: string\n}\n\ninterface InboxMail {\n  id: string\n  folder: InboxFolderId\n  from: { name: string; email: string; initials: string }\n  to: string\n  subject: string\n  preview: string\n  body: string\n  receivedAt: Date\n  read: boolean\n  starred: boolean\n  hasAttachment: boolean\n  labels: string[]\n}\n\nconst FOLDERS: InboxFolder[] = [\n  { id: 'inbox', label: 'Inbox' },\n  { id: 'starred', label: 'Starred' },\n  { id: 'sent', label: 'Sent' },\n  { id: 'drafts', label: 'Drafts' },\n  { id: 'spam', label: 'Spam' },\n  { id: 'trash', label: 'Trash' },\n]\n\nconst LABELS: InboxLabel[] = [\n  { id: 'work', label: 'Work', color: 'bg-primary' },\n  { id: 'personal', label: 'Personal', color: 'bg-success' },\n  { id: 'finance', label: 'Finance', color: 'bg-info' },\n  { id: 'travel', label: 'Travel', color: 'bg-warning' },\n]\n\nfunction seedMails(): InboxMail[] {\n  const now = new Date()\n  const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)\n  return [\n    {\n      id: 'e1',\n      folder: 'inbox',\n      from: { name: 'Sarah Connor', email: 'sarah@acme.com', initials: 'SC' },\n      to: 'you@acme.com',\n      subject: 'Q2 OKR draft — your sign-off needed',\n      preview: 'Pulled together the Q2 OKR draft from the leadership offsite. I have flagged the two areas where we need…',\n      body: 'Hi,\\n\\nPulled together the Q2 OKR draft from the leadership offsite. I have flagged the two areas where we need your call before we lock it:\\n\\n• Hiring plan: 4 vs 6 ENG roles (page 3)\\n• EU rollout timing: April vs June (page 7)\\n\\nGoing to share with the rest of the leadership team Wednesday EOD, so anything from you by Tuesday is great.\\n\\nThanks,\\nSarah',\n      receivedAt: new Date(now.getTime() - 18 * 60 * 1000),\n      read: false,\n      starred: true,\n      hasAttachment: true,\n      labels: ['work'],\n    },\n    {\n      id: 'e2',\n      folder: 'inbox',\n      from: { name: 'Marcus Rivera', email: 'marcus@acme.com', initials: 'MR' },\n      to: 'you@acme.com',\n      subject: 'Re: Dashboard review tomorrow',\n      preview: 'Yes! Pushing to 10:30 so we can join from the standup. I will own the slides and pull the latest kanban metrics.',\n      body: 'Yes! Pushing to 10:30 so we can join from the standup. I will own the slides and pull the latest kanban metrics.\\n\\nFew things I want to cover:\\n  - Throughput delta vs last sprint\\n  - The two stories we de-scoped\\n  - Q2 capacity ask\\n\\nSee you tomorrow,\\nMarcus',\n      receivedAt: new Date(now.getTime() - 95 * 60 * 1000),\n      read: false,\n      starred: false,\n      hasAttachment: false,\n      labels: ['work'],\n    },\n    {\n      id: 'e3',\n      folder: 'inbox',\n      from: { name: 'Stripe', email: 'receipts@stripe.com', initials: 'St' },\n      to: 'you@acme.com',\n      subject: 'Your invoice from Acme Inc. — $1,290.00',\n      preview: 'Thanks for using Stripe. Your invoice for the period Mar 1 – Mar 31 is attached. Total due: $1,290.00…',\n      body: 'Thanks for using Stripe. Your invoice for the period Mar 1 – Mar 31 is attached. Total due: $1,290.00.',\n      receivedAt: new Date(now.getTime() - 4 * 60 * 60 * 1000),\n      read: true,\n      starred: false,\n      hasAttachment: true,\n      labels: ['finance'],\n    },\n    {\n      id: 'e4',\n      folder: 'inbox',\n      from: { name: 'Linear', email: 'no-reply@linear.app', initials: 'Li' },\n      to: 'you@acme.com',\n      subject: 'Weekly summary: 14 issues closed, 22 opened',\n      preview: 'Good news — the team closed 14 issues this week, up 3 from last week. Top contributors: Marcus, Priya, Devon.',\n      body: 'Good news — the team closed 14 issues this week, up 3 from last week. Top contributors: Marcus, Priya, Devon.',\n      receivedAt: new Date(now.getTime() - 9 * 60 * 60 * 1000),\n      read: true,\n      starred: false,\n      hasAttachment: false,\n      labels: ['work'],\n    },\n    {\n      id: 'e5',\n      folder: 'inbox',\n      from: { name: 'Priya Shah', email: 'priya@acme.com', initials: 'PS' },\n      to: 'you@acme.com',\n      subject: 'Color tokens are live in staging',\n      preview: 'Pushed the new OKLCH token set to staging. All chart colors, success/warning/info, sidebar variants. Take a look…',\n      body: 'Pushed the new OKLCH token set to staging. All chart colors, success/warning/info, sidebar variants. Take a look when you have a sec.',\n      receivedAt: yesterday,\n      read: true,\n      starred: true,\n      hasAttachment: false,\n      labels: ['work'],\n    },\n    {\n      id: 'e6',\n      folder: 'inbox',\n      from: { name: 'United Airlines', email: 'reservations@united.com', initials: 'UA' },\n      to: 'you@acme.com',\n      subject: 'Your itinerary: SFO → LHR, departing Apr 12',\n      preview: 'Your reservation is confirmed. Check-in opens 24h before departure. Booking reference: 7HQ4P2.',\n      body: 'Your reservation is confirmed. Check-in opens 24h before departure. Booking reference: 7HQ4P2.',\n      receivedAt: new Date(now.getTime() - 38 * 60 * 60 * 1000),\n      read: true,\n      starred: false,\n      hasAttachment: true,\n      labels: ['travel'],\n    },\n    {\n      id: 'e7',\n      folder: 'inbox',\n      from: { name: 'Mom', email: 'mom@family.net', initials: 'M' },\n      to: 'you@acme.com',\n      subject: 'Sunday lunch?',\n      preview: 'Are you free for lunch this Sunday? Dad is making his lasagne. Let me know!',\n      body: 'Are you free for lunch this Sunday? Dad is making his lasagne. Let me know!',\n      receivedAt: new Date(now.getTime() - 55 * 60 * 60 * 1000),\n      read: true,\n      starred: false,\n      hasAttachment: false,\n      labels: ['personal'],\n    },\n  ]\n}\n\nfunction formatMailTime(d: Date): string {\n  const now = new Date()\n  const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n  if (d >= todayStart) return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })\n  const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)\n  if (d.toDateString() === yesterday.toDateString()) return 'Yesterday'\n  const diffDays = Math.floor((now.getTime() - d.getTime()) / (24 * 60 * 60 * 1000))\n  if (diffDays < 7) return d.toLocaleDateString(undefined, { weekday: 'short' })\n  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })\n}\n\nfunction formatFullTime(d: Date): string {\n  return d.toLocaleString(undefined, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })\n}\n\nfunction labelMeta(id: string): InboxLabel | undefined {\n  return LABELS.find((l) => l.id === id)\n}\n\n/**\n * <uip-inbox> — the registry Inbox block as a web component.\n *\n * Class strings are React's Inbox verbatim. React state becomes @state\n * fields. Folder icons are static per-row markup (React mapped icon\n * components; here each row spells out its lucide icon). The reply box uses\n * a native <textarea> (as in React) with Cmd/Ctrl+Enter to send. The list\n * rows are <button>s; the star is a nested span-button — React's structure\n * is kept, with stopPropagation on toggle.\n */\nexport class UipInbox extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    mails: { state: true },\n    activeFolder: { state: true },\n    activeLabel: { state: true },\n    activeId: { state: true },\n    search: { state: true },\n    replyMode: { state: true },\n    replyDraft: { state: true },\n    justSent: { state: true },\n  }\n\n  private mails: InboxMail[] = []\n  private activeFolder: InboxFolderId = 'inbox'\n  private activeLabel: string | null = null\n  private activeId = 'e1'\n  private search = ''\n  private replyMode: InboxReplyMode = 'reply'\n  private replyDraft = ''\n  private justSent = false\n  private sentTimer?: number\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'inbox')\n    if (this.mails.length === 0) this.mails = seedMails()\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    if (this.sentTimer) window.clearTimeout(this.sentTimer)\n  }\n\n  private unreadCount(id: InboxFolderId): number {\n    if (id === 'starred') return this.mails.filter((m) => m.starred && !m.read && m.folder !== 'trash').length\n    return this.mails.filter((m) => m.folder === id && !m.read).length\n  }\n\n  private get labelCounts(): Record<string, number> {\n    const acc: Record<string, number> = {}\n    for (const l of LABELS) acc[l.id] = this.mails.filter((m) => m.labels.includes(l.id)).length\n    return acc\n  }\n\n  private get visibleMails(): InboxMail[] {\n    const q = this.search.trim().toLowerCase()\n    let list = this.mails.filter((m) => {\n      if (this.activeLabel) return m.labels.includes(this.activeLabel)\n      if (this.activeFolder === 'starred') return m.starred && m.folder !== 'trash'\n      return m.folder === this.activeFolder\n    })\n    if (q) {\n      list = list.filter(\n        (m) =>\n          m.subject.toLowerCase().includes(q) ||\n          m.from.name.toLowerCase().includes(q) ||\n          m.preview.toLowerCase().includes(q),\n      )\n    }\n    return [...list].sort((a, b) => b.receivedAt.getTime() - a.receivedAt.getTime())\n  }\n\n  private get activeMail(): InboxMail | null {\n    return this.mails.find((m) => m.id === this.activeId) ?? null\n  }\n\n  private selectMail(id: string) {\n    this.activeId = id\n    this.mails = this.mails.map((m) => (m.id === id ? { ...m, read: true } : m))\n  }\n\n  private selectFolder(id: InboxFolderId) {\n    this.activeFolder = id\n    this.activeLabel = null\n    const list = [...this.mails]\n      .filter((m) => (id === 'starred' ? m.starred && m.folder !== 'trash' : m.folder === id))\n      .sort((a, b) => b.receivedAt.getTime() - a.receivedAt.getTime())\n    if (list[0]) this.activeId = list[0].id\n  }\n\n  private selectLabel(id: string) {\n    this.activeLabel = id\n    const list = [...this.mails]\n      .filter((m) => m.labels.includes(id))\n      .sort((a, b) => b.receivedAt.getTime() - a.receivedAt.getTime())\n    if (list[0]) this.activeId = list[0].id\n  }\n\n  private toggleStar(id: string, e?: Event) {\n    e?.stopPropagation()\n    this.mails = this.mails.map((m) => (m.id === id ? { ...m, starred: !m.starred } : m))\n  }\n\n  private archive(id: string) {\n    const next = this.mails.filter((m) => m.id !== id)\n    const visible = [...next]\n      .filter((m) => {\n        if (this.activeLabel) return m.labels.includes(this.activeLabel)\n        if (this.activeFolder === 'starred') return m.starred && m.folder !== 'trash'\n        return m.folder === this.activeFolder\n      })\n      .sort((a, b) => b.receivedAt.getTime() - a.receivedAt.getTime())\n    if (visible[0]) this.activeId = visible[0].id\n    this.mails = next\n  }\n\n  private folderIcon(id: InboxFolderId, cls: string) {\n    switch (id) {\n      case 'inbox':\n        return icon(InboxIcon, 'inbox', cls)\n      case 'starred':\n        return icon(Star, 'star', cls)\n      case 'sent':\n        return icon(Send, 'send', cls)\n      case 'drafts':\n        return icon(FileText, 'file-text', cls)\n      case 'spam':\n        return icon(AlertOctagon, 'octagon-alert', cls)\n      default:\n        return icon(Trash2, 'trash-2', cls)\n    }\n  }\n\n  private sendReply() {\n    const body = this.replyDraft.trim()\n    if (!body) return\n    this.replyDraft = ''\n    this.justSent = true\n    if (this.sentTimer) window.clearTimeout(this.sentTimer)\n    this.sentTimer = window.setTimeout(() => {\n      this.justSent = false\n    }, 1800)\n  }\n\n  render() {\n    const activeMail = this.activeMail\n    const visible = this.visibleMails\n    const counts = this.labelCounts\n    const replyRecipient = activeMail && this.replyMode !== 'forward' ? `${activeMail.from.name} <${activeMail.from.email}>` : ''\n    const replyPlaceholder = this.replyMode === 'forward' ? 'Add a note before forwarding' : 'Write a reply'\n    return html`\n      <div\n        data-slot=\"inbox\"\n        class=${cn('bg-card text-card-foreground grid h-[680px] w-full grid-cols-[220px_340px_1fr] overflow-hidden rounded-xl border shadow-sm')}\n      >\n        <aside class=\"bg-muted/30 flex min-w-0 flex-col border-r\">\n          <div class=\"flex h-14 shrink-0 items-center border-b px-3\">\n            <uip-button class=\"h-9 w-full justify-start gap-2 rounded-lg\" size=\"sm\">\n              ${icon(Pencil, 'pencil', 'size-4')} Compose\n            </uip-button>\n          </div>\n          <nav class=\"flex-1 space-y-1 overflow-y-auto px-3 pt-3 pb-3\">\n            <p class=\"text-muted-foreground px-2 pb-1 text-xs font-semibold tracking-widest uppercase\">Folders</p>\n            ${FOLDERS.map(\n              (f) => html`\n                <button\n                  class=${cn(\n                    'flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors',\n                    this.activeFolder === f.id && !this.activeLabel\n                      ? 'bg-primary/10 text-primary font-medium'\n                      : 'hover:bg-muted/60 text-foreground/80',\n                  )}\n                  @click=${() => this.selectFolder(f.id)}\n                >\n                  <span class=\"flex min-w-0 items-center gap-2\">\n                    ${this.folderIcon(f.id, 'size-4 shrink-0')}\n                    <span class=\"truncate\">${f.label}</span>\n                  </span>\n                  ${this.unreadCount(f.id) > 0\n                    ? html`<span class=\"text-muted-foreground shrink-0 text-xs tabular-nums\"\n                        >${this.unreadCount(f.id)}</span\n                      >`\n                    : ''}\n                </button>\n              `,\n            )}\n\n            <p class=\"text-muted-foreground mt-4 px-2 pb-1 text-xs font-semibold tracking-widest uppercase\">Labels</p>\n            ${LABELS.map(\n              (l) => html`\n                <button\n                  class=${cn(\n                    'flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors',\n                    this.activeLabel === l.id\n                      ? 'bg-primary/10 text-primary font-medium'\n                      : 'hover:bg-muted/60 text-foreground/80',\n                  )}\n                  @click=${() => this.selectLabel(l.id)}\n                >\n                  <span class=\"flex min-w-0 items-center gap-2\">\n                    <span class=${cn('size-2.5 shrink-0 rounded-full', l.color)}></span>\n                    <span class=\"truncate\">${l.label}</span>\n                  </span>\n                  <span class=\"text-muted-foreground shrink-0 text-xs tabular-nums\">${counts[l.id] ?? 0}</span>\n                </button>\n              `,\n            )}\n          </nav>\n        </aside>\n\n        <section class=\"bg-background flex min-w-0 flex-col border-r\">\n          <div class=\"flex h-14 shrink-0 items-center gap-2 border-b px-3\">\n            <div class=\"relative flex-1\">\n              <uip-input\n                .value=${this.search}\n                placeholder=\"Search mail\"\n                class=\"h-8 rounded-lg text-xs\"\n                @input=${(e: Event) => {\n                  this.search = (e.target as UipInput).value\n                }}\n              >\n                <span slot=\"prefix\">${icon(Search, 'search', 'size-3.5')}</span>\n              </uip-input>\n            </div>\n            <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Filter\">\n              ${icon(Tag, 'tag', 'size-4')}\n            </uip-button>\n          </div>\n          <div class=\"flex items-center justify-between px-4 pt-3 pb-2\">\n            <h2 class=\"text-sm font-semibold tracking-tight capitalize\">\n              ${this.activeLabel ? labelMeta(this.activeLabel)?.label : this.activeFolder}\n            </h2>\n            <span class=\"text-muted-foreground text-xs tabular-nums\">\n              ${visible.length} message${visible.length === 1 ? '' : 's'}\n            </span>\n          </div>\n          <div class=\"list-scroll flex-1 overflow-y-auto pb-3\">\n            ${visible.map(\n              (m) => html`\n                <button\n                  aria-label=${`Open message from ${m.from.name}`}\n                  class=${cn(\n                    'group relative w-full px-4 py-3 text-left transition-colors',\n                    m.id === this.activeId ? 'bg-primary/5' : 'hover:bg-muted/60',\n                  )}\n                  @click=${() => this.selectMail(m.id)}\n                >\n                  ${m.id === this.activeId\n                    ? html`<span class=\"bg-primary absolute top-2 bottom-2 left-0 w-[3px] rounded-r-full\"></span>`\n                    : ''}\n                  <div class=\"flex items-start gap-3\">\n                    <uip-avatar class=\"mt-0.5 size-8 shrink-0\">\n                      <uip-avatar-fallback class=\"text-xs\">${m.from.initials}</uip-avatar-fallback>\n                    </uip-avatar>\n                    <div class=\"min-w-0 flex-1\">\n                      <div class=\"flex items-center justify-between gap-2\">\n                        <p class=${cn('truncate text-sm', m.read ? 'font-medium' : 'font-semibold')}>${m.from.name}</p>\n                        <span class=\"text-muted-foreground shrink-0 text-xs tabular-nums\"\n                          >${formatMailTime(m.receivedAt)}</span\n                        >\n                      </div>\n                      <p class=${cn('mt-0.5 truncate text-xs', m.read ? 'text-foreground/80' : 'font-semibold')}>\n                        ${m.subject}\n                      </p>\n                      <p class=\"text-muted-foreground mt-0.5 line-clamp-2 text-xs leading-relaxed\">${m.preview}</p>\n                      <div class=\"mt-1.5 flex items-center gap-1.5\">\n                        ${m.labels.map(\n                          (lid) => html`\n                            <span\n                              class=\"text-muted-foreground bg-muted inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs\"\n                            >\n                              <span class=${cn('size-1.5 rounded-full', labelMeta(lid)?.color ?? '')}></span>\n                              ${labelMeta(lid)?.label}\n                            </span>\n                          `,\n                        )}\n                        ${m.hasAttachment ? icon(Paperclip, 'paperclip', 'text-muted-foreground size-3') : ''}\n                        ${!m.read ? html`<span class=\"bg-primary ml-auto size-1.5 shrink-0 rounded-full\"></span>` : ''}\n                      </div>\n                    </div>\n                    <span\n                      role=\"button\"\n                      tabindex=\"0\"\n                      class=\"text-muted-foreground hover:text-warning inline-flex shrink-0 cursor-pointer items-center justify-center transition-colors\"\n                      title=${m.starred ? 'Unstar' : 'Star'}\n                      @click=${(e: Event) => this.toggleStar(m.id, e)}\n                      @keydown=${(e: KeyboardEvent) => {\n                        if (e.key === 'Enter' || e.key === ' ') {\n                          e.preventDefault()\n                          this.toggleStar(m.id, e)\n                        }\n                      }}\n                    >\n                      ${icon(Star, 'star', cn('size-3.5', m.starred ? 'fill-warning text-warning' : ''))}\n                    </span>\n                  </div>\n                </button>\n              `,\n            )}\n            ${visible.length === 0\n              ? html`\n                  <div class=\"flex flex-col items-center justify-center px-6 py-16 text-center\">\n                    <div class=\"bg-muted mb-3 rounded-full p-3\">${icon(InboxIcon, 'inbox', 'text-muted-foreground size-5')}</div>\n                    <p class=\"text-sm font-medium\">Nothing here</p>\n                    <p class=\"text-muted-foreground mt-0.5 text-xs\">No messages match the current filter.</p>\n                  </div>\n                `\n              : ''}\n          </div>\n        </section>\n\n        <section class=\"bg-background flex min-w-0 flex-col\">\n          ${activeMail\n            ? html`\n                <div class=\"flex h-14 shrink-0 items-center justify-between gap-2 border-b px-4\">\n                  <div class=\"flex items-center gap-1\">\n                    <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Back\">\n                      ${icon(ArrowLeft, 'arrow-left', 'size-4')}\n                    </uip-button>\n                    <uip-button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      class=\"size-8\"\n                      aria-label=\"Archive\"\n                      @click=${() => this.archive(activeMail.id)}\n                    >\n                      ${icon(Archive, 'archive', 'size-4')}\n                    </uip-button>\n                    <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Delete\">\n                      ${icon(Trash2, 'trash-2', 'size-4')}\n                    </uip-button>\n                    <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Save\">\n                      ${icon(Bookmark, 'bookmark', 'size-4')}\n                    </uip-button>\n                  </div>\n                  <div class=\"flex items-center gap-1\">\n                    <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Reply\">\n                      ${icon(Reply, 'reply', 'size-4')}\n                    </uip-button>\n                    <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Reply all\">\n                      ${icon(ReplyAll, 'reply-all', 'size-4')}\n                    </uip-button>\n                    <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Forward\">\n                      ${icon(Forward, 'forward', 'size-4')}\n                    </uip-button>\n                    <uip-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"More options\">\n                      ${icon(MoreVertical, 'ellipsis-vertical', 'size-4')}\n                    </uip-button>\n                  </div>\n                </div>\n\n                <div class=\"reader-scroll flex-1 overflow-y-auto\">\n                  <div class=\"px-6 pt-5 pb-3\">\n                    <h1 class=\"text-foreground text-xl leading-tight font-semibold tracking-tight\">\n                      ${activeMail.subject}\n                    </h1>\n                    <div class=\"mt-2 flex flex-wrap items-center gap-2\">\n                      ${activeMail.labels.map(\n                        (lid) => html`\n                          <span\n                            class=\"text-foreground/70 bg-muted inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs\"\n                          >\n                            <span class=${cn('size-1.5 rounded-full', labelMeta(lid)?.color ?? '')}></span>\n                            ${labelMeta(lid)?.label}\n                          </span>\n                        `,\n                      )}\n                    </div>\n                  </div>\n\n                  <div class=\"flex items-start gap-3 px-6 pb-4\">\n                    <uip-avatar class=\"size-10\">\n                      <uip-avatar-fallback>${activeMail.from.initials}</uip-avatar-fallback>\n                    </uip-avatar>\n                    <div class=\"min-w-0 flex-1\">\n                      <div class=\"flex flex-wrap items-baseline gap-x-2\">\n                        <p class=\"text-sm font-semibold\">${activeMail.from.name}</p>\n                        <p class=\"text-muted-foreground text-xs\">&lt;${activeMail.from.email}&gt;</p>\n                      </div>\n                      <p class=\"text-muted-foreground text-xs\">to ${activeMail.to}</p>\n                    </div>\n                    <div class=\"text-muted-foreground flex shrink-0 items-center gap-2 text-xs\">\n                      <span>${formatFullTime(activeMail.receivedAt)}</span>\n                      <button\n                        class=\"hover:text-warning transition-colors\"\n                        title=${activeMail.starred ? 'Unstar' : 'Star'}\n                        @click=${() => this.toggleStar(activeMail.id)}\n                      >\n                        ${icon(Star, 'star', cn('size-4', activeMail.starred ? 'fill-warning text-warning' : ''))}\n                      </button>\n                    </div>\n                  </div>\n\n                  <article class=\"text-foreground/90 px-6 pb-6 text-sm leading-relaxed whitespace-pre-wrap\">\n                    ${activeMail.body}\n                  </article>\n\n                  ${activeMail.hasAttachment\n                    ? html`\n                        <div class=\"px-6 pb-6\">\n                          <div class=\"bg-muted/40 flex items-center gap-3 rounded-lg border px-3 py-2.5\">\n                            <div class=\"bg-info/10 text-info flex size-8 items-center justify-center rounded-md\">\n                              ${icon(FileText, 'file-text', 'size-4')}\n                            </div>\n                            <div class=\"min-w-0 flex-1\">\n                              <p class=\"truncate text-xs font-medium\">attachment.pdf</p>\n                              <p class=\"text-muted-foreground text-xs\">142 KB · PDF</p>\n                            </div>\n                            <uip-button aria-label=\"Download attachment\" variant=\"ghost\" size=\"sm\" class=\"h-7 text-xs\">\n                              Download\n                            </uip-button>\n                          </div>\n                        </div>\n                      `\n                    : ''}\n                </div>\n\n                <div class=\"bg-muted/20 border-t px-4 pt-3 pb-3\">\n                  <div class=\"mb-2 flex items-center gap-1\">\n                    <button\n                      class=${cn(\n                        'inline-flex h-7 items-center gap-1.5 rounded-md px-2 text-xs font-medium transition-colors',\n                        this.replyMode === 'reply'\n                          ? 'bg-background text-foreground border-input/60 border shadow-sm'\n                          : 'text-muted-foreground hover:bg-background/60 hover:text-foreground',\n                      )}\n                      @click=${() => {\n                        this.replyMode = 'reply'\n                        this.justSent = false\n                      }}\n                    >\n                      ${icon(Reply, 'reply', 'size-3.5')} Reply\n                    </button>\n                    <button\n                      class=${cn(\n                        'inline-flex h-7 items-center gap-1.5 rounded-md px-2 text-xs font-medium transition-colors',\n                        this.replyMode === 'reply-all'\n                          ? 'bg-background text-foreground border-input/60 border shadow-sm'\n                          : 'text-muted-foreground hover:bg-background/60 hover:text-foreground',\n                      )}\n                      @click=${() => {\n                        this.replyMode = 'reply-all'\n                        this.justSent = false\n                      }}\n                    >\n                      ${icon(ReplyAll, 'reply-all', 'size-3.5')} Reply all\n                    </button>\n                    <button\n                      class=${cn(\n                        'inline-flex h-7 items-center gap-1.5 rounded-md px-2 text-xs font-medium transition-colors',\n                        this.replyMode === 'forward'\n                          ? 'bg-background text-foreground border-input/60 border shadow-sm'\n                          : 'text-muted-foreground hover:bg-background/60 hover:text-foreground',\n                      )}\n                      @click=${() => {\n                        this.replyMode = 'forward'\n                        this.justSent = false\n                      }}\n                    >\n                      ${icon(Forward, 'forward', 'size-3.5')} Forward\n                    </button>\n                  </div>\n                  <div\n                    class=\"bg-background border-input/60 focus-within:border-primary/40 focus-within:ring-primary/10 group flex flex-col overflow-hidden rounded-2xl border shadow-sm transition-colors duration-150 focus-within:shadow-md focus-within:ring-4\"\n                  >\n                    ${replyRecipient\n                      ? html`<p class=\"border-input/40 text-muted-foreground border-b px-4 pt-2.5 pb-2 text-xs\">\n                          <span class=\"text-foreground/70 font-medium\">To:</span> ${replyRecipient}\n                        </p>`\n                      : ''}\n                    <textarea\n                      .value=${this.replyDraft}\n                      placeholder=${replyPlaceholder}\n                      rows=\"1\"\n                      class=\"placeholder:text-muted-foreground/70 max-h-40 min-h-[44px] resize-none border-0 bg-transparent px-4 pt-3 pb-1 text-sm leading-relaxed shadow-none outline-none focus-visible:ring-0\"\n                      @input=${(e: Event) => {\n                        this.replyDraft = (e.target as HTMLTextAreaElement).value\n                      }}\n                      @keydown=${(e: KeyboardEvent) => {\n                        if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n                          e.preventDefault()\n                          this.sendReply()\n                        }\n                      }}\n                    ></textarea>\n                    <div class=\"flex items-center justify-between gap-2 px-2 pb-2\">\n                      <div class=\"flex items-center gap-0.5\">\n                        <uip-button\n                          variant=\"ghost\"\n                          size=\"icon\"\n                          class=\"text-muted-foreground hover:text-foreground size-8\"\n                          aria-label=\"Attach file\"\n                        >\n                          ${icon(Paperclip, 'paperclip', 'size-4')}\n                        </uip-button>\n                        <uip-button\n                          variant=\"ghost\"\n                          size=\"icon\"\n                          class=\"text-muted-foreground hover:text-foreground size-8\"\n                          aria-label=\"Emoji\"\n                        >\n                          ${icon(Smile, 'smile', 'size-4')}\n                        </uip-button>\n                        <uip-button\n                          variant=\"ghost\"\n                          size=\"icon\"\n                          class=\"text-muted-foreground hover:text-foreground size-8\"\n                          aria-label=\"Mention\"\n                        >\n                          ${icon(AtSign, 'at-sign', 'size-4')}\n                        </uip-button>\n                      </div>\n                      <div class=\"flex items-center gap-2\">\n                        <kbd\n                          class=\"bg-muted text-muted-foreground hidden h-5 items-center gap-0.5 rounded border px-1.5 font-mono text-xs sm:inline-flex\"\n                        >\n                          <span>⌘</span>\n                          <span>↵</span>\n                        </kbd>\n                        <uip-button\n                          class=\"h-8 gap-1.5 rounded-lg px-3 text-xs font-medium\"\n                          ?disabled=${!this.replyDraft.trim()}\n                          aria-label=\"Send\"\n                          @click=${this.sendReply}\n                        >\n                          Send ${icon(ArrowRight, 'arrow-right', 'size-3.5')}\n                        </uip-button>\n                      </div>\n                    </div>\n                  </div>\n                  ${this.justSent ? html`<p class=\"text-success mt-2 text-center text-xs font-medium\">Reply sent</p>` : ''}\n                </div>\n              `\n            : html`\n                <div class=\"flex flex-1 items-center justify-center\">\n                  <div class=\"text-center\">\n                    <div class=\"bg-muted mx-auto mb-3 size-12 rounded-full p-3\">\n                      ${icon(InboxIcon, 'inbox', 'text-muted-foreground size-6')}\n                    </div>\n                    <p class=\"text-sm font-medium\">No message selected</p>\n                    <p class=\"text-muted-foreground mt-1 text-xs\">Pick a message from the list to read it here.</p>\n                  </div>\n                </div>\n              `}\n        </section>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-inbox') || customElements.define('uip-inbox', UipInbox)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-inbox': UipInbox\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/inbox/inbox.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/avatar.json",
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/input.json",
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
