{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-modal",
  "title": "Settings Modal",
  "description": "Controlled multi-page settings dialog. General, profile, tools, and storage.",
  "dependencies": [
    "@internationalized/date@^3.12.3",
    "lucide-react@^1.33.0"
  ],
  "registryDependencies": [
    "https://components.exe.xyz/r/button.json",
    "https://components.exe.xyz/r/chip.json",
    "https://components.exe.xyz/r/close-button.json",
    "https://components.exe.xyz/r/date-picker.json",
    "https://components.exe.xyz/r/dialog.json",
    "https://components.exe.xyz/r/file-upload.json",
    "https://components.exe.xyz/r/input.json",
    "https://components.exe.xyz/r/label.json",
    "https://components.exe.xyz/r/link-button.json",
    "https://components.exe.xyz/r/pagination.json",
    "https://components.exe.xyz/r/select.json",
    "https://components.exe.xyz/r/sheet.json",
    "https://components.exe.xyz/r/switch.json",
    "https://components.exe.xyz/r/table.json",
    "https://components.exe.xyz/r/theme-toggle.json",
    "https://components.exe.xyz/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/blocks/settings-modal.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarDate } from \"@internationalized/date\"\nimport {\n  AppWindow,\n  Globe,\n  HardDrive,\n  Plug,\n  Terminal,\n  Workflow,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Chip } from \"@/components/ui/chip\"\nimport { CloseButton } from \"@/components/ui/close-button\"\nimport { DatePicker } from \"@/components/ui/date-picker\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogTitle,\n} from \"@/components/ui/dialog\"\nimport { FileUpload } from \"@/components/ui/file-upload\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { LinkButton } from \"@/components/ui/link-button\"\nimport {\n  Pagination,\n  PaginationContent,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious,\n} from \"@/components/ui/pagination\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetTitle,\n} from \"@/components/ui/sheet\"\nimport { Switch } from \"@/components/ui/switch\"\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\"\nimport { ThemeToggle } from \"@/components/ui/theme-toggle\"\n\nexport type SettingsPage = \"general\" | \"profile\" | \"tools\" | \"storage\"\n\nconst PAGES: { id: SettingsPage; label: string }[] = [\n  { id: \"general\", label: \"General\" },\n  { id: \"profile\", label: \"Profile\" },\n  { id: \"tools\", label: \"Tools\" },\n  { id: \"storage\", label: \"Storage\" },\n]\n\nconst PLUGINS: {\n  id: string\n  title: string\n  description: string\n  icon: typeof Globe\n  enabled: boolean\n  comingSoon?: boolean\n}[] = [\n  {\n    id: \"search\",\n    title: \"Web search\",\n    description: \"Look up live sources during a run.\",\n    icon: Globe,\n    enabled: true,\n  },\n  {\n    id: \"shell\",\n    title: \"Code interpreter\",\n    description: \"Run code in a sandbox.\",\n    icon: Terminal,\n    enabled: true,\n  },\n  {\n    id: \"browser\",\n    title: \"Browser\",\n    description: \"Open pages and extract text.\",\n    icon: AppWindow,\n    enabled: false,\n  },\n  {\n    id: \"linear\",\n    title: \"Linear\",\n    description: \"Sync issues into the run.\",\n    icon: Workflow,\n    enabled: false,\n    comingSoon: true,\n  },\n  {\n    id: \"mcp\",\n    title: \"MCP tools\",\n    description: \"Connect Model Context Protocol servers.\",\n    icon: Plug,\n    enabled: true,\n  },\n]\n\nconst FILES = [\n  { name: \"brief.pdf\", kind: \"PDF\", size: \"2.4 MB\", modified: \"12 Aug\" },\n  { name: \"refs.zip\", kind: \"Archive\", size: \"18.1 MB\", modified: \"11 Aug\" },\n  { name: \"plotter-notes.md\", kind: \"Markdown\", size: \"24 KB\", modified: \"10 Aug\" },\n  { name: \"session-42.json\", kind: \"JSON\", size: \"1.1 MB\", modified: \"9 Aug\" },\n  { name: \"lab-panel.csv\", kind: \"CSV\", size: \"860 KB\", modified: \"4 Aug\" },\n  { name: \"voice-memo.wav\", kind: \"Audio\", size: \"6.2 MB\", modified: \"1 Aug\" },\n]\n\nconst PAGE_SIZE = 4\n\nfunction useIsDesktop() {\n  const [desktop, setDesktop] = React.useState(true)\n  React.useEffect(() => {\n    const mq = window.matchMedia(\"(min-width: 768px)\")\n    const sync = () => setDesktop(mq.matches)\n    sync()\n    mq.addEventListener(\"change\", sync)\n    return () => mq.removeEventListener(\"change\", sync)\n  }, [])\n  return desktop\n}\n\nfunction DefaultPlanArt() {\n  return (\n    <svg viewBox=\"0 0 220 120\" className=\"size-full\" aria-hidden>\n      <rect width=\"220\" height=\"120\" fill=\"var(--muted)\" />\n      <rect\n        x=\"10\"\n        y=\"10\"\n        width=\"200\"\n        height=\"100\"\n        fill=\"none\"\n        stroke=\"var(--foreground)\"\n        strokeOpacity=\"0.18\"\n      />\n      <circle cx=\"64\" cy=\"60\" r=\"28\" fill=\"var(--primary)\" />\n      <rect\n        x=\"108\"\n        y=\"32\"\n        width=\"84\"\n        height=\"56\"\n        fill=\"var(--chart-2)\"\n        fillOpacity=\"0.85\"\n      />\n      <path\n        d=\"M18 102h184\"\n        stroke=\"var(--foreground)\"\n        strokeOpacity=\"0.2\"\n        strokeDasharray=\"3 3\"\n      />\n    </svg>\n  )\n}\n\nfunction PageNav({\n  page,\n  onPageChange,\n  orientation = \"vertical\",\n}: {\n  page: SettingsPage\n  onPageChange: (page: SettingsPage) => void\n  orientation?: \"vertical\" | \"horizontal\"\n}) {\n  return (\n    <nav aria-label=\"Settings sections\">\n      <ul\n        className={cn(\n          \"flex gap-0.5\",\n          orientation === \"vertical\" ? \"flex-col p-2\" : \"flex-row px-3 py-2\"\n        )}\n      >\n        {PAGES.map((item) => {\n          const current = item.id === page\n          return (\n            <li key={item.id}>\n              <button\n                type=\"button\"\n                aria-current={current ? \"page\" : undefined}\n                onClick={() => onPageChange(item.id)}\n                className={cn(\n                  \"rounded-[2px] px-2.5 py-1.5 text-sm outline-none transition-colors\",\n                  \"focus-visible:ring-3 focus-visible:ring-ring/50\",\n                  orientation === \"vertical\" ? \"w-full text-left\" : \"shrink-0\",\n                  current\n                    ? \"bg-muted font-medium text-foreground\"\n                    : \"text-muted-foreground hover:bg-muted/60 hover:text-foreground\"\n                )}\n              >\n                {item.label}\n              </button>\n            </li>\n          )\n        })}\n      </ul>\n    </nav>\n  )\n}\n\nfunction Field({\n  id,\n  label,\n  children,\n}: {\n  id: string\n  label: string\n  children: React.ReactNode\n}) {\n  return (\n    <div className=\"flex flex-col gap-1.5\">\n      <Label htmlFor={id}>{label}</Label>\n      {children}\n    </div>\n  )\n}\n\nfunction SwitchRow({\n  id,\n  label,\n  defaultChecked,\n}: {\n  id: string\n  label: string\n  defaultChecked?: boolean\n}) {\n  return (\n    <div className=\"flex items-center justify-between gap-3 py-1.5\">\n      <Label htmlFor={id} className=\"font-normal\">\n        {label}\n      </Label>\n      <Switch id={id} defaultChecked={defaultChecked} />\n    </div>\n  )\n}\n\nfunction GeneralPage({\n  planArtSrc,\n  onPlanUpgrade,\n}: {\n  planArtSrc?: string\n  onPlanUpgrade?: () => void\n}) {\n  return (\n    <div className=\"flex flex-col gap-6\">\n      <section className=\"overflow-hidden rounded-[2px] border border-border\">\n        <div className=\"h-28 bg-muted\">\n          {planArtSrc ? (\n            <img\n              src={planArtSrc}\n              alt=\"\"\n              className=\"size-full object-cover\"\n            />\n          ) : (\n            <DefaultPlanArt />\n          )}\n        </div>\n        <div className=\"flex flex-col gap-2 px-3 py-3\">\n          <div className=\"flex items-start justify-between gap-3\">\n            <div>\n              <h3 className=\"font-heading text-sm font-medium\">Ultra $149/mo</h3>\n              <p className=\"text-sm text-muted-foreground\">\n                You are on 7x more usage than Regular.\n              </p>\n            </div>\n            <Button size=\"sm\" onClick={onPlanUpgrade}>\n              Upgrade\n            </Button>\n          </div>\n          <LinkButton href=\"#usage\" size=\"xs\" variant=\"secondary\">\n            View usage limits\n          </LinkButton>\n        </div>\n      </section>\n\n      <section className=\"flex flex-col gap-3\">\n        <h3 className=\"font-heading text-sm font-medium\">Pull requests</h3>\n        <Field id=\"pr-provider\" label=\"PR provider\">\n          <Select defaultValue=\"github\">\n            <SelectTrigger id=\"pr-provider\" className=\"w-full\">\n              <SelectValue placeholder=\"Provider\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem value=\"github\">GitHub</SelectItem>\n              <SelectItem value=\"gitlab\">GitLab</SelectItem>\n              <SelectItem value=\"bitbucket\">Bitbucket</SelectItem>\n            </SelectContent>\n          </Select>\n        </Field>\n        <Field id=\"pr-destination\" label=\"PR destination\">\n          <Select defaultValue=\"pr\">\n            <SelectTrigger id=\"pr-destination\" className=\"w-full\">\n              <SelectValue placeholder=\"Destination\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem value=\"pr\">Open a pull request</SelectItem>\n              <SelectItem value=\"commit\">Commit to the branch</SelectItem>\n            </SelectContent>\n          </Select>\n        </Field>\n      </section>\n\n      <section className=\"flex flex-col gap-1\">\n        <h3 className=\"font-heading text-sm font-medium\">Notifications</h3>\n        <SwitchRow id=\"notify-critical\" label=\"Critical requests\" defaultChecked />\n        <SwitchRow id=\"notify-system\" label=\"System notifications\" defaultChecked />\n        <SwitchRow id=\"notify-sound\" label=\"Completion sound\" />\n        <SwitchRow id=\"notify-dispatch\" label=\"Dispatch alerts\" defaultChecked />\n      </section>\n\n      <section className=\"flex flex-col gap-2\">\n        <h3 className=\"font-heading text-sm font-medium\">Appearance</h3>\n        <p className=\"text-sm text-muted-foreground\">\n          Manual light and dark. Ignores the system theme.\n        </p>\n        <ThemeToggle />\n      </section>\n    </div>\n  )\n}\n\nfunction ProfilePage({ onProfileSave }: { onProfileSave?: () => void }) {\n  return (\n    <form\n      className=\"flex flex-col gap-4\"\n      onSubmit={(event) => {\n        event.preventDefault()\n        onProfileSave?.()\n      }}\n    >\n      <Field id=\"settings-name\" label=\"Name\">\n        <Input\n          id=\"settings-name\"\n          name=\"name\"\n          defaultValue=\"Ada Fenwick\"\n          autoComplete=\"name\"\n        />\n      </Field>\n      <Field id=\"settings-email\" label=\"Email\">\n        <Input\n          id=\"settings-email\"\n          name=\"email\"\n          type=\"email\"\n          defaultValue=\"ada@plotter.dev\"\n          autoComplete=\"email\"\n        />\n      </Field>\n      <div className=\"flex flex-col gap-1.5\">\n        <Label>Photo</Label>\n        <FileUpload accept=\"image/*\" multiple={false} />\n      </div>\n      <div className=\"flex flex-col gap-1.5\">\n        <Label>Date of birth</Label>\n        <DatePicker\n          defaultValue={new CalendarDate(1997, 7, 28)}\n          aria-label=\"Date of birth\"\n        />\n      </div>\n      <div>\n        <Button type=\"submit\" size=\"sm\">\n          Save profile\n        </Button>\n      </div>\n    </form>\n  )\n}\n\nfunction ToolsPage() {\n  const [scope, setScope] = React.useState<\"all\" | \"enabled\" | \"disabled\">(\n    \"all\"\n  )\n  const rows = PLUGINS.filter((plugin) => {\n    if (scope === \"enabled\") return plugin.enabled\n    if (scope === \"disabled\") return !plugin.enabled\n    return true\n  })\n\n  return (\n    <div className=\"flex flex-col gap-3\">\n      <div\n        role=\"tablist\"\n        aria-label=\"Tool scope\"\n        className=\"inline-flex w-fit rounded-[2px] bg-muted p-0.5\"\n      >\n        {(\n          [\n            [\"all\", \"All\"],\n            [\"enabled\", \"Enabled\"],\n            [\"disabled\", \"Disabled\"],\n          ] as const\n        ).map(([id, label]) => (\n          <button\n            key={id}\n            type=\"button\"\n            role=\"tab\"\n            aria-selected={scope === id}\n            className={cn(\n              \"rounded-[2px] px-2.5 py-1 text-xs font-medium outline-none\",\n              \"focus-visible:ring-3 focus-visible:ring-ring/50\",\n              scope === id\n                ? \"bg-card text-foreground\"\n                : \"text-muted-foreground hover:text-foreground\"\n            )}\n            onClick={() => setScope(id)}\n          >\n            {label}\n          </button>\n        ))}\n      </div>\n      <ul className=\"flex flex-col\">\n        {rows.map((plugin) => {\n          const Icon = plugin.icon\n          const comingSoon = plugin.comingSoon === true\n          return (\n            <li\n              key={plugin.id}\n              className=\"flex items-center gap-3 border-b border-border py-3 last:border-b-0\"\n            >\n              <span className=\"flex size-8 shrink-0 items-center justify-center rounded-[2px] bg-muted\">\n                <Icon className=\"size-4\" aria-hidden />\n              </span>\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"text-sm font-medium\">{plugin.title}</p>\n                <p className=\"text-xs text-muted-foreground\">\n                  {plugin.description}\n                </p>\n              </div>\n              {comingSoon ? (\n                <Chip variant=\"outline\">Coming soon</Chip>\n              ) : (\n                <Switch\n                  defaultChecked={plugin.enabled}\n                  aria-label={plugin.title}\n                />\n              )}\n            </li>\n          )\n        })}\n      </ul>\n    </div>\n  )\n}\n\nfunction StoragePage() {\n  const [page, setPage] = React.useState(1)\n  const pages = Math.ceil(FILES.length / PAGE_SIZE)\n  const slice = FILES.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)\n  const usedPct = 25\n\n  return (\n    <div className=\"flex flex-col gap-4\">\n      <section className=\"flex flex-col gap-2\">\n        <div className=\"flex items-baseline justify-between gap-3\">\n          <h3 className=\"flex items-center gap-2 font-heading text-sm font-medium\">\n            <HardDrive className=\"size-3.5\" aria-hidden />\n            Workspace storage\n          </h3>\n          <p className=\"font-mono text-[11px] text-muted-foreground\">\n            12.4 GB of 50 GB\n          </p>\n        </div>\n        <div\n          role=\"meter\"\n          aria-label=\"Workspace storage\"\n          aria-valuemin={0}\n          aria-valuemax={100}\n          aria-valuenow={usedPct}\n          className=\"h-1.5 overflow-hidden rounded-[2px] bg-muted\"\n        >\n          <div className=\"h-full bg-primary\" style={{ width: `${usedPct}%` }} />\n        </div>\n      </section>\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead>File</TableHead>\n            <TableHead>Kind</TableHead>\n            <TableHead className=\"text-right\">Size</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {slice.map((file) => (\n            <TableRow key={file.name}>\n              <TableCell>\n                <div className=\"flex flex-col\">\n                  <span>{file.name}</span>\n                  <span className=\"font-mono text-[11px] text-muted-foreground\">\n                    {file.modified}\n                  </span>\n                </div>\n              </TableCell>\n              <TableCell>{file.kind}</TableCell>\n              <TableCell className=\"text-right font-mono text-[12px]\">\n                {file.size}\n              </TableCell>\n            </TableRow>\n          ))}\n        </TableBody>\n      </Table>\n      <Pagination>\n        <PaginationContent>\n          <PaginationItem>\n            <PaginationPrevious\n              href=\"#\"\n              onClick={(event) => {\n                event.preventDefault()\n                setPage((value) => Math.max(1, value - 1))\n              }}\n            />\n          </PaginationItem>\n          {Array.from({ length: pages }, (_, index) => (\n            <PaginationItem key={index}>\n              <PaginationLink\n                href=\"#\"\n                isActive={page === index + 1}\n                onClick={(event) => {\n                  event.preventDefault()\n                  setPage(index + 1)\n                }}\n              >\n                {index + 1}\n              </PaginationLink>\n            </PaginationItem>\n          ))}\n          <PaginationItem>\n            <PaginationNext\n              href=\"#\"\n              onClick={(event) => {\n                event.preventDefault()\n                setPage((value) => Math.min(pages, value + 1))\n              }}\n            />\n          </PaginationItem>\n        </PaginationContent>\n      </Pagination>\n    </div>\n  )\n}\n\nfunction SettingsBody({\n  page,\n  planArtSrc,\n  onPlanUpgrade,\n  onProfileSave,\n}: {\n  page: SettingsPage\n  planArtSrc?: string\n  onPlanUpgrade?: () => void\n  onProfileSave?: () => void\n}) {\n  switch (page) {\n    case \"general\":\n      return (\n        <GeneralPage planArtSrc={planArtSrc} onPlanUpgrade={onPlanUpgrade} />\n      )\n    case \"profile\":\n      return <ProfilePage onProfileSave={onProfileSave} />\n    case \"tools\":\n      return <ToolsPage />\n    case \"storage\":\n      return <StoragePage />\n  }\n}\n\nfunction SettingsLayout({\n  heading,\n  description,\n  page,\n  onPageChange,\n  onClose,\n  compact,\n  children,\n}: {\n  heading: React.ReactNode\n  description: React.ReactNode\n  page: SettingsPage\n  onPageChange: (page: SettingsPage) => void\n  onClose: () => void\n  compact?: boolean\n  children: React.ReactNode\n}) {\n  const title = PAGES.find((item) => item.id === page)?.label ?? \"Settings\"\n  return (\n    <div\n      className={cn(\n        \"flex min-h-0 flex-1 bg-popover\",\n        compact ? \"flex-col\" : \"flex-row\"\n      )}\n    >\n      {compact ? (\n        <div className=\"sticky top-0 z-10 border-b border-border bg-popover\">\n          <div className=\"flex items-center justify-between gap-3 px-3 py-2\">\n            <div className=\"min-w-0\">\n              {heading}\n              {description}\n            </div>\n            <CloseButton onClick={onClose} />\n          </div>\n          <PageNav\n            page={page}\n            onPageChange={onPageChange}\n            orientation=\"horizontal\"\n          />\n        </div>\n      ) : (\n        <div className=\"sticky top-0 flex w-44 shrink-0 flex-col border-r border-border\">\n          <div className=\"px-3 pt-3 pb-2\">\n            {heading}\n            {description}\n          </div>\n          <PageNav page={page} onPageChange={onPageChange} />\n        </div>\n      )}\n      <div className=\"flex min-h-0 min-w-0 flex-1 flex-col\">\n        <header className=\"sticky top-0 z-10 flex items-center justify-between gap-3 border-b border-border bg-popover px-4 py-3\">\n          <h3 className=\"font-heading text-sm font-medium\">{title}</h3>\n          {compact ? null : <CloseButton onClick={onClose} />}\n        </header>\n        <div className=\"min-h-0 flex-1 overflow-y-auto px-4 py-4\">\n          {children}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport function SettingsModal({\n  isOpen,\n  onClose,\n  defaultPage = \"general\",\n  planArtSrc,\n  onPlanUpgrade,\n  onProfileSave,\n}: {\n  isOpen: boolean\n  onClose: () => void\n  defaultPage?: SettingsPage\n  planArtSrc?: string\n  onPlanUpgrade?: () => void\n  onProfileSave?: () => void\n}) {\n  const desktop = useIsDesktop()\n  const [page, setPage] = React.useState<SettingsPage>(defaultPage)\n\n  React.useEffect(() => {\n    if (isOpen) setPage(defaultPage)\n  }, [isOpen, defaultPage])\n\n  const onOpenChange = (open: boolean) => {\n    if (!open) onClose()\n  }\n\n  const layout = (\n    compact: boolean,\n    heading: React.ReactNode,\n    description: React.ReactNode\n  ) => (\n    <SettingsLayout\n      heading={heading}\n      description={description}\n      page={page}\n      onPageChange={setPage}\n      onClose={onClose}\n      compact={compact}\n    >\n      <SettingsBody\n        page={page}\n        planArtSrc={planArtSrc}\n        onPlanUpgrade={onPlanUpgrade}\n        onProfileSave={onProfileSave}\n      />\n    </SettingsLayout>\n  )\n\n  return (\n    <>\n      <Dialog open={isOpen && desktop} onOpenChange={onOpenChange}>\n        <DialogContent\n          showCloseButton={false}\n          className=\"flex max-h-[min(42rem,calc(100vh-2rem))] w-full max-w-[calc(100%-2rem)] gap-0 overflow-hidden rounded-[2px] p-0 sm:max-w-3xl\"\n          aria-describedby={undefined}\n        >\n          {layout(\n            false,\n            <DialogTitle>Settings</DialogTitle>,\n            <DialogDescription className=\"sr-only\">\n              Workspace plan, profile, tools, and storage.\n            </DialogDescription>\n          )}\n        </DialogContent>\n      </Dialog>\n      <Sheet open={isOpen && !desktop} onOpenChange={onOpenChange}>\n        <SheetContent\n          side=\"right\"\n          showCloseButton={false}\n          className=\"w-full gap-0 p-0 sm:max-w-lg\"\n        >\n          {layout(\n            true,\n            <SheetTitle>Settings</SheetTitle>,\n            <SheetDescription className=\"sr-only\">\n              Workspace plan, profile, tools, and storage.\n            </SheetDescription>\n          )}\n        </SheetContent>\n      </Sheet>\n    </>\n  )\n}\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}