@hoardodile/ui 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2BVZEYNW.js +97 -0
- package/dist/chunk-2BVZEYNW.js.map +1 -0
- package/dist/chunk-2DTR6CD5.js +47 -0
- package/dist/chunk-2DTR6CD5.js.map +1 -0
- package/dist/chunk-2HWSCY4T.js +20 -0
- package/dist/chunk-2HWSCY4T.js.map +1 -0
- package/dist/chunk-363CCKQ7.js +75 -0
- package/dist/chunk-363CCKQ7.js.map +1 -0
- package/dist/chunk-3ZQJWBVB.js +30 -0
- package/dist/chunk-3ZQJWBVB.js.map +1 -0
- package/dist/chunk-4D36OO5W.js +277 -0
- package/dist/chunk-4D36OO5W.js.map +1 -0
- package/dist/chunk-4UITFLIN.js +31 -0
- package/dist/chunk-4UITFLIN.js.map +1 -0
- package/dist/chunk-6HAIUXNZ.js +11 -0
- package/dist/chunk-6HAIUXNZ.js.map +1 -0
- package/dist/chunk-7JMMFY67.js +62 -0
- package/dist/chunk-7JMMFY67.js.map +1 -0
- package/dist/chunk-7MWWHI6T.js +1149 -0
- package/dist/chunk-7MWWHI6T.js.map +1 -0
- package/dist/chunk-AEHTAUAQ.js +65 -0
- package/dist/chunk-AEHTAUAQ.js.map +1 -0
- package/dist/chunk-ASRGQ5BD.js +130 -0
- package/dist/chunk-ASRGQ5BD.js.map +1 -0
- package/dist/chunk-BFGOE5AI.js +187 -0
- package/dist/chunk-BFGOE5AI.js.map +1 -0
- package/dist/chunk-BRT25IDQ.js +46 -0
- package/dist/chunk-BRT25IDQ.js.map +1 -0
- package/dist/chunk-BU5DQOPM.js +29 -0
- package/dist/chunk-BU5DQOPM.js.map +1 -0
- package/dist/chunk-BWASOT5X.js +266 -0
- package/dist/chunk-BWASOT5X.js.map +1 -0
- package/dist/chunk-F32V4Z7Q.js +26 -0
- package/dist/chunk-F32V4Z7Q.js.map +1 -0
- package/dist/chunk-F7RHDRRJ.js +24 -0
- package/dist/chunk-F7RHDRRJ.js.map +1 -0
- package/dist/chunk-FH5RNCFR.js +21 -0
- package/dist/chunk-FH5RNCFR.js.map +1 -0
- package/dist/chunk-GKSX5ULZ.js +513 -0
- package/dist/chunk-GKSX5ULZ.js.map +1 -0
- package/dist/chunk-JPFDNRCA.js +26 -0
- package/dist/chunk-JPFDNRCA.js.map +1 -0
- package/dist/chunk-KNPRUZ4H.js +13 -0
- package/dist/chunk-KNPRUZ4H.js.map +1 -0
- package/dist/chunk-KOFWEPPB.js +254 -0
- package/dist/chunk-KOFWEPPB.js.map +1 -0
- package/dist/chunk-M5OJN54O.js +10 -0
- package/dist/chunk-M5OJN54O.js.map +1 -0
- package/dist/chunk-OEVALKQ5.js +51 -0
- package/dist/chunk-OEVALKQ5.js.map +1 -0
- package/dist/chunk-OW5FWLPH.js +33 -0
- package/dist/chunk-OW5FWLPH.js.map +1 -0
- package/dist/chunk-POX7TXBH.js +41 -0
- package/dist/chunk-POX7TXBH.js.map +1 -0
- package/dist/chunk-PWJBHUM2.js +58 -0
- package/dist/chunk-PWJBHUM2.js.map +1 -0
- package/dist/chunk-QGMDWGJT.js +23 -0
- package/dist/chunk-QGMDWGJT.js.map +1 -0
- package/dist/chunk-QGTQQNUL.js +36 -0
- package/dist/chunk-QGTQQNUL.js.map +1 -0
- package/dist/chunk-SAY52ZKI.js +152 -0
- package/dist/chunk-SAY52ZKI.js.map +1 -0
- package/dist/chunk-SKDPGSRL.js +166 -0
- package/dist/chunk-SKDPGSRL.js.map +1 -0
- package/dist/chunk-T56ARCUK.js +38 -0
- package/dist/chunk-T56ARCUK.js.map +1 -0
- package/dist/chunk-TDDHQZ62.js +227 -0
- package/dist/chunk-TDDHQZ62.js.map +1 -0
- package/dist/chunk-TMQ5OMBK.js +29 -0
- package/dist/chunk-TMQ5OMBK.js.map +1 -0
- package/dist/chunk-UEVND7AS.js +23 -0
- package/dist/chunk-UEVND7AS.js.map +1 -0
- package/dist/chunk-UPWHCG4S.js +40 -0
- package/dist/chunk-UPWHCG4S.js.map +1 -0
- package/dist/chunk-VWUNF7VX.js +3 -0
- package/dist/chunk-VWUNF7VX.js.map +1 -0
- package/dist/chunk-WI6O7GZK.js +45 -0
- package/dist/chunk-WI6O7GZK.js.map +1 -0
- package/dist/chunk-WVEK3WCX.js +21 -0
- package/dist/chunk-WVEK3WCX.js.map +1 -0
- package/dist/chunk-XBPQEZES.js +48 -0
- package/dist/chunk-XBPQEZES.js.map +1 -0
- package/dist/chunk-ZYAGKKR7.js +142 -0
- package/dist/chunk-ZYAGKKR7.js.map +1 -0
- package/dist/components/alert.js +2 -21
- package/dist/components/alert.js.map +1 -1
- package/dist/components/app-dialog.js +10 -654
- package/dist/components/app-dialog.js.map +1 -1
- package/dist/components/badge.js +2 -23
- package/dist/components/badge.js.map +1 -1
- package/dist/components/breadcrumb.js +5 -501
- package/dist/components/breadcrumb.js.map +1 -1
- package/dist/components/button.js +3 -76
- package/dist/components/button.js.map +1 -1
- package/dist/components/caption-bar.js +7 -518
- package/dist/components/caption-bar.js.map +1 -1
- package/dist/components/checkbox.js +4 -74
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip-button.js +4 -85
- package/dist/components/chip-button.js.map +1 -1
- package/dist/components/close-confirm-dialog.js +12 -662
- package/dist/components/close-confirm-dialog.js.map +1 -1
- package/dist/components/color-picker.js +7 -530
- package/dist/components/color-picker.js.map +1 -1
- package/dist/components/combobox.js +15 -934
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-by-typing-dialog.js +12 -669
- package/dist/components/confirm-by-typing-dialog.js.map +1 -1
- package/dist/components/confirm-dialog.js +11 -637
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/count-badge.js +2 -21
- package/dist/components/count-badge.js.map +1 -1
- package/dist/components/dialog.js +8 -569
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/dropdown-menu.js +9 -1004
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/dropdown-select.js +9 -880
- package/dist/components/dropdown-select.js.map +1 -1
- package/dist/components/empty.js +2 -21
- package/dist/components/empty.js.map +1 -1
- package/dist/components/filter-rail.js +7 -145
- package/dist/components/filter-rail.js.map +1 -1
- package/dist/components/fixed-action-bar.js +2 -21
- package/dist/components/fixed-action-bar.js.map +1 -1
- package/dist/components/font-picker.js +11 -586
- package/dist/components/font-picker.js.map +1 -1
- package/dist/components/form.js +9 -66
- package/dist/components/form.js.map +1 -1
- package/dist/components/icon-tile.js +3 -43
- package/dist/components/icon-tile.js.map +1 -1
- package/dist/components/icon-toggle.js +3 -43
- package/dist/components/icon-toggle.js.map +1 -1
- package/dist/components/icon.js +3 -47
- package/dist/components/icon.js.map +1 -1
- package/dist/components/image-crop-panel.js +19 -772
- package/dist/components/image-crop-panel.js.map +1 -1
- package/dist/components/image-cropper.js +1 -164
- package/dist/components/image-cropper.js.map +1 -1
- package/dist/components/input-group.js +6 -268
- package/dist/components/input-group.js.map +1 -1
- package/dist/components/input.js +3 -58
- package/dist/components/input.js.map +1 -1
- package/dist/components/label.js +3 -38
- package/dist/components/label.js.map +1 -1
- package/dist/components/list-empty-row.js +2 -21
- package/dist/components/list-empty-row.js.map +1 -1
- package/dist/components/management-empty.js +2 -21
- package/dist/components/management-empty.js.map +1 -1
- package/dist/components/management-skeleton.js +4 -59
- package/dist/components/management-skeleton.js.map +1 -1
- package/dist/components/meta-chip.js +2 -21
- package/dist/components/meta-chip.js.map +1 -1
- package/dist/components/mobile-drawer.js +7 -268
- package/dist/components/mobile-drawer.js.map +1 -1
- package/dist/components/page-header.js +3 -42
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/page-scaffold.js +5 -76
- package/dist/components/page-scaffold.js.map +1 -1
- package/dist/components/pagination-bar.js +13 -614
- package/dist/components/pagination-bar.js.map +1 -1
- package/dist/components/pagination.js +6 -681
- package/dist/components/pagination.js.map +1 -1
- package/dist/components/panel-toolbar.js +11 -713
- package/dist/components/panel-toolbar.js.map +1 -1
- package/dist/components/pill-tabs.js +2 -21
- package/dist/components/pill-tabs.js.map +1 -1
- package/dist/components/plugin-download-consent.js +13 -663
- package/dist/components/plugin-download-consent.js.map +1 -1
- package/dist/components/popover.js +5 -242
- package/dist/components/popover.js.map +1 -1
- package/dist/components/preview-card.js +2 -21
- package/dist/components/preview-card.js.map +1 -1
- package/dist/components/progress.js +2 -20
- package/dist/components/progress.js.map +1 -1
- package/dist/components/radio-group.js +2 -21
- package/dist/components/radio-group.js.map +1 -1
- package/dist/components/scroll-area.js +2 -21
- package/dist/components/scroll-area.js.map +1 -1
- package/dist/components/search-field.js +6 -587
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/secondary-stat.js +3 -40
- package/dist/components/secondary-stat.js.map +1 -1
- package/dist/components/section-header.js +3 -43
- package/dist/components/section-header.js.map +1 -1
- package/dist/components/section-label.js +3 -44
- package/dist/components/section-label.js.map +1 -1
- package/dist/components/section-tabs.js +2 -21
- package/dist/components/section-tabs.js.map +1 -1
- package/dist/components/separator.js +3 -47
- package/dist/components/separator.js.map +1 -1
- package/dist/components/sheet.js +8 -467
- package/dist/components/sheet.js.map +1 -1
- package/dist/components/sidebar.js +24 -1044
- package/dist/components/sidebar.js.map +1 -1
- package/dist/components/skeleton.js +3 -39
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/slider.js +2 -21
- package/dist/components/slider.js.map +1 -1
- package/dist/components/sortable-chip-list.js +2 -21
- package/dist/components/sortable-chip-list.js.map +1 -1
- package/dist/components/special-tag-surface.js +3 -531
- package/dist/components/special-tag-surface.js.map +1 -1
- package/dist/components/spinner.js +3 -63
- package/dist/components/spinner.js.map +1 -1
- package/dist/components/surface.js +3 -49
- package/dist/components/surface.js.map +1 -1
- package/dist/components/switch.js +3 -51
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.js +2 -20
- package/dist/components/table.js.map +1 -1
- package/dist/components/tag-chip.js +6 -617
- package/dist/components/tag-chip.js.map +1 -1
- package/dist/components/textarea.js +3 -56
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/toast.js +8 -806
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toggle.js +2 -20
- package/dist/components/toggle.js.map +1 -1
- package/dist/components/tooltip.js +3 -80
- package/dist/components/tooltip.js.map +1 -1
- package/dist/hooks/use-dialog-footer-actions.js +1 -9
- package/dist/hooks/use-dialog-footer-actions.js.map +1 -1
- package/dist/hooks/use-form-field.js +1 -34
- package/dist/hooks/use-form-field.js.map +1 -1
- package/dist/hooks/use-mobile.js +2 -27
- package/dist/hooks/use-mobile.js.map +1 -1
- package/dist/hooks/useMobileBackToClose.js +3 -232
- package/dist/hooks/useMobileBackToClose.js.map +1 -1
- package/dist/icons/actions.js +4 -521
- package/dist/icons/actions.js.map +1 -1
- package/dist/icons/icon-style.js +1 -49
- package/dist/icons/icon-style.js.map +1 -1
- package/dist/icons/marks.js +1 -46
- package/dist/icons/marks.js.map +1 -1
- package/dist/icons/registry.d.ts +2 -1
- package/dist/icons/registry.js +2 -1186
- package/dist/icons/registry.js.map +1 -1
- package/dist/index.js +10 -651
- package/dist/index.js.map +1 -1
- package/dist/lib/colors.js +1 -63
- package/dist/lib/colors.js.map +1 -1
- package/dist/lib/merge-config.js +1 -18
- package/dist/lib/merge-config.js.map +1 -1
- package/dist/lib/pagination.js +1 -24
- package/dist/lib/pagination.js.map +1 -1
- package/dist/lib/tag-surface.js +5 -613
- package/dist/lib/tag-surface.js.map +1 -1
- package/dist/lib/utils.js +2 -24
- package/dist/lib/utils.js.map +1 -1
- package/dist/res-card-template/format.d.ts +21 -0
- package/dist/res-card-template/format.js +3 -0
- package/dist/res-card-template/format.js.map +1 -0
- package/dist/res-card-template/icon.d.ts +38 -0
- package/dist/res-card-template/icon.js +3 -0
- package/dist/res-card-template/icon.js.map +1 -0
- package/dist/res-card-template/index.d.ts +4 -0
- package/dist/res-card-template/index.js +5 -0
- package/dist/res-card-template/index.js.map +1 -0
- package/dist/res-card-template/template.d.ts +48 -0
- package/dist/res-card-template/template.js +5 -0
- package/dist/res-card-template/template.js.map +1 -0
- package/dist/viewport.js +1 -11
- package/dist/viewport.js.map +1 -1
- package/package.json +9 -2
- package/src/components/image-crop-panel.dist.test.tsx +54 -0
- package/src/icons/registry.ts +8 -0
- package/src/res-card-template/format.test.ts +45 -0
- package/src/res-card-template/format.ts +36 -0
- package/src/res-card-template/icon.test.ts +100 -0
- package/src/res-card-template/icon.ts +86 -0
- package/src/res-card-template/index.ts +22 -0
- package/src/res-card-template/template.test.ts +457 -0
- package/src/res-card-template/template.ts +429 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/merge-config.ts","../../src/lib/utils.ts","../../src/components/button.tsx","../../src/icons/marks.tsx","../../src/components/checkbox.tsx","../../src/viewport.ts","../../src/hooks/use-mobile.ts","../../src/hooks/useMobileBackToClose.ts","../../src/components/dialog.tsx","../../src/hooks/use-dialog-footer-actions.ts","../../src/components/app-dialog.tsx","../../src/components/plugin-download-consent.tsx"],"names":["ButtonPrimitive","jsx","CheckboxPrimitive","DialogPrimitive","useRef","useState","jsxs","useTranslation","useEffect"],"mappings":"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","file":"plugin-download-consent.js","sourcesContent":["import { extendTailwindMerge } from \"tailwind-merge\"\n\n// The app's `@theme inline` tokens (theme.css) generate utility names\n// tailwind-merge doesn't know, so it misclassifies them — e.g. `text-ui` is\n// parsed as a text color and dropped next to any `text-*` color. The default\n// class groups are theme-backed, so extending the theme (keys mirror the v4\n// token namespaces one-to-one) registers every custom utility with correct\n// conflict semantics. merge-config.test.ts pins this list against theme.css.\nexport const mergeTheme = {\n\ttext: [\"tiny\", \"ui\", \"doc\", \"doc-title\", \"doc-heading\", \"quote\"],\n\tspacing: [\"chip\", \"control\", \"nav\", \"sidebar\", \"panel\"],\n\ttracking: [\"label\"],\n\tshadow: [\"card\", \"dialog\"],\n\tcontainer: [\"reading\", \"medium\", \"content\"],\n\tfont: [\"doc\", \"heading\"],\n\tease: [\"standard\"],\n\tanimate: [\"skel\", \"pop\"],\n} as const\n\nexport const twMerge = extendTailwindMerge({\n\textend: { theme: mergeTheme },\n})\n","import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"./merge-config\"\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs))\n}\n","import { Button as ButtonPrimitive } from \"@base-ui/react/button\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@hoardodile/ui/lib/utils\"\n\nconst buttonVariants = cva(\n \"group/button inline-flex shrink-0 items-center justify-center rounded-lg text-ui font-medium whitespace-nowrap outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-primary px-4! text-primary-foreground hover:bg-primary/80\",\n outline:\n \"border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-foreground\",\n ghost:\n \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n destructive:\n \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n danger:\n \"bg-[color-mix(in_oklab,var(--destructive),black_12%)] text-white hover:bg-[color-mix(in_oklab,var(--destructive),black_4%)] focus-visible:border-destructive/40 focus-visible:ring-destructive/20 disabled:bg-destructive/10 disabled:text-muted-foreground dark:focus-visible:ring-destructive/40\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default:\n \"h-control gap-2 px-3 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n xs: \"h-6 gap-2 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n sm: \"h-chip gap-2 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5\",\n lg: \"h-10 gap-2 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n icon: \"size-9\",\n \"icon-xs\":\n \"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3\",\n \"icon-sm\":\n \"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md\",\n \"icon-lg\": \"size-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nfunction Button({\n active,\n className,\n variant = \"default\",\n size = \"default\",\n ...props\n}: ButtonPrimitive.Props &\n VariantProps<typeof buttonVariants> & {\n /** Latched toggle state: accent fill, hover pinned so an active\n button never shifts. Callers keep choosing the matching aria\n attribute (`aria-pressed` / `aria-expanded`). */\n active?: boolean\n }) {\n return (\n <ButtonPrimitive\n data-slot=\"button\"\n className={cn(\n buttonVariants({ variant, size, className }),\n active && \"bg-accent text-accent-foreground hover:bg-accent\"\n )}\n {...props}\n />\n )\n}\n\nexport { Button, buttonVariants }\n","/**\n * Local utility marks — the three plain glyphs Solar's vocabulary lacks\n * (its ✓/×/+ only exist inside Circle/Square containers, whose marks occupy\n * barely a third of the box and go muddy below 16px). These are drawn\n * in-house for the spots where a bare mark is the whole meaning: checkbox\n * ticks, selected badges, pill clears, add affordances.\n *\n * Check and Cross are strokes in Solar's language (24 viewBox, round caps)\n * at 2 units — diagonals anti-alias gracefully at any tier. Plus is a\n * filled cross instead: its pure orthogonal edges land on integer pixels\n * at the md tier (3-unit arms render as a crisp 2px bar at 16px), where a\n * 1.33px stroke would straddle pixel boundaries and blur. Weight-insensitive:\n * no tone layer, no weight variants. Original geometry — not derived\n * from Solar assets.\n *\n * Marks ride the same `mode` channel as registry icons (declared, ignored):\n * a check is a check in every weight.\n */\nimport type { IconMode } from \"./icon-style.ts\"\n\ntype MarkProps = {\n\tclassName?: string\n\tmode?: IconMode\n}\n\nfunction Mark({\n\td,\n\tfilled,\n\tclassName,\n}: MarkProps & { d: string; filled?: boolean }) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tclassName={className}\n\t\t>\n\t\t\t{filled ? (\n\t\t\t\t<path d={d} fill=\"currentColor\" />\n\t\t\t) : (\n\t\t\t\t<path\n\t\t\t\t\td={d}\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t/>\n\t\t\t)}\n\t\t</svg>\n\t)\n}\n\n/** Plain ✓ — checkbox ticks, selected badges. */\nexport function Check(props: MarkProps) {\n\treturn <Mark {...props} d=\"M5 12.8l4.7 4.7L19 7.2\" />\n}\n\n/** Plain × — pill and chip clears. */\nexport function Cross(props: MarkProps) {\n\treturn <Mark {...props} d=\"M6 6l12 12M18 6L6 18\" />\n}\n\n/** Plain + — add affordances. Filled cross, 3-unit arms spanning 5–19. */\nexport function Plus(props: MarkProps) {\n\treturn (\n\t\t<Mark\n\t\t\t{...props}\n\t\t\tfilled\n\t\t\td=\"M10.5 5h3v5.5H19v3h-5.5V19h-3v-5.5H5v-3h5.5V5Z\"\n\t\t/>\n\t)\n}\n","\"use client\"\n\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\"\n\nimport { cn } from \"@hoardodile/ui/lib/utils\"\nimport { Check } from \"@hoardodile/ui/icons/marks\"\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n return (\n <CheckboxPrimitive.Root\n data-slot=\"checkbox\"\n className={cn(\n \"peer relative flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm bg-muted outline-none group-has-disabled/field:opacity-50 focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:bg-destructive/20 aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-checked:bg-primary data-checked:text-primary-foreground\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n data-slot=\"checkbox-indicator\"\n className=\"grid place-content-center text-current [&>svg]:size-3\"\n >\n <Check />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n}\n\nexport { Checkbox }\n","/**\n * Mobile viewport initial-scale factor used across the app shell and\n * plugin iframe previews. Kept in one place so it can be changed\n * without hunting through HTML templates and CSS rules.\n */\nexport const MOBILE_INITIAL_SCALE = 0.8\n\n/**\n * Width in pixels at which the app switches from below-md to md+ layouts.\n *\n * This value is intentionally aligned with Tailwind's `md` breakpoint\n * (768 px) so that JS detection and CSS responsive prefixes stay in sync.\n * Components that need to know whether they are in the \"below md\" viewport\n * should use {@link isBelowMd} or {@link useBelowMd} from @hoardodile/ui/hooks/use-mobile.\n */\nexport const MOBILE_BREAKPOINT_PX = 768\n\n/**\n * Same breakpoint expressed in rems, matching Tailwind's default theme\n * (`--breakpoint-md: 48rem`). Useful for CSS media queries and comments.\n */\nexport const MOBILE_BREAKPOINT_REM = MOBILE_BREAKPOINT_PX / 16\n\n/**\n * Media query string that matches viewports below the `md` breakpoint\n * (< 768 px). Shared between JS hooks and anywhere else that needs to detect\n * a below-md viewport.\n *\n * Keep this in sync with the CSS/Tailwind breakpoint so JS and CSS never\n * disagree about the viewport size.\n */\nexport const MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT_PX - 1}px)`\n\n/**\n * Width in pixels at which the app switches from a drawer to a fixed left\n * sidebar (the \"app shell\"). Raised above `md` so mid-size viewports keep\n * the whole width for their content column — below this the sidebar hides\n * behind the hamburger drawer.\n *\n * Use the CSS `sidebar:` / `max-sidebar:` variants (`--breakpoint-sidebar`)\n * at call sites — this value is the single source of truth for both.\n */\nexport const SIDEBAR_BREAKPOINT_PX = 1150\n\n/**\n * Media query string that matches viewports below the sidebar breakpoint\n * (`max-sidebar`), i.e. where the shell shows the drawer instead of the fixed\n * sidebar. Shared between JS hooks and anywhere else that needs the\n * below-sidebar viewport.\n */\nexport const SIDEBAR_QUERY = `(max-width: ${SIDEBAR_BREAKPOINT_PX - 1}px)`\n\n/**\n * Width in pixels at which the right filter rail switches from a fixed\n * column to a drawer (DESIGN.md — Layout: \"the panel becomes an\n * overlay below 1440px\"). Below this the sidebar + panel + content no\n * longer fit comfortably, so the rail hides behind a Filters button.\n *\n * Use the CSS `panel:` / `max-panel:` variants (`--breakpoint-panel`)\n * at call sites — this value is the single source of truth for both.\n */\nexport const PANEL_BREAKPOINT_PX = 1440\n\n/**\n * Media query string that matches viewports below the panel breakpoint\n * (< 1440 px), i.e. where the filter rail lives in a drawer. Shared\n * between JS hooks and anywhere else that needs the below-panel viewport.\n */\nexport const PANEL_QUERY = `(max-width: ${PANEL_BREAKPOINT_PX - 1}px)`\n","import {\n\tMOBILE_QUERY,\n\tPANEL_QUERY,\n\tSIDEBAR_QUERY,\n} from \"@hoardodile/ui/viewport\"\nimport { useMedia } from \"react-use\"\n\n/**\n * One-shot check for viewports below the `md` breakpoint (< 768 px).\n *\n * Prefer {@link useBelowMd} in React components so they re-render when the\n * viewport crosses the breakpoint. Use this helper only in event handlers,\n * utility functions, or effects where a reactive hook is not appropriate.\n */\nexport function isBelowMd(): boolean {\n\tif (typeof window === \"undefined\") return false\n\treturn window.matchMedia(MOBILE_QUERY).matches\n}\n\nexport function useBelowMd(): boolean {\n\treturn useMedia(MOBILE_QUERY, false)\n}\n\n/**\n * Reactive check for viewports below the app-shell sidebar breakpoint\n * (`max-sidebar`), where the fixed sidebar is replaced by the hamburger\n * drawer. Keep in sync with `SIDEBAR_BREAKPOINT_PX` and the\n * `sidebar:` / `max-sidebar:` CSS variants. The shell alone decides its own chrome;\n * compact-UI consumers keep using {@link useBelowMd}.\n */\nexport function useBelowSidebar(): boolean {\n\treturn useMedia(SIDEBAR_QUERY, false)\n}\n\n/**\n * Reactive check for viewports below the filter-rail breakpoint\n * (< 1440 px), where the rail becomes a drawer instead of a fixed right\n * column. Keep in sync with `PANEL_BREAKPOINT_PX` and the\n * `panel:` / `max-panel:` CSS variants.\n */\nexport function useBelowPanel(): boolean {\n\treturn useMedia(PANEL_QUERY, false)\n}\n\n/**\n * Compatibility alias for official shadcn-generated components (e.g.\n * `sidebar.tsx`), which import `useIsMobile` from this module. Mobile\n * detection rules are owned by THIS file for the whole repo (apps and\n * plugins alike) — never let the registry version of `use-mobile.ts`\n * replace it; restore after every `shadcn add`.\n */\nexport function useIsMobile(): boolean {\n\treturn useBelowMd()\n}\n","import { isBelowMd } from \"@hoardodile/ui/hooks/use-mobile\"\nimport { useEffect, useRef } from \"react\"\n\nconst HISTORY_KEY = \"__appMobileOverlay\"\n\n/**\n * LIFO stack of currently-open overlays registered via\n * {@link useMobileBackToClose}. Each entry knows how to close its own\n * overlay. The browser back gesture only closes the top entry, so\n * stacking dialogs/popovers/sheets unwind in reverse opening order\n * instead of all collapsing on the first back press.\n *\n * State is module-level so multiple component instances share a\n * single popstate listener and a single ordering source of truth.\n */\ntype OverlayEntry = {\n\treadonly id: number\n\t/** Original history state captured at push time for later restore. */\n\treadonly baseState: unknown\n\tclose: (() => void) | undefined\n}\n\nconst overlayStack: OverlayEntry[] = []\nlet popListenerInstalled = false\nlet historyPatched = false\nlet nextOverlayId = 0\n// Counts consecutive popstate events to suppress. When we call\n// `history.back()` N times (e.g. to unwind an active entry plus\n// parked entries), the next N popstate events must be swallowed so\n// they don't cascade-close deeper overlays.\nlet suppressPopCount = 0\n/**\n * Tracks every synthetic entry ID ever pushed. Used by the\n * capture-phase popstate listener to identify \"phantom\" entries —\n * synthetic history slots that remain in forward/back history after\n * the overlay was closed via route navigation. The listener auto-skips\n * these to prevent dead back-presses and router confusion.\n */\nconst seenSyntheticIds = new Set<number>()\n\n/**\n * Optional navigation-resolver registration injected by the host app.\n *\n * When set (via {@link setNavigationResolver}), `scheduleStateRestore`\n * uses it to receive a deterministic \"navigation complete\" signal from\n * the router instead of guessing timing via `requestAnimationFrame`.\n *\n * The registered function accepts a one-shot callback and returns an\n * unsubscribe function. The hook calls it, inspects `history.state`\n * inside the callback, then unsubscribes.\n */\ntype NavigationResolver = (onResolved: () => void) => () => void\nlet navigationResolver: NavigationResolver | undefined\n\n/**\n * Registers a navigation-resolver factory so that\n * {@link useMobileBackToClose} can wait for the router to finish\n * flushing its history mutations before inspecting `history.state`.\n *\n * Call this once at app startup, after the router is created:\n *\n * ```ts\n * import { setNavigationResolver } from \"@hoardodile/ui\"\n * import { router } from \"./router\"\n *\n * setNavigationResolver((fn) => router.subscribe(\"onResolved\", fn))\n * ```\n *\n * When no resolver is registered, the hook falls back to\n * `requestAnimationFrame` (correct for microtask-based routers but\n * less deterministic).\n */\nexport function setNavigationResolver(resolver: NavigationResolver): void {\n\tnavigationResolver = resolver\n}\n\n/**\n * Patches `history.pushState` once (idempotent) so that every call\n * from a router (or any other non-overlay code) is intercepted.\n *\n * When a pushState is detected that does NOT carry our\n * {@link HISTORY_KEY} marker, all open overlay entries are closed\n * and the stack is cleared. The synthetic entry's marker is\n * intentionally NOT stripped — the capture-phase popstate listener\n * uses it to identify and auto-skip the stranded entry when the\n * user later navigates past it.\n */\nfunction patchHistory(): void {\n\tif (historyPatched) return\n\tif (typeof window === \"undefined\") return\n\thistoryPatched = true\n\tconst origPush = window.history.pushState.bind(window.history)\n\twindow.history.pushState = (\n\t\tdata: unknown,\n\t\tunused: string,\n\t\turl?: string | URL | null,\n\t): void => {\n\t\tconst state = data as Record<string, unknown> | null\n\t\tif (state !== null && typeof state === \"object\" && HISTORY_KEY in state) {\n\t\t\t// Our own synthetic push — pass through.\n\t\t\torigPush(data, unused, url)\n\t\t\treturn\n\t\t}\n\t\t// Real navigation — close all overlays so the stack is\n\t\t// cleared synchronously (before React's async effects).\n\t\t// The synthetic entry remains in history with its marker\n\t\t// intact; the capture-phase popstate listener will\n\t\t// auto-skip it later.\n\t\tif (overlayStack.length > 0) {\n\t\t\tcloseAllOverlays()\n\t\t}\n\t\torigPush(data, unused, url)\n\t}\n}\n\n/**\n * Closes every active overlay in the stack (top-down) and clears\n * the stack. Called when a real navigation is detected so that\n * overlay state doesn't leak across route boundaries.\n */\nfunction closeAllOverlays(): void {\n\twhile (overlayStack.length > 0) {\n\t\tconst entry = overlayStack.pop()\n\t\tif (entry?.close !== undefined) {\n\t\t\tconst fn = entry.close\n\t\t\tentry.close = undefined\n\t\t\tfn()\n\t\t}\n\t}\n}\n\n/**\n * Installs popstate listeners and patches history.pushState.\n *\n * Two listeners are installed:\n *\n * 1. **Capture-phase** — runs BEFORE any bubbling-phase listeners\n * (including TanStack Router's popstate handler). Inspects the\n * state at the history pointer:\n *\n * - **Active entry** (id found in overlayStack): the back\n * gesture landed on a synthetic entry that belongs to an\n * overlay still below the top of the stack. Pop and close the\n * top overlay (the one the user is actually dismissing) and\n * block the event from reaching the router via\n * `stopImmediatePropagation()`.\n *\n * - **Phantom entry** (id in `seenSyntheticIds` but NOT in\n * overlayStack): a synthetic history slot that remains after\n * the overlay was closed via route navigation. Blocks the\n * event AND auto-skips by calling `history.back()` to advance\n * the pointer past the phantom, so the router never sees its\n * stale URL.\n *\n * 2. **Bubbling-phase** — closes the top overlay when the back\n * gesture returns to the base route state (no synthetic marker),\n * which happens for the last open overlay.\n */\nfunction ensurePopListener(): void {\n\tif (popListenerInstalled) return\n\tif (typeof window === \"undefined\") return\n\tpopListenerInstalled = true\n\n\t// Capture-phase gate: block synthetic popstate from reaching\n\t// the router (or any other bubbling-phase listener on window).\n\twindow.addEventListener(\n\t\t\"popstate\",\n\t\t(e: PopStateEvent) => {\n\t\t\tif (suppressPopCount > 0) return // let the counter handle it\n\t\t\tconst state = e.state as Record<string, unknown> | null\n\t\t\tif (state !== null && typeof state === \"object\" && HISTORY_KEY in state) {\n\t\t\t\tconst id = state[HISTORY_KEY]\n\t\t\t\t// Active entry still in the stack → the back gesture\n\t\t\t\t// landed on a synthetic entry of an overlay below the\n\t\t\t\t// top. Close the top overlay and hide the event from\n\t\t\t\t// the router.\n\t\t\t\tif (\n\t\t\t\t\ttypeof id === \"number\" &&\n\t\t\t\t\toverlayStack.some((entry) => entry.id === id)\n\t\t\t\t) {\n\t\t\t\t\tcloseTopOverlay()\n\t\t\t\t\te.stopImmediatePropagation()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Phantom entry: a synthetic history slot that\n\t\t\t\t// remains after the overlay was closed via\n\t\t\t\t// route navigation. Block the event from the\n\t\t\t\t// router AND auto-skip by calling history.back()\n\t\t\t\t// to move the pointer past the phantom.\n\t\t\t\te.stopImmediatePropagation()\n\t\t\t\tif (typeof id === \"number\" && seenSyntheticIds.has(id)) {\n\t\t\t\t\twindow.history.back()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\ttrue, // capture phase\n\t)\n\n\twindow.addEventListener(\"popstate\", handlePop)\n\tpatchHistory()\n}\n\nfunction closeTopOverlay(): void {\n\tconst top = overlayStack.pop()\n\tif (top === undefined) return\n\t// `close` may be cleared if this entry was dismissed via UI from a\n\t// non-top position; the popstate then just unwinds a stale\n\t// synthetic history slot and there is nothing to close.\n\tif (top.close === undefined) return\n\t// Clear close before calling it so the resulting effect sees\n\t// idRef as undefined (back-gesture close) and skips the\n\t// back() + replaceState() UI-close path.\n\tconst closeFn = top.close\n\ttop.close = undefined\n\tcloseFn()\n}\n\nfunction handlePop(): void {\n\tif (suppressPopCount > 0) {\n\t\tsuppressPopCount--\n\t\treturn\n\t}\n\tcloseTopOverlay()\n}\n\n/**\n * Removes the entry with the given `id` from the overlay stack.\n *\n * When the entry is **not** the top of the stack, it is \"parked\"\n * (left in place with its close callback cleared) so the synthetic\n * history slot survives until swept.\n *\n * @returns An object describing what was removed:\n * - `removed`: whether the entry was found.\n * - `parkedBelow`: how many parked (close=undefined) entries were\n * found below the removed entry. These must be unwound alongside\n * the active entry to prevent dead back-presses.\n */\nfunction removeFromStack(id: number): {\n\treadonly removed: boolean\n\treadonly parkedBelow: number\n} {\n\tconst idx = overlayStack.findIndex((e) => e.id === id)\n\tif (idx === -1) return { removed: false, parkedBelow: 0 }\n\tconst wasTop = idx === overlayStack.length - 1\n\tif (!wasTop) {\n\t\t// Park: keep the history slot but neutralise the callback.\n\t\tconst entry = overlayStack[idx]\n\t\tif (entry !== undefined) entry.close = undefined\n\t\treturn { removed: true, parkedBelow: 0 }\n\t}\n\t// Top entry — remove it and count ALL parked entries below so\n\t// their orphaned synthetic history slots are unwound in the same\n\t// batch as the active entry's slot.\n\toverlayStack.pop()\n\tconst remaining = overlayStack.splice(0, overlayStack.length)\n\tconst active = remaining.filter((e) => e.close !== undefined)\n\tconst parkedBelow = remaining.length - active.length\n\toverlayStack.push(...active)\n\treturn { removed: true, parkedBelow }\n}\n\n/**\n * Cleans up the synthetic history entry after an overlay closes via\n * UI (not back gesture, not route navigation).\n *\n * `history.back()` is asynchronous — it queues a history traversal\n * task. The popstate event fires only after the traversal completes\n * (pointer has moved to the target entry). By replacing the state\n * inside the popstate handler, we ensure:\n *\n * 1. The pointer is at the base entry (not the synthetic one).\n * 2. `replaceState` truncates forward history, eliminating the\n * phantom synthetic entry that `back()` alone would leave behind.\n *\n * **Route-change safety**: When a navigation-resolver is registered\n * (via {@link setNavigationResolver}), `scheduleStateRestore` uses\n * the router's deterministic \"navigation resolved\" signal to know\n * when `history.state` reflects the new route. When no resolver is\n * available (e.g. tests), it falls back to `requestAnimationFrame`,\n * which runs after the microtask queue where the router flushes its\n * pending `pushState` calls.\n *\n * - If `history.state` still carries our marker → normal UI close,\n * proceed with the `back()` + `replaceState()` cleanup.\n * - If `history.state` no longer carries our marker → a route\n * change has pushed a real entry on top. Skip the `back()` call\n * to avoid racing with the router's push. The capture-phase\n * popstate listener will auto-skip the stranded phantom entry.\n */\nfunction scheduleStateRestore(\n\tid: number,\n\tbaseState: unknown,\n\tparkedBelow: number,\n): void {\n\tfunction check(): void {\n\t\tconst currentState = window.history.state as Record<string, unknown> | null\n\n\t\t// The history pointer is no longer on this overlay's own\n\t\t// synthetic entry (route change, or another overlay has\n\t\t// already pushed/popped on top). Do NOT call history.back()\n\t\t// — it would race with the current top entry and navigate\n\t\t// to the wrong place. Just sweep parked entries from the\n\t\t// stack but do NOT prune `seenSyntheticIds`; any stranded\n\t\t// synthetic entries will be auto-skipped by the capture-phase\n\t\t// listener when the user navigates past them.\n\t\tif (\n\t\t\tcurrentState === null ||\n\t\t\ttypeof currentState !== \"object\" ||\n\t\t\t!(HISTORY_KEY in currentState) ||\n\t\t\tcurrentState[HISTORY_KEY] !== id\n\t\t) {\n\t\t\tsweepParkedEntries()\n\t\t\treturn\n\t\t}\n\n\t\tconst totalBacks = 1 + parkedBelow\n\t\tlet done = false\n\n\t\tfunction restore(): void {\n\t\t\tif (done) return\n\t\t\tdone = true\n\t\t\twindow.removeEventListener(\"popstate\", onPop)\n\t\t\twindow.history.replaceState(baseState, \"\")\n\t\t\t// replaceState truncates forward history, eliminating\n\t\t\t// phantom entries. Safe to prune their IDs from\n\t\t\t// `seenSyntheticIds` (excluding IDs still in the\n\t\t\t// active stack).\n\t\t\tconst sweptIds = sweepParkedEntries()\n\t\t\tcleanupSeenIds(sweptIds)\n\t\t}\n\n\t\tfunction onPop(): void {\n\t\t\tif (suppressPopCount > 0) {\n\t\t\t\tsuppressPopCount--\n\t\t\t\treturn\n\t\t\t}\n\t\t\trestore()\n\t\t}\n\n\t\twindow.addEventListener(\"popstate\", onPop)\n\n\t\tsuppressPopCount += totalBacks\n\t\tfor (let i = 0; i < totalBacks; i++) {\n\t\t\twindow.history.back()\n\t\t}\n\t}\n\n\t// Use both the router's deterministic \"navigation resolved\" signal\n\t// and a requestAnimationFrame fallback. In the common UI-close case\n\t// (e.g. clicking outside a dropdown) there is no route change, so\n\t// the resolver never fires. Without the fallback the synthetic\n\t// history entry would be left behind as a phantom.\n\tlet disposed = false\n\tlet rafId: number | undefined\n\tlet unsub: (() => void) | undefined\n\n\tfunction run(): void {\n\t\tif (disposed) return\n\t\tdisposed = true\n\t\tunsub?.()\n\t\tif (rafId !== undefined) {\n\t\t\tcancelAnimationFrame(rafId)\n\t\t}\n\t\tcheck()\n\t}\n\n\tif (navigationResolver !== undefined) {\n\t\tunsub = navigationResolver(run)\n\t}\n\n\trafId = requestAnimationFrame(run)\n}\n\n/**\n * Sweeps all parked entries (those with no close callback) from the\n * overlay stack. Called after `replaceState` truncates forward\n * history, since parked entries no longer have corresponding history\n * entries and would cause dead back-presses.\n *\n * @returns The IDs of entries that were removed, used by the caller\n * to prune `seenSyntheticIds`.\n */\nfunction sweepParkedEntries(): readonly number[] {\n\tconst sweptIds: number[] = []\n\tfor (let i = overlayStack.length - 1; i >= 0; i--) {\n\t\tconst entry = overlayStack[i]\n\t\tif (entry !== undefined && entry.close === undefined) {\n\t\t\tsweptIds.push(entry.id)\n\t\t\toverlayStack.splice(i, 1)\n\t\t}\n\t}\n\treturn sweptIds\n}\n\n/**\n * Removes swept IDs from `seenSyntheticIds` to prevent unbounded\n * growth over the lifetime of the SPA session.\n *\n * After `replaceState` truncates forward history, phantom entries\n * for swept parked overlays are eliminated. Their IDs can safely\n * be removed from `seenSyntheticIds` — unless the same ID belongs\n * to an entry still active in the stack (which happens when a\n * lower-ID entry is parked but a higher-ID entry above it is still\n * open and not swept).\n */\nfunction cleanupSeenIds(sweptIds: readonly number[]): void {\n\tconst activeIds = new Set(overlayStack.map((e) => e.id))\n\tfor (const id of sweptIds) {\n\t\tif (!activeIds.has(id)) {\n\t\t\tseenSyntheticIds.delete(id)\n\t\t}\n\t}\n}\n\n/**\n * On mobile (< sm), when an overlay (dialog/sheet/popover) opens we push a\n * synthetic history entry so the device back gesture closes it instead of\n * navigating away from the page.\n *\n * **Closing via UI** calls `history.back()` to navigate past the\n * synthetic entry, then uses `replaceState` (inside the resulting\n * popstate handler) to restore the original history state.\n *\n * **Closing via back gesture** is handled by the popstate listener,\n * which pops the stack entry and calls the close callback.\n *\n * **Closing via route navigation**: the `pushState` interceptor\n * closes all overlays synchronously but does NOT strip the synthetic\n * marker. The React effect's `scheduleStateRestore` waits for the\n * router's navigation-resolved signal (or falls back to\n * `requestAnimationFrame`); by then the router's push has completed\n * and the marker is gone from `history.state`, so `back()` is\n * skipped. The stranded synthetic entry is later auto-skipped by the\n * capture-phase popstate listener when the user navigates past it.\n *\n * Multiple overlays open at once stack: pressing back closes the most\n * recently opened one first, matching the visual stacking order.\n *\n * No-op on non-mobile viewports and when no `onOpenChange` is provided\n * (uncontrolled overlays cannot be closed externally).\n */\nexport function useMobileBackToClose(\n\topen: boolean | undefined,\n\tonOpenChange: ((open: boolean) => void) | undefined,\n): void {\n\tconst idRef = useRef<number | undefined>(undefined)\n\tconst onChangeRef = useRef(onOpenChange)\n\tonChangeRef.current = onOpenChange\n\n\tuseEffect(() => {\n\t\tif (typeof window === \"undefined\") return\n\t\tif (onChangeRef.current === undefined) return\n\t\tif (!isBelowMd()) return\n\t\tensurePopListener()\n\n\t\tif (open === true && idRef.current === undefined) {\n\t\t\tconst id = ++nextOverlayId\n\t\t\tidRef.current = id\n\t\t\tseenSyntheticIds.add(id)\n\t\t\t// Capture the base state BEFORE pushing so we can restore\n\t\t\t// it on close (via replaceState) to eliminate phantoms.\n\t\t\tconst baseState = window.history.state ?? null\n\t\t\twindow.history.pushState(\n\t\t\t\t{\n\t\t\t\t\t...((baseState as Record<string, unknown>) ?? {}),\n\t\t\t\t\t[HISTORY_KEY]: id,\n\t\t\t\t},\n\t\t\t\t\"\",\n\t\t\t)\n\t\t\toverlayStack.push({\n\t\t\t\tid,\n\t\t\t\tbaseState,\n\t\t\t\tclose() {\n\t\t\t\t\tidRef.current = undefined\n\t\t\t\t\tonChangeRef.current?.(false)\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tif (open !== true && idRef.current !== undefined) {\n\t\t\tconst id = idRef.current\n\t\t\tidRef.current = undefined\n\t\t\tconst entryIdx = overlayStack.findIndex((e) => e.id === id)\n\t\t\tconst entry = entryIdx !== -1 ? overlayStack[entryIdx] : undefined\n\t\t\tconst { removed, parkedBelow } = removeFromStack(id)\n\t\t\tif (removed && entry !== undefined) {\n\t\t\t\tscheduleStateRestore(id, entry.baseState, parkedBelow)\n\t\t\t}\n\t\t}\n\t}, [open])\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (idRef.current !== undefined) {\n\t\t\t\tconst id = idRef.current\n\t\t\t\tidRef.current = undefined\n\t\t\t\tconst entryIdx = overlayStack.findIndex((e) => e.id === id)\n\t\t\t\tconst entry = entryIdx !== -1 ? overlayStack[entryIdx] : undefined\n\t\t\t\tconst { removed, parkedBelow } = removeFromStack(id)\n\t\t\t\tif (removed && entry !== undefined) {\n\t\t\t\t\tscheduleStateRestore(id, entry.baseState, parkedBelow)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}, [])\n}\n","import * as React from \"react\"\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\"\nimport { useTranslation } from \"react-i18next\"\n\nimport { cn } from \"@hoardodile/ui/lib/utils\"\nimport { Button } from \"@hoardodile/ui/components/button\"\nimport { useMobileBackToClose } from \"@hoardodile/ui/hooks/useMobileBackToClose\"\nimport { Cross } from \"@hoardodile/ui/icons/marks\"\n\nfunction Dialog({\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: Omit<DialogPrimitive.Root.Props, \"onOpenChange\"> & {\n /**\n * `eventDetails` is absent for closes triggered by the mobile back\n * gesture (a synthetic close with no underlying Base UI event).\n */\n onOpenChange?: (\n open: boolean,\n eventDetails?: DialogPrimitive.Root.ChangeEventDetails,\n ) => void\n}) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(\n defaultOpen ?? false,\n )\n const isControlled = open !== undefined\n const currentOpen = isControlled ? open : uncontrolledOpen\n function handleOpenChange(\n next: boolean,\n eventDetails?: DialogPrimitive.Root.ChangeEventDetails,\n ) {\n if (!isControlled) setUncontrolledOpen(next)\n onOpenChange?.(next, eventDetails)\n }\n useMobileBackToClose(currentOpen, handleOpenChange)\n return (\n <DialogPrimitive.Root\n data-slot=\"dialog\"\n open={currentOpen}\n onOpenChange={handleOpenChange}\n {...props}\n />\n )\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: DialogPrimitive.Backdrop.Props) {\n return (\n <DialogPrimitive.Backdrop\n data-slot=\"dialog-overlay\"\n className={cn(\n // The dialog layer: a static whisper-of-ink scrim plus\n // backdrop blur — part of the surface definition, not motion.\n \"fixed inset-0 isolate z-50 bg-foreground/5 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-sm\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n overlayClassName,\n contentMotion = \"default\",\n ...props\n}: DialogPrimitive.Popup.Props &\n React.RefAttributes<HTMLDivElement> & {\n showCloseButton?: boolean\n /** Merged into {@link DialogOverlay}. Use to drop backdrop blur over heavy GPU layers (e.g. WebGL). */\n overlayClassName?: string\n /**\n * `minimal` uses fade-only enter/exit (no slide/zoom) to reduce jank\n * when opening over canvas/video or other expensive surfaces.\n */\n contentMotion?: \"default\" | \"minimal\"\n }) {\n const { t } = useTranslation(\"ui\", { useSuspense: false })\n // Auto-wrap \"loose\" body children (anything that is not a DialogHeader\n // or DialogFooter) inside a DialogBody. This guarantees a single\n // scroll container — historically callers relied on DialogContent\n // itself scrolling and added their own nested `overflow-y-auto`\n // wrappers, which produced a confusing two-scrollbar behaviour on\n // touch devices. With a single inner scroller, header/footer stay\n // pinned and only the middle region scrolls on every viewport.\n const arranged = arrangeDialogChildren(children)\n const motionClasses =\n contentMotion === \"minimal\"\n ? \"data-ending-style:opacity-0 data-starting-style:opacity-0\"\n : cn(\n \"data-ending-style:translate-y-10 data-ending-style:opacity-0 data-starting-style:translate-y-10 data-starting-style:opacity-0\",\n // ≥ sm the popup is centred with -translate-y-1/2, so the\n // starting/ending styles must keep that offset (zoom + fade\n // only, no slide) instead of clobbering it with translate-y-0.\n \"sm:data-ending-style:-translate-y-1/2 sm:data-ending-style:scale-95 sm:data-starting-style:-translate-y-1/2 sm:data-starting-style:scale-95\"\n )\n return (\n <DialogPortal>\n <DialogOverlay className={overlayClassName} />\n <DialogPrimitive.Popup\n data-slot=\"dialog-content\"\n className={cn(\n // The floating-card anatomy: `bg-card`\n // hairline-bordered surface with the system's one deep shadow.\n // Mobile: bottom sheet occupying most of the viewport so edit\n // forms have room to breathe. The component never scrolls\n // itself; only the inner DialogBody does. Header/body/footer\n // parts are held apart by the card's section gap\n // (`gap-4` between the header document, the body and the\n // hairline footer).\n \"fixed z-50 flex flex-col gap-4 bg-card text-sm text-card-foreground shadow-dialog outline-hidden transition duration-200 ease-in-out\",\n \"inset-x-0 bottom-0 max-h-[85svh] overflow-hidden rounded-t-2xl rounded-b-none border border-b-0 border-border\",\n motionClasses,\n // ≥ sm: centred modal, allow up to 90vh.\n \"sm:inset-x-auto sm:bottom-auto sm:top-1/2 sm:left-1/2 sm:w-full sm:max-w-md sm:max-h-[90vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-2xl\",\n className\n )}\n {...props}\n >\n {arranged}\n {showCloseButton && (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n className=\"absolute top-3 right-3 z-20 flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors duration-150 hover:bg-muted hover:text-foreground\"\n >\n <Cross className=\"size-3\" />\n <span className=\"sr-only\">{t(\"aria.close\")}</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n )\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn(\n // The header document: the card's `p-5`\n // rhythm — 20px top and sides, no bottom padding — with the\n // eyebrow, title row and description stacked tight; the section\n // gap below separates it from the body.\n \"flex shrink-0 flex-col bg-card px-5 pt-5\",\n className\n )}\n {...props}\n />\n )\n}\n\n/**\n * Scrolling body region for dialogs. The dialog itself never scrolls —\n * only this element does — so touch users always interact with one\n * obvious scroll surface. The card's `p-5` side rhythm lives here; the\n * section gaps (`gap-4`) separate it from the header and footer.\n */\nfunction DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-body\"\n className={cn(\n \"flex-1 overflow-y-auto overscroll-contain px-5\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DialogFooter({\n className,\n flush = false,\n showCloseButton = false,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n flush?: boolean\n showCloseButton?: boolean\n}) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\"flex shrink-0 flex-col bg-card\", className)}\n {...props}\n >\n {/* Inset hairline: the same side padding as the content below, so\n the divider never runs edge to edge. */}\n <div\n className={cn(\n \"border-t border-border\",\n flush ? \"mx-4\" : \"mx-5\"\n )}\n />\n <div\n className={cn(\n // The hairline action bar: 16px above the\n // buttons, the card's bottom padding (20px) below.\n \"flex flex-row justify-end flex-wrap gap-2\",\n flush ? \"p-4\" : \"px-5 pt-4 pb-5\"\n )}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n Close\n </DialogPrimitive.Close>\n )}\n </div>\n </div>\n )\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn(\"text-sm font-semibold\", className)}\n {...props}\n />\n )\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: DialogPrimitive.Description.Props) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn(\n \"text-xs leading-5 text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\n/**\n * Splits dialog children into header/footer/body groups so loose body\n * nodes (passed by callers that pre-date `<DialogBody>`) get wrapped in\n * a single scrolling region. Children already wrapped in\n * `<DialogBody>` are passed through unchanged.\n */\nfunction arrangeDialogChildren(children: React.ReactNode): React.ReactNode {\n const headers: React.ReactNode[] = []\n const footers: React.ReactNode[] = []\n const bodyNodes: React.ReactNode[] = []\n let hasExplicitBody = false\n React.Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) {\n bodyNodes.push(child)\n return\n }\n if (child.type === DialogHeader) {\n headers.push(child)\n return\n }\n if (child.type === DialogFooter) {\n footers.push(child)\n return\n }\n if (child.type === DialogBody) {\n hasExplicitBody = true\n bodyNodes.push(child)\n return\n }\n bodyNodes.push(child)\n })\n const body = hasExplicitBody ? (\n bodyNodes\n ) : (\n <DialogBody>{bodyNodes}</DialogBody>\n )\n return (\n <>\n {headers}\n {body}\n {footers}\n </>\n )\n}\n\nexport {\n Dialog,\n DialogBody,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n}\n","import { createContext, type ReactNode, useContext } from \"react\"\n\n/**\n * Footer action slot plumbing for `AppDialog` — the context shared\n * between the dialog shell (`@hoardodile/ui/components/app-dialog`) and\n * body panels that contribute their primary action through\n * `DialogFooterActions`, so action buttons land in the footer instead of\n * the body.\n */\n\ntype DialogFooterActionsValue = {\n\treadonly setFooterActions: (node: ReactNode | null) => void\n}\n\nconst DialogFooterActionsContext =\n\tcreateContext<DialogFooterActionsValue | null>(null)\n\n/** The nearest dialog's footer action slot, or null outside a dialog. */\nfunction useDialogFooterActions(): DialogFooterActionsValue | null {\n\treturn useContext(DialogFooterActionsContext)\n}\n\nexport { DialogFooterActionsContext, useDialogFooterActions }\n","import {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogDescription,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogTitle,\n} from \"./dialog\"\nimport {\n\tDialogFooterActionsContext,\n\tuseDialogFooterActions,\n} from \"@hoardodile/ui/hooks/use-dialog-footer-actions\"\nimport { cn } from \"@hoardodile/ui/lib/utils\"\nimport {\n\ttype ReactNode,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\"\n\n/** Dialog width tiers: the 320–480 narrow slot plus\n the wide working tiers — edit hubs, pickers and selectors get\n real estate; confirmations never leave the narrow slot. */\nexport type AppDialogSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\"\n\nconst sizeClasses: Record<AppDialogSize, string> = {\n\tsm: \"sm:max-w-sm\",\n\tmd: \"sm:max-w-md\",\n\tlg: \"sm:max-w-2xl\",\n\txl: \"sm:max-w-3xl\",\n\t\"2xl\": \"sm:max-w-4xl\",\n}\n\nexport { useDialogFooterActions }\n\n/**\n * Declarative footer action: renders nothing in the body and contributes\n * the node to the surrounding dialog's footer (appended after the\n * dialog's own `footer` prop). Panels embed their primary action this way\n * so action buttons live in the footer, never in the body. Outside a\n * dialog (sheets, pickers) it renders nothing — callers keep their inline\n * action bar as the fallback.\n */\nexport function DialogFooterActions({ children }: { children: ReactNode }) {\n\tconst slot = useDialogFooterActions()\n\tuseEffect(() => {\n\t\tif (slot === null) return\n\t\tslot.setFooterActions(children)\n\t\treturn () => slot.setFooterActions(null)\n\t}, [slot, children])\n\treturn null\n}\n\nexport type AppDialogProps = Readonly<{\n\topen: boolean\n\tonOpenChange: (open: boolean) => void\n\ttitle: ReactNode\n\t/** Uppercase section marker above the title (feature dialogs). */\n\teyebrow?: ReactNode\n\t/** Leading icon tile in the title row (feature dialogs). */\n\ticon?: ReactNode\n\t/** Danger register — the title ink takes the destructive hue; the\n\t danger itself stays in copy and the confirm button. */\n\tdanger?: boolean\n\t/** Width tier; defaults to `md` (the narrow form slot). */\n\tsize?: AppDialogSize\n\t/**\n\t * Drops the body padding for workspace bodies that own their layout\n\t * edge to edge (the two-pane editors); the header keeps its padding\n\t * and the footer becomes a plain bottom bar.\n\t */\n\tflush?: boolean\n\tdescription?: ReactNode\n\tchildren: ReactNode\n\t/** Leading footer actions; body panels may append theirs via\n\t {@link DialogFooterActions}. */\n\tfooter?: ReactNode\n\tcontentClassName?: string\n\t/** Passed to dialog overlay (e.g. disable backdrop blur over WebGL). */\n\toverlayClassName?: string\n\t/** Fade-only motion — lighter than default slide/zoom when over heavy surfaces. */\n\tcontentMotion?: \"default\" | \"minimal\"\n\t/** Defaults to true. Set to false to honour native auto-focus (rare). */\n\tsuppressAutoFocus?: boolean\n\tcontentTestId?: string\n}>\n\n/**\n * Standard dialog shell. Wires up `Dialog`/`DialogContent`/`DialogHeader`/\n * `DialogTitle` and an optional eyebrow, icon, description and footer so\n * callers stop re-stating the boilerplate. Use {@link ConfirmDialog} for\n * the common \"cancel + primary action\" pattern.\n */\nexport function AppDialog(props: AppDialogProps) {\n\tconst {\n\t\topen,\n\t\tonOpenChange,\n\t\ttitle,\n\t\teyebrow,\n\t\ticon,\n\t\tdanger,\n\t\tsize,\n\t\tflush,\n\t\tdescription,\n\t\tchildren,\n\t\tfooter,\n\t\tcontentClassName,\n\t\toverlayClassName,\n\t\tcontentMotion,\n\t\tsuppressAutoFocus = true,\n\t\tcontentTestId,\n\t} = props\n\t// Focusing the first focusable element on open would steal the caret\n\t// from the page underneath, jump scroll position, and on iOS\n\t// occasionally pop the soft keyboard before the user even reaches an\n\t// input. Routing focus to the dialog container itself keeps the\n\t// dialog accessible without those side effects.\n\tconst contentRef = useRef<HTMLDivElement | null>(null)\n\n\t// Footer actions contributed by body panels (DialogFooterActions):\n\t// appended after the dialog's own footer, reset when the dialog closes.\n\tconst [footerActions, setFooterActions] = useState<ReactNode>(null)\n\tconst actionsValue = useMemo(\n\t\t() => ({ setFooterActions }),\n\t\t[setFooterActions],\n\t)\n\n\treturn (\n\t\t<Dialog open={open} onOpenChange={onOpenChange}>\n\t\t\t<DialogContent\n\t\t\t\tref={contentRef}\n\t\t\t\tclassName={cn(\n\t\t\t\t\tsize !== undefined && sizeClasses[size],\n\t\t\t\t\tflush && \"p-0\",\n\t\t\t\t\tcontentClassName,\n\t\t\t\t)}\n\t\t\t\toverlayClassName={overlayClassName}\n\t\t\t\tcontentMotion={contentMotion}\n\t\t\t\tinitialFocus={suppressAutoFocus ? contentRef : undefined}\n\t\t\t\tdata-testid={contentTestId}\n\t\t\t>\n\t\t\t\t<DialogHeader className={flush ? \"gap-4\" : undefined}>\n\t\t\t\t\t{eyebrow !== undefined ? (\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"text-tiny font-semibold tracking-label uppercase\",\n\t\t\t\t\t\t\t\tdanger ? \"text-destructive\" : \"text-muted-foreground\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{eyebrow}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<div className=\"flex min-w-0 items-center gap-3 pr-6\">\n\t\t\t\t\t\t{icon}\n\t\t\t\t\t\t<DialogTitle\n\t\t\t\t\t\t\tclassName={cn(\"truncate\", danger && \"text-destructive\")}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{title}\n\t\t\t\t\t\t</DialogTitle>\n\t\t\t\t\t</div>\n\t\t\t\t\t{description !== undefined ? (\n\t\t\t\t\t\t<DialogDescription>{description}</DialogDescription>\n\t\t\t\t\t) : null}\n\t\t\t\t</DialogHeader>\n\t\t\t\t{/* The header, body and footer must stay direct children of\n\t\t\t\t DialogContent — its arrangement logic recognises them by\n\t\t\t\t type, and a wrapper in between would re-wrap the body in a\n\t\t\t\t second DialogBody. The footer-actions context therefore\n\t\t\t\t wraps only the body's own content. */}\n\t\t\t\t<DialogBody className={flush ? \"p-0\" : undefined}>\n\t\t\t\t\t<DialogFooterActionsContext.Provider value={actionsValue}>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</DialogFooterActionsContext.Provider>\n\t\t\t\t</DialogBody>\n\t\t\t\t{(footer !== undefined && footer !== null) ||\n\t\t\t\tfooterActions !== null ? (\n\t\t\t\t\t<DialogFooter flush={flush}>\n\t\t\t\t\t\t{footer}\n\t\t\t\t\t\t{footerActions}\n\t\t\t\t\t</DialogFooter>\n\t\t\t\t) : null}\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t)\n}\n","import { Button } from \"./button.tsx\"\nimport { Checkbox } from \"./checkbox.tsx\"\nimport { AppDialog } from \"./app-dialog.tsx\"\nimport { useEffect, useState } from \"react\"\nimport { useTranslation } from \"react-i18next\"\n\n/**\n * The shared plugin asset-download consent dialog — host chrome used by\n * both host surfaces (the app and the workbench) so the user sees one\n * dialog with one contract wherever a plugin asks to download.\n *\n * One entry = one batch question: a single download is an items array of\n * one (rendered with the single-file layout); a batched plugin call is\n * one dialog listing every item, answered all-or-nothing.\n *\n * Presentational by design: the queue lives in the host's consent store\n * (`@hoardodile/host-web`), this component renders exactly one ticket\n * and reports the decision through callbacks. Copy comes from the shared\n * `ui` catalog namespace (`pluginDownload.*`), so every React surface\n * renders the same localized strings; nothing here imports anything\n * outside `@hoardodile/ui` + `@hoardodile/i18n`, keeping the SDK closure\n * intact.\n */\n\n/**\n * A queued consent ticket, structurally identical to the server's\n * `pluginDownloadRequested` SSE event minus its `type` discriminator\n * (and to `@hoardodile/host-web`'s consent-store entry). Declared here\n * instead of imported so this package stays closure-clean.\n */\nexport type PluginConsentItem = {\n\treadonly url: string\n\treadonly dest: string\n\treadonly sizeBytes?: number\n\treadonly reason?: string\n}\n\nexport type PluginConsentTicket = {\n\treadonly ticketId: string\n\treadonly pluginId: string\n\treadonly pluginName: string\n\treadonly items: readonly PluginConsentItem[]\n}\n\nexport type PluginDownloadConsentDialogProps = {\n\t/** Ticket to show; `null` hides the dialog (one at a time). */\n\treadonly entry: PluginConsentTicket | null\n\t/** Decision callbacks — the host wires them to its own decide path. */\n\treadonly onDeny: (ticketId: string) => void\n\treadonly onAllow: (ticketId: string, remember: boolean) => void\n\t/** Byte formatter; defaults to `\"<n> B\"`. */\n\treadonly formatBytes?: (bytes: number) => string\n\t/**\n\t * Overlay stacking override. Hosts with a layer above the standard\n\t * dialog stack (e.g. the app's plugin preview window at z-60) pass a\n\t * higher value so the consent question stays visible and clickable.\n\t */\n\treadonly overlayClassName?: string\n\t/** Content stacking override; see {@link overlayClassName}. */\n\treadonly contentClassName?: string\n}\n\n/**\n * Renders the shared consent question: every download URL verbatim\n * (selectable monospace, never a link), its vault-relative destination\n * and the plugin's stated reason, with Allow / Deny and a session-remember\n * checkbox. A batch is one dialog listing all items.\n *\n * Design contract (hd-plugin-design): card + hairline + `--radius-2xl`\n * + `--shadow-dialog`, footer parted by an inset hairline, focus on the\n * dialog container; the danger stays in copy and actions, never in color.\n */\nexport function PluginDownloadConsentDialog(\n\tprops: PluginDownloadConsentDialogProps,\n) {\n\tconst { entry, onDeny, onAllow } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\tconst formatBytes = props.formatBytes ?? ((bytes: number) => `${bytes} B`)\n\tconst activeId = entry === null ? null : entry.ticketId\n\tconst itemCount = entry === null ? 0 : entry.items.length\n\tconst [remember, setRemember] = useState(false)\n\n\t// A fresh ticket starts with \"remember\" unchecked — the choice must\n\t// never leak from one download to the next.\n\tuseEffect(() => {\n\t\tsetRemember(false)\n\t}, [activeId])\n\n\treturn (\n\t\t<AppDialog\n\t\t\topen={entry !== null}\n\t\t\tonOpenChange={(open) => {\n\t\t\t\tif (!open && activeId !== null) onDeny(activeId)\n\t\t\t}}\n\t\t\toverlayClassName={props.overlayClassName}\n\t\t\tcontentClassName={props.contentClassName}\n\t\t\ttitle={\n\t\t\t\tentry === null || itemCount <= 1\n\t\t\t\t\t? t(\"pluginDownload.title\", undefined)\n\t\t\t\t\t: t(\"pluginDownload.titleMany\", { count: itemCount })\n\t\t\t}\n\t\t\teyebrow={t(\"pluginDownload.eyebrow\", undefined)}\n\t\t\tdescription={\n\t\t\t\tentry === null\n\t\t\t\t\t? undefined\n\t\t\t\t\t: itemCount <= 1\n\t\t\t\t\t\t? t(\"pluginDownload.description\", {\n\t\t\t\t\t\t\t\tpluginName: entry.pluginName,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: t(\"pluginDownload.descriptionMany\", {\n\t\t\t\t\t\t\t\tpluginName: entry.pluginName,\n\t\t\t\t\t\t\t\tcount: itemCount,\n\t\t\t\t\t\t\t})\n\t\t\t}\n\t\t\tsize=\"md\"\n\t\t\tcontentTestId=\"plugin-download-consent\"\n\t\t\tfooter={\n\t\t\t\t<div className=\"flex w-full items-center justify-between\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tdisabled={activeId === null}\n\t\t\t\t\t\tonClick={() => activeId !== null && onDeny(activeId)}\n\t\t\t\t\t\tdata-testid=\"plugin-download-deny\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{t(\"pluginDownload.deny\", undefined)}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tdisabled={activeId === null}\n\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\tactiveId !== null && onAllow(activeId, remember)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdata-testid=\"plugin-download-allow\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{t(\"pluginDownload.allow\", undefined)}\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t}\n\t\t>\n\t\t\t{entry === null ? null : itemCount <= 1 ? (\n\t\t\t\t<ConsentBody\n\t\t\t\t\tentry={entry.items[0]!}\n\t\t\t\t\tremember={remember}\n\t\t\t\t\tonRememberChange={setRemember}\n\t\t\t\t\tformatBytes={formatBytes}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<ConsentBatch\n\t\t\t\t\titems={entry.items}\n\t\t\t\t\tremember={remember}\n\t\t\t\t\tonRememberChange={setRemember}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</AppDialog>\n\t)\n}\n\nfunction ConsentBody(props: {\n\treadonly entry: PluginConsentItem\n\treadonly remember: boolean\n\treadonly onRememberChange: (value: boolean) => void\n\treadonly formatBytes: (bytes: number) => string\n}) {\n\tconst { entry, remember, onRememberChange, formatBytes } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<ConsentUrlField label={t(\"pluginDownload.urlLabel\", undefined)} url={entry.url} />\n\t\t\t<div className=\"flex flex-col gap-1.5\">\n\t\t\t\t<span className=\"text-xs tracking-label text-muted-foreground uppercase\">\n\t\t\t\t\t{t(\"pluginDownload.destLabel\", undefined)}\n\t\t\t\t</span>\n\t\t\t\t<code\n\t\t\t\t\tclassName=\"text-xs text-secondary-foreground break-all select-all\"\n\t\t\t\t\tdata-testid=\"plugin-download-dest\"\n\t\t\t\t>\n\t\t\t\t\t{entry.dest}\n\t\t\t\t</code>\n\t\t\t</div>\n\t\t\t<div className=\"text-xs text-muted-foreground\">\n\t\t\t\t{t(\"pluginDownload.sizeLabel\", undefined)}:{\" \"}\n\t\t\t\t{entry.sizeBytes === undefined\n\t\t\t\t\t? t(\"pluginDownload.unknownSize\", undefined)\n\t\t\t\t\t: formatBytes(entry.sizeBytes)}\n\t\t\t</div>\n\t\t\t{entry.reason !== undefined && entry.reason.length > 0 ? (\n\t\t\t\t<p className=\"text-xs text-secondary-foreground italic\">\n\t\t\t\t\t{entry.reason}\n\t\t\t\t</p>\n\t\t\t) : null}\n\t\t\t<RememberRow remember={remember} onRememberChange={onRememberChange} />\n\t\t</div>\n\t)\n}\n\n/** Batch layout: one row per download, URL verbatim + dest + reason. */\nfunction ConsentBatch(props: {\n\treadonly items: readonly PluginConsentItem[]\n\treadonly remember: boolean\n\treadonly onRememberChange: (value: boolean) => void\n}) {\n\tconst { items, remember, onRememberChange } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<ul className=\"flex max-h-52 flex-col gap-3 overflow-y-auto pr-1\">\n\t\t\t\t{items.map((item, index) => (\n\t\t\t\t\t<li\n\t\t\t\t\t\tkey={`${item.url}#${item.dest}`}\n\t\t\t\t\t\tclassName=\"flex flex-col gap-1 rounded-md border border-border px-2.5 py-2\"\n\t\t\t\t\t\tdata-testid={`plugin-download-item-${index}`}\n\t\t\t\t\t>\n\t\t\t\t\t\t<ConsentUrlField\n\t\t\t\t\t\t\tlabel={t(\"pluginDownload.itemLabel\", {\n\t\t\t\t\t\t\t\tindex: index + 1,\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\turl={item.url}\n\t\t\t\t\t\t\ttestId={`plugin-download-url-${index}`}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<code\n\t\t\t\t\t\t\tclassName=\"text-xs text-secondary-foreground break-all select-all\"\n\t\t\t\t\t\t\tdata-testid={`plugin-download-dest-${index}`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{item.dest}\n\t\t\t\t\t\t</code>\n\t\t\t\t\t\t{item.reason !== undefined && item.reason.length > 0 ? (\n\t\t\t\t\t\t\t<p className=\"text-xs text-secondary-foreground italic\">\n\t\t\t\t\t\t\t\t{item.reason}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</li>\n\t\t\t\t))}\n\t\t\t</ul>\n\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t{t(\"pluginDownload.batchNote\", { count: items.length })}\n\t\t\t</p>\n\t\t\t<RememberRow remember={remember} onRememberChange={onRememberChange} />\n\t\t</div>\n\t)\n}\n\nfunction ConsentUrlField(props: {\n\treadonly label: string\n\treadonly url: string\n\treadonly testId?: string\n}) {\n\treturn (\n\t\t<div className=\"flex min-w-0 flex-col gap-1\">\n\t\t\t<span className=\"text-xs tracking-label text-muted-foreground uppercase\">\n\t\t\t\t{props.label}\n\t\t\t</span>\n\t\t\t<code\n\t\t\t\tclassName=\"rounded-sm bg-muted px-2 py-1.5 text-xs text-secondary-foreground break-all select-all\"\n\t\t\t\tdata-testid={props.testId ?? \"plugin-download-url\"}\n\t\t\t>\n\t\t\t\t{props.url}\n\t\t\t</code>\n\t\t</div>\n\t)\n}\n\nfunction RememberRow(props: {\n\treadonly remember: boolean\n\treadonly onRememberChange: (value: boolean) => void\n}) {\n\tconst { remember, onRememberChange } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\treturn (\n\t\t<div className=\"flex items-center gap-2 border-t pt-3\">\n\t\t\t<Checkbox\n\t\t\t\tchecked={remember}\n\t\t\t\tonCheckedChange={(checked) => onRememberChange(checked === true)}\n\t\t\t\tid=\"plugin-download-remember\"\n\t\t\t/>\n\t\t\t<label\n\t\t\t\thtmlFor=\"plugin-download-remember\"\n\t\t\t\tclassName=\"text-xs text-secondary-foreground select-none\"\n\t\t\t>\n\t\t\t\t{t(\"pluginDownload.remember\", undefined)}\n\t\t\t</label>\n\t\t</div>\n\t)\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/plugin-download-consent.tsx"],"names":[],"mappings":"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","file":"plugin-download-consent.js","sourcesContent":["import { Button } from \"./button.tsx\"\nimport { Checkbox } from \"./checkbox.tsx\"\nimport { AppDialog } from \"./app-dialog.tsx\"\nimport { useEffect, useState } from \"react\"\nimport { useTranslation } from \"react-i18next\"\n\n/**\n * The shared plugin asset-download consent dialog — host chrome used by\n * both host surfaces (the app and the workbench) so the user sees one\n * dialog with one contract wherever a plugin asks to download.\n *\n * One entry = one batch question: a single download is an items array of\n * one (rendered with the single-file layout); a batched plugin call is\n * one dialog listing every item, answered all-or-nothing.\n *\n * Presentational by design: the queue lives in the host's consent store\n * (`@hoardodile/host-web`), this component renders exactly one ticket\n * and reports the decision through callbacks. Copy comes from the shared\n * `ui` catalog namespace (`pluginDownload.*`), so every React surface\n * renders the same localized strings; nothing here imports anything\n * outside `@hoardodile/ui` + `@hoardodile/i18n`, keeping the SDK closure\n * intact.\n */\n\n/**\n * A queued consent ticket, structurally identical to the server's\n * `pluginDownloadRequested` SSE event minus its `type` discriminator\n * (and to `@hoardodile/host-web`'s consent-store entry). Declared here\n * instead of imported so this package stays closure-clean.\n */\nexport type PluginConsentItem = {\n\treadonly url: string\n\treadonly dest: string\n\treadonly sizeBytes?: number\n\treadonly reason?: string\n}\n\nexport type PluginConsentTicket = {\n\treadonly ticketId: string\n\treadonly pluginId: string\n\treadonly pluginName: string\n\treadonly items: readonly PluginConsentItem[]\n}\n\nexport type PluginDownloadConsentDialogProps = {\n\t/** Ticket to show; `null` hides the dialog (one at a time). */\n\treadonly entry: PluginConsentTicket | null\n\t/** Decision callbacks — the host wires them to its own decide path. */\n\treadonly onDeny: (ticketId: string) => void\n\treadonly onAllow: (ticketId: string, remember: boolean) => void\n\t/** Byte formatter; defaults to `\"<n> B\"`. */\n\treadonly formatBytes?: (bytes: number) => string\n\t/**\n\t * Overlay stacking override. Hosts with a layer above the standard\n\t * dialog stack (e.g. the app's plugin preview window at z-60) pass a\n\t * higher value so the consent question stays visible and clickable.\n\t */\n\treadonly overlayClassName?: string\n\t/** Content stacking override; see {@link overlayClassName}. */\n\treadonly contentClassName?: string\n}\n\n/**\n * Renders the shared consent question: every download URL verbatim\n * (selectable monospace, never a link), its vault-relative destination\n * and the plugin's stated reason, with Allow / Deny and a session-remember\n * checkbox. A batch is one dialog listing all items.\n *\n * Design contract (hd-plugin-design): card + hairline + `--radius-2xl`\n * + `--shadow-dialog`, footer parted by an inset hairline, focus on the\n * dialog container; the danger stays in copy and actions, never in color.\n */\nexport function PluginDownloadConsentDialog(\n\tprops: PluginDownloadConsentDialogProps,\n) {\n\tconst { entry, onDeny, onAllow } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\tconst formatBytes = props.formatBytes ?? ((bytes: number) => `${bytes} B`)\n\tconst activeId = entry === null ? null : entry.ticketId\n\tconst itemCount = entry === null ? 0 : entry.items.length\n\tconst [remember, setRemember] = useState(false)\n\n\t// A fresh ticket starts with \"remember\" unchecked — the choice must\n\t// never leak from one download to the next.\n\tuseEffect(() => {\n\t\tsetRemember(false)\n\t}, [activeId])\n\n\treturn (\n\t\t<AppDialog\n\t\t\topen={entry !== null}\n\t\t\tonOpenChange={(open) => {\n\t\t\t\tif (!open && activeId !== null) onDeny(activeId)\n\t\t\t}}\n\t\t\toverlayClassName={props.overlayClassName}\n\t\t\tcontentClassName={props.contentClassName}\n\t\t\ttitle={\n\t\t\t\tentry === null || itemCount <= 1\n\t\t\t\t\t? t(\"pluginDownload.title\", undefined)\n\t\t\t\t\t: t(\"pluginDownload.titleMany\", { count: itemCount })\n\t\t\t}\n\t\t\teyebrow={t(\"pluginDownload.eyebrow\", undefined)}\n\t\t\tdescription={\n\t\t\t\tentry === null\n\t\t\t\t\t? undefined\n\t\t\t\t\t: itemCount <= 1\n\t\t\t\t\t\t? t(\"pluginDownload.description\", {\n\t\t\t\t\t\t\t\tpluginName: entry.pluginName,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: t(\"pluginDownload.descriptionMany\", {\n\t\t\t\t\t\t\t\tpluginName: entry.pluginName,\n\t\t\t\t\t\t\t\tcount: itemCount,\n\t\t\t\t\t\t\t})\n\t\t\t}\n\t\t\tsize=\"md\"\n\t\t\tcontentTestId=\"plugin-download-consent\"\n\t\t\tfooter={\n\t\t\t\t<div className=\"flex w-full items-center justify-between\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tdisabled={activeId === null}\n\t\t\t\t\t\tonClick={() => activeId !== null && onDeny(activeId)}\n\t\t\t\t\t\tdata-testid=\"plugin-download-deny\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{t(\"pluginDownload.deny\", undefined)}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tdisabled={activeId === null}\n\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\tactiveId !== null && onAllow(activeId, remember)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdata-testid=\"plugin-download-allow\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{t(\"pluginDownload.allow\", undefined)}\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t}\n\t\t>\n\t\t\t{entry === null ? null : itemCount <= 1 ? (\n\t\t\t\t<ConsentBody\n\t\t\t\t\tentry={entry.items[0]!}\n\t\t\t\t\tremember={remember}\n\t\t\t\t\tonRememberChange={setRemember}\n\t\t\t\t\tformatBytes={formatBytes}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<ConsentBatch\n\t\t\t\t\titems={entry.items}\n\t\t\t\t\tremember={remember}\n\t\t\t\t\tonRememberChange={setRemember}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</AppDialog>\n\t)\n}\n\nfunction ConsentBody(props: {\n\treadonly entry: PluginConsentItem\n\treadonly remember: boolean\n\treadonly onRememberChange: (value: boolean) => void\n\treadonly formatBytes: (bytes: number) => string\n}) {\n\tconst { entry, remember, onRememberChange, formatBytes } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<ConsentUrlField label={t(\"pluginDownload.urlLabel\", undefined)} url={entry.url} />\n\t\t\t<div className=\"flex flex-col gap-1.5\">\n\t\t\t\t<span className=\"text-xs tracking-label text-muted-foreground uppercase\">\n\t\t\t\t\t{t(\"pluginDownload.destLabel\", undefined)}\n\t\t\t\t</span>\n\t\t\t\t<code\n\t\t\t\t\tclassName=\"text-xs text-secondary-foreground break-all select-all\"\n\t\t\t\t\tdata-testid=\"plugin-download-dest\"\n\t\t\t\t>\n\t\t\t\t\t{entry.dest}\n\t\t\t\t</code>\n\t\t\t</div>\n\t\t\t<div className=\"text-xs text-muted-foreground\">\n\t\t\t\t{t(\"pluginDownload.sizeLabel\", undefined)}:{\" \"}\n\t\t\t\t{entry.sizeBytes === undefined\n\t\t\t\t\t? t(\"pluginDownload.unknownSize\", undefined)\n\t\t\t\t\t: formatBytes(entry.sizeBytes)}\n\t\t\t</div>\n\t\t\t{entry.reason !== undefined && entry.reason.length > 0 ? (\n\t\t\t\t<p className=\"text-xs text-secondary-foreground italic\">\n\t\t\t\t\t{entry.reason}\n\t\t\t\t</p>\n\t\t\t) : null}\n\t\t\t<RememberRow remember={remember} onRememberChange={onRememberChange} />\n\t\t</div>\n\t)\n}\n\n/** Batch layout: one row per download, URL verbatim + dest + reason. */\nfunction ConsentBatch(props: {\n\treadonly items: readonly PluginConsentItem[]\n\treadonly remember: boolean\n\treadonly onRememberChange: (value: boolean) => void\n}) {\n\tconst { items, remember, onRememberChange } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<ul className=\"flex max-h-52 flex-col gap-3 overflow-y-auto pr-1\">\n\t\t\t\t{items.map((item, index) => (\n\t\t\t\t\t<li\n\t\t\t\t\t\tkey={`${item.url}#${item.dest}`}\n\t\t\t\t\t\tclassName=\"flex flex-col gap-1 rounded-md border border-border px-2.5 py-2\"\n\t\t\t\t\t\tdata-testid={`plugin-download-item-${index}`}\n\t\t\t\t\t>\n\t\t\t\t\t\t<ConsentUrlField\n\t\t\t\t\t\t\tlabel={t(\"pluginDownload.itemLabel\", {\n\t\t\t\t\t\t\t\tindex: index + 1,\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\turl={item.url}\n\t\t\t\t\t\t\ttestId={`plugin-download-url-${index}`}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<code\n\t\t\t\t\t\t\tclassName=\"text-xs text-secondary-foreground break-all select-all\"\n\t\t\t\t\t\t\tdata-testid={`plugin-download-dest-${index}`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{item.dest}\n\t\t\t\t\t\t</code>\n\t\t\t\t\t\t{item.reason !== undefined && item.reason.length > 0 ? (\n\t\t\t\t\t\t\t<p className=\"text-xs text-secondary-foreground italic\">\n\t\t\t\t\t\t\t\t{item.reason}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</li>\n\t\t\t\t))}\n\t\t\t</ul>\n\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t{t(\"pluginDownload.batchNote\", { count: items.length })}\n\t\t\t</p>\n\t\t\t<RememberRow remember={remember} onRememberChange={onRememberChange} />\n\t\t</div>\n\t)\n}\n\nfunction ConsentUrlField(props: {\n\treadonly label: string\n\treadonly url: string\n\treadonly testId?: string\n}) {\n\treturn (\n\t\t<div className=\"flex min-w-0 flex-col gap-1\">\n\t\t\t<span className=\"text-xs tracking-label text-muted-foreground uppercase\">\n\t\t\t\t{props.label}\n\t\t\t</span>\n\t\t\t<code\n\t\t\t\tclassName=\"rounded-sm bg-muted px-2 py-1.5 text-xs text-secondary-foreground break-all select-all\"\n\t\t\t\tdata-testid={props.testId ?? \"plugin-download-url\"}\n\t\t\t>\n\t\t\t\t{props.url}\n\t\t\t</code>\n\t\t</div>\n\t)\n}\n\nfunction RememberRow(props: {\n\treadonly remember: boolean\n\treadonly onRememberChange: (value: boolean) => void\n}) {\n\tconst { remember, onRememberChange } = props\n\tconst { t } = useTranslation(\"ui\", { useSuspense: false })\n\treturn (\n\t\t<div className=\"flex items-center gap-2 border-t pt-3\">\n\t\t\t<Checkbox\n\t\t\t\tchecked={remember}\n\t\t\t\tonCheckedChange={(checked) => onRememberChange(checked === true)}\n\t\t\t\tid=\"plugin-download-remember\"\n\t\t\t/>\n\t\t\t<label\n\t\t\t\thtmlFor=\"plugin-download-remember\"\n\t\t\t\tclassName=\"text-xs text-secondary-foreground select-none\"\n\t\t\t>\n\t\t\t\t{t(\"pluginDownload.remember\", undefined)}\n\t\t\t</label>\n\t\t</div>\n\t)\n}\n"]}
|
|
@@ -1,249 +1,12 @@
|
|
|
1
|
+
import { useMobileBackToClose } from '../chunk-TDDHQZ62.js';
|
|
2
|
+
import '../chunk-QGMDWGJT.js';
|
|
3
|
+
import '../chunk-KNPRUZ4H.js';
|
|
4
|
+
import { cn } from '../chunk-M5OJN54O.js';
|
|
5
|
+
import '../chunk-2HWSCY4T.js';
|
|
1
6
|
import * as React from 'react';
|
|
2
|
-
import { useRef, useEffect } from 'react';
|
|
3
7
|
import { Popover as Popover$1 } from '@base-ui/react/popover';
|
|
4
|
-
import { clsx } from 'clsx';
|
|
5
|
-
import { extendTailwindMerge } from 'tailwind-merge';
|
|
6
|
-
import 'react-use';
|
|
7
8
|
import { jsx } from 'react/jsx-runtime';
|
|
8
9
|
|
|
9
|
-
// src/components/popover.tsx
|
|
10
|
-
var mergeTheme = {
|
|
11
|
-
text: ["tiny", "ui", "doc", "doc-title", "doc-heading", "quote"],
|
|
12
|
-
spacing: ["chip", "control", "nav", "sidebar", "panel"],
|
|
13
|
-
tracking: ["label"],
|
|
14
|
-
shadow: ["card", "dialog"],
|
|
15
|
-
container: ["reading", "medium", "content"],
|
|
16
|
-
font: ["doc", "heading"],
|
|
17
|
-
ease: ["standard"],
|
|
18
|
-
animate: ["skel", "pop"]
|
|
19
|
-
};
|
|
20
|
-
var twMerge = extendTailwindMerge({
|
|
21
|
-
extend: { theme: mergeTheme }
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// src/lib/utils.ts
|
|
25
|
-
function cn(...inputs) {
|
|
26
|
-
return twMerge(clsx(inputs));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// src/viewport.ts
|
|
30
|
-
var MOBILE_BREAKPOINT_PX = 768;
|
|
31
|
-
var MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT_PX - 1}px)`;
|
|
32
|
-
function isBelowMd() {
|
|
33
|
-
if (typeof window === "undefined") return false;
|
|
34
|
-
return window.matchMedia(MOBILE_QUERY).matches;
|
|
35
|
-
}
|
|
36
|
-
var HISTORY_KEY = "__appMobileOverlay";
|
|
37
|
-
var overlayStack = [];
|
|
38
|
-
var popListenerInstalled = false;
|
|
39
|
-
var historyPatched = false;
|
|
40
|
-
var nextOverlayId = 0;
|
|
41
|
-
var suppressPopCount = 0;
|
|
42
|
-
var seenSyntheticIds = /* @__PURE__ */ new Set();
|
|
43
|
-
function patchHistory() {
|
|
44
|
-
if (historyPatched) return;
|
|
45
|
-
if (typeof window === "undefined") return;
|
|
46
|
-
historyPatched = true;
|
|
47
|
-
const origPush = window.history.pushState.bind(window.history);
|
|
48
|
-
window.history.pushState = (data, unused, url) => {
|
|
49
|
-
const state = data;
|
|
50
|
-
if (state !== null && typeof state === "object" && HISTORY_KEY in state) {
|
|
51
|
-
origPush(data, unused, url);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (overlayStack.length > 0) {
|
|
55
|
-
closeAllOverlays();
|
|
56
|
-
}
|
|
57
|
-
origPush(data, unused, url);
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function closeAllOverlays() {
|
|
61
|
-
while (overlayStack.length > 0) {
|
|
62
|
-
const entry = overlayStack.pop();
|
|
63
|
-
if (entry?.close !== void 0) {
|
|
64
|
-
const fn = entry.close;
|
|
65
|
-
entry.close = void 0;
|
|
66
|
-
fn();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function ensurePopListener() {
|
|
71
|
-
if (popListenerInstalled) return;
|
|
72
|
-
if (typeof window === "undefined") return;
|
|
73
|
-
popListenerInstalled = true;
|
|
74
|
-
window.addEventListener(
|
|
75
|
-
"popstate",
|
|
76
|
-
(e) => {
|
|
77
|
-
if (suppressPopCount > 0) return;
|
|
78
|
-
const state = e.state;
|
|
79
|
-
if (state !== null && typeof state === "object" && HISTORY_KEY in state) {
|
|
80
|
-
const id = state[HISTORY_KEY];
|
|
81
|
-
if (typeof id === "number" && overlayStack.some((entry) => entry.id === id)) {
|
|
82
|
-
closeTopOverlay();
|
|
83
|
-
e.stopImmediatePropagation();
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
e.stopImmediatePropagation();
|
|
87
|
-
if (typeof id === "number" && seenSyntheticIds.has(id)) {
|
|
88
|
-
window.history.back();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
true
|
|
93
|
-
// capture phase
|
|
94
|
-
);
|
|
95
|
-
window.addEventListener("popstate", handlePop);
|
|
96
|
-
patchHistory();
|
|
97
|
-
}
|
|
98
|
-
function closeTopOverlay() {
|
|
99
|
-
const top = overlayStack.pop();
|
|
100
|
-
if (top === void 0) return;
|
|
101
|
-
if (top.close === void 0) return;
|
|
102
|
-
const closeFn = top.close;
|
|
103
|
-
top.close = void 0;
|
|
104
|
-
closeFn();
|
|
105
|
-
}
|
|
106
|
-
function handlePop() {
|
|
107
|
-
if (suppressPopCount > 0) {
|
|
108
|
-
suppressPopCount--;
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
closeTopOverlay();
|
|
112
|
-
}
|
|
113
|
-
function removeFromStack(id) {
|
|
114
|
-
const idx = overlayStack.findIndex((e) => e.id === id);
|
|
115
|
-
if (idx === -1) return { removed: false, parkedBelow: 0 };
|
|
116
|
-
const wasTop = idx === overlayStack.length - 1;
|
|
117
|
-
if (!wasTop) {
|
|
118
|
-
const entry = overlayStack[idx];
|
|
119
|
-
if (entry !== void 0) entry.close = void 0;
|
|
120
|
-
return { removed: true, parkedBelow: 0 };
|
|
121
|
-
}
|
|
122
|
-
overlayStack.pop();
|
|
123
|
-
const remaining = overlayStack.splice(0, overlayStack.length);
|
|
124
|
-
const active = remaining.filter((e) => e.close !== void 0);
|
|
125
|
-
const parkedBelow = remaining.length - active.length;
|
|
126
|
-
overlayStack.push(...active);
|
|
127
|
-
return { removed: true, parkedBelow };
|
|
128
|
-
}
|
|
129
|
-
function scheduleStateRestore(id, baseState, parkedBelow) {
|
|
130
|
-
function check() {
|
|
131
|
-
const currentState = window.history.state;
|
|
132
|
-
if (currentState === null || typeof currentState !== "object" || !(HISTORY_KEY in currentState) || currentState[HISTORY_KEY] !== id) {
|
|
133
|
-
sweepParkedEntries();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const totalBacks = 1 + parkedBelow;
|
|
137
|
-
let done = false;
|
|
138
|
-
function restore() {
|
|
139
|
-
if (done) return;
|
|
140
|
-
done = true;
|
|
141
|
-
window.removeEventListener("popstate", onPop);
|
|
142
|
-
window.history.replaceState(baseState, "");
|
|
143
|
-
const sweptIds = sweepParkedEntries();
|
|
144
|
-
cleanupSeenIds(sweptIds);
|
|
145
|
-
}
|
|
146
|
-
function onPop() {
|
|
147
|
-
if (suppressPopCount > 0) {
|
|
148
|
-
suppressPopCount--;
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
restore();
|
|
152
|
-
}
|
|
153
|
-
window.addEventListener("popstate", onPop);
|
|
154
|
-
suppressPopCount += totalBacks;
|
|
155
|
-
for (let i = 0; i < totalBacks; i++) {
|
|
156
|
-
window.history.back();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
let disposed = false;
|
|
160
|
-
let rafId;
|
|
161
|
-
function run() {
|
|
162
|
-
if (disposed) return;
|
|
163
|
-
disposed = true;
|
|
164
|
-
if (rafId !== void 0) {
|
|
165
|
-
cancelAnimationFrame(rafId);
|
|
166
|
-
}
|
|
167
|
-
check();
|
|
168
|
-
}
|
|
169
|
-
rafId = requestAnimationFrame(run);
|
|
170
|
-
}
|
|
171
|
-
function sweepParkedEntries() {
|
|
172
|
-
const sweptIds = [];
|
|
173
|
-
for (let i = overlayStack.length - 1; i >= 0; i--) {
|
|
174
|
-
const entry = overlayStack[i];
|
|
175
|
-
if (entry !== void 0 && entry.close === void 0) {
|
|
176
|
-
sweptIds.push(entry.id);
|
|
177
|
-
overlayStack.splice(i, 1);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return sweptIds;
|
|
181
|
-
}
|
|
182
|
-
function cleanupSeenIds(sweptIds) {
|
|
183
|
-
const activeIds = new Set(overlayStack.map((e) => e.id));
|
|
184
|
-
for (const id of sweptIds) {
|
|
185
|
-
if (!activeIds.has(id)) {
|
|
186
|
-
seenSyntheticIds.delete(id);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function useMobileBackToClose(open, onOpenChange) {
|
|
191
|
-
const idRef = useRef(void 0);
|
|
192
|
-
const onChangeRef = useRef(onOpenChange);
|
|
193
|
-
onChangeRef.current = onOpenChange;
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
if (typeof window === "undefined") return;
|
|
196
|
-
if (onChangeRef.current === void 0) return;
|
|
197
|
-
if (!isBelowMd()) return;
|
|
198
|
-
ensurePopListener();
|
|
199
|
-
if (open === true && idRef.current === void 0) {
|
|
200
|
-
const id = ++nextOverlayId;
|
|
201
|
-
idRef.current = id;
|
|
202
|
-
seenSyntheticIds.add(id);
|
|
203
|
-
const baseState = window.history.state ?? null;
|
|
204
|
-
window.history.pushState(
|
|
205
|
-
{
|
|
206
|
-
...baseState ?? {},
|
|
207
|
-
[HISTORY_KEY]: id
|
|
208
|
-
},
|
|
209
|
-
""
|
|
210
|
-
);
|
|
211
|
-
overlayStack.push({
|
|
212
|
-
id,
|
|
213
|
-
baseState,
|
|
214
|
-
close() {
|
|
215
|
-
idRef.current = void 0;
|
|
216
|
-
onChangeRef.current?.(false);
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (open !== true && idRef.current !== void 0) {
|
|
222
|
-
const id = idRef.current;
|
|
223
|
-
idRef.current = void 0;
|
|
224
|
-
const entryIdx = overlayStack.findIndex((e) => e.id === id);
|
|
225
|
-
const entry = entryIdx !== -1 ? overlayStack[entryIdx] : void 0;
|
|
226
|
-
const { removed, parkedBelow } = removeFromStack(id);
|
|
227
|
-
if (removed && entry !== void 0) {
|
|
228
|
-
scheduleStateRestore(id, entry.baseState, parkedBelow);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}, [open]);
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
return () => {
|
|
234
|
-
if (idRef.current !== void 0) {
|
|
235
|
-
const id = idRef.current;
|
|
236
|
-
idRef.current = void 0;
|
|
237
|
-
const entryIdx = overlayStack.findIndex((e) => e.id === id);
|
|
238
|
-
const entry = entryIdx !== -1 ? overlayStack[entryIdx] : void 0;
|
|
239
|
-
const { removed, parkedBelow } = removeFromStack(id);
|
|
240
|
-
if (removed && entry !== void 0) {
|
|
241
|
-
scheduleStateRestore(id, entry.baseState, parkedBelow);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
}, []);
|
|
246
|
-
}
|
|
247
10
|
function Popover({
|
|
248
11
|
open,
|
|
249
12
|
defaultOpen,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/merge-config.ts","../../src/lib/utils.ts","../../src/viewport.ts","../../src/hooks/use-mobile.ts","../../src/hooks/useMobileBackToClose.ts","../../src/components/popover.tsx"],"names":["PopoverPrimitive"],"mappings":"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","file":"popover.js","sourcesContent":["import { extendTailwindMerge } from \"tailwind-merge\"\n\n// The app's `@theme inline` tokens (theme.css) generate utility names\n// tailwind-merge doesn't know, so it misclassifies them — e.g. `text-ui` is\n// parsed as a text color and dropped next to any `text-*` color. The default\n// class groups are theme-backed, so extending the theme (keys mirror the v4\n// token namespaces one-to-one) registers every custom utility with correct\n// conflict semantics. merge-config.test.ts pins this list against theme.css.\nexport const mergeTheme = {\n\ttext: [\"tiny\", \"ui\", \"doc\", \"doc-title\", \"doc-heading\", \"quote\"],\n\tspacing: [\"chip\", \"control\", \"nav\", \"sidebar\", \"panel\"],\n\ttracking: [\"label\"],\n\tshadow: [\"card\", \"dialog\"],\n\tcontainer: [\"reading\", \"medium\", \"content\"],\n\tfont: [\"doc\", \"heading\"],\n\tease: [\"standard\"],\n\tanimate: [\"skel\", \"pop\"],\n} as const\n\nexport const twMerge = extendTailwindMerge({\n\textend: { theme: mergeTheme },\n})\n","import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"./merge-config\"\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs))\n}\n","/**\n * Mobile viewport initial-scale factor used across the app shell and\n * plugin iframe previews. Kept in one place so it can be changed\n * without hunting through HTML templates and CSS rules.\n */\nexport const MOBILE_INITIAL_SCALE = 0.8\n\n/**\n * Width in pixels at which the app switches from below-md to md+ layouts.\n *\n * This value is intentionally aligned with Tailwind's `md` breakpoint\n * (768 px) so that JS detection and CSS responsive prefixes stay in sync.\n * Components that need to know whether they are in the \"below md\" viewport\n * should use {@link isBelowMd} or {@link useBelowMd} from @hoardodile/ui/hooks/use-mobile.\n */\nexport const MOBILE_BREAKPOINT_PX = 768\n\n/**\n * Same breakpoint expressed in rems, matching Tailwind's default theme\n * (`--breakpoint-md: 48rem`). Useful for CSS media queries and comments.\n */\nexport const MOBILE_BREAKPOINT_REM = MOBILE_BREAKPOINT_PX / 16\n\n/**\n * Media query string that matches viewports below the `md` breakpoint\n * (< 768 px). Shared between JS hooks and anywhere else that needs to detect\n * a below-md viewport.\n *\n * Keep this in sync with the CSS/Tailwind breakpoint so JS and CSS never\n * disagree about the viewport size.\n */\nexport const MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT_PX - 1}px)`\n\n/**\n * Width in pixels at which the app switches from a drawer to a fixed left\n * sidebar (the \"app shell\"). Raised above `md` so mid-size viewports keep\n * the whole width for their content column — below this the sidebar hides\n * behind the hamburger drawer.\n *\n * Use the CSS `sidebar:` / `max-sidebar:` variants (`--breakpoint-sidebar`)\n * at call sites — this value is the single source of truth for both.\n */\nexport const SIDEBAR_BREAKPOINT_PX = 1150\n\n/**\n * Media query string that matches viewports below the sidebar breakpoint\n * (`max-sidebar`), i.e. where the shell shows the drawer instead of the fixed\n * sidebar. Shared between JS hooks and anywhere else that needs the\n * below-sidebar viewport.\n */\nexport const SIDEBAR_QUERY = `(max-width: ${SIDEBAR_BREAKPOINT_PX - 1}px)`\n\n/**\n * Width in pixels at which the right filter rail switches from a fixed\n * column to a drawer (DESIGN.md — Layout: \"the panel becomes an\n * overlay below 1440px\"). Below this the sidebar + panel + content no\n * longer fit comfortably, so the rail hides behind a Filters button.\n *\n * Use the CSS `panel:` / `max-panel:` variants (`--breakpoint-panel`)\n * at call sites — this value is the single source of truth for both.\n */\nexport const PANEL_BREAKPOINT_PX = 1440\n\n/**\n * Media query string that matches viewports below the panel breakpoint\n * (< 1440 px), i.e. where the filter rail lives in a drawer. Shared\n * between JS hooks and anywhere else that needs the below-panel viewport.\n */\nexport const PANEL_QUERY = `(max-width: ${PANEL_BREAKPOINT_PX - 1}px)`\n","import {\n\tMOBILE_QUERY,\n\tPANEL_QUERY,\n\tSIDEBAR_QUERY,\n} from \"@hoardodile/ui/viewport\"\nimport { useMedia } from \"react-use\"\n\n/**\n * One-shot check for viewports below the `md` breakpoint (< 768 px).\n *\n * Prefer {@link useBelowMd} in React components so they re-render when the\n * viewport crosses the breakpoint. Use this helper only in event handlers,\n * utility functions, or effects where a reactive hook is not appropriate.\n */\nexport function isBelowMd(): boolean {\n\tif (typeof window === \"undefined\") return false\n\treturn window.matchMedia(MOBILE_QUERY).matches\n}\n\nexport function useBelowMd(): boolean {\n\treturn useMedia(MOBILE_QUERY, false)\n}\n\n/**\n * Reactive check for viewports below the app-shell sidebar breakpoint\n * (`max-sidebar`), where the fixed sidebar is replaced by the hamburger\n * drawer. Keep in sync with `SIDEBAR_BREAKPOINT_PX` and the\n * `sidebar:` / `max-sidebar:` CSS variants. The shell alone decides its own chrome;\n * compact-UI consumers keep using {@link useBelowMd}.\n */\nexport function useBelowSidebar(): boolean {\n\treturn useMedia(SIDEBAR_QUERY, false)\n}\n\n/**\n * Reactive check for viewports below the filter-rail breakpoint\n * (< 1440 px), where the rail becomes a drawer instead of a fixed right\n * column. Keep in sync with `PANEL_BREAKPOINT_PX` and the\n * `panel:` / `max-panel:` CSS variants.\n */\nexport function useBelowPanel(): boolean {\n\treturn useMedia(PANEL_QUERY, false)\n}\n\n/**\n * Compatibility alias for official shadcn-generated components (e.g.\n * `sidebar.tsx`), which import `useIsMobile` from this module. Mobile\n * detection rules are owned by THIS file for the whole repo (apps and\n * plugins alike) — never let the registry version of `use-mobile.ts`\n * replace it; restore after every `shadcn add`.\n */\nexport function useIsMobile(): boolean {\n\treturn useBelowMd()\n}\n","import { isBelowMd } from \"@hoardodile/ui/hooks/use-mobile\"\nimport { useEffect, useRef } from \"react\"\n\nconst HISTORY_KEY = \"__appMobileOverlay\"\n\n/**\n * LIFO stack of currently-open overlays registered via\n * {@link useMobileBackToClose}. Each entry knows how to close its own\n * overlay. The browser back gesture only closes the top entry, so\n * stacking dialogs/popovers/sheets unwind in reverse opening order\n * instead of all collapsing on the first back press.\n *\n * State is module-level so multiple component instances share a\n * single popstate listener and a single ordering source of truth.\n */\ntype OverlayEntry = {\n\treadonly id: number\n\t/** Original history state captured at push time for later restore. */\n\treadonly baseState: unknown\n\tclose: (() => void) | undefined\n}\n\nconst overlayStack: OverlayEntry[] = []\nlet popListenerInstalled = false\nlet historyPatched = false\nlet nextOverlayId = 0\n// Counts consecutive popstate events to suppress. When we call\n// `history.back()` N times (e.g. to unwind an active entry plus\n// parked entries), the next N popstate events must be swallowed so\n// they don't cascade-close deeper overlays.\nlet suppressPopCount = 0\n/**\n * Tracks every synthetic entry ID ever pushed. Used by the\n * capture-phase popstate listener to identify \"phantom\" entries —\n * synthetic history slots that remain in forward/back history after\n * the overlay was closed via route navigation. The listener auto-skips\n * these to prevent dead back-presses and router confusion.\n */\nconst seenSyntheticIds = new Set<number>()\n\n/**\n * Optional navigation-resolver registration injected by the host app.\n *\n * When set (via {@link setNavigationResolver}), `scheduleStateRestore`\n * uses it to receive a deterministic \"navigation complete\" signal from\n * the router instead of guessing timing via `requestAnimationFrame`.\n *\n * The registered function accepts a one-shot callback and returns an\n * unsubscribe function. The hook calls it, inspects `history.state`\n * inside the callback, then unsubscribes.\n */\ntype NavigationResolver = (onResolved: () => void) => () => void\nlet navigationResolver: NavigationResolver | undefined\n\n/**\n * Registers a navigation-resolver factory so that\n * {@link useMobileBackToClose} can wait for the router to finish\n * flushing its history mutations before inspecting `history.state`.\n *\n * Call this once at app startup, after the router is created:\n *\n * ```ts\n * import { setNavigationResolver } from \"@hoardodile/ui\"\n * import { router } from \"./router\"\n *\n * setNavigationResolver((fn) => router.subscribe(\"onResolved\", fn))\n * ```\n *\n * When no resolver is registered, the hook falls back to\n * `requestAnimationFrame` (correct for microtask-based routers but\n * less deterministic).\n */\nexport function setNavigationResolver(resolver: NavigationResolver): void {\n\tnavigationResolver = resolver\n}\n\n/**\n * Patches `history.pushState` once (idempotent) so that every call\n * from a router (or any other non-overlay code) is intercepted.\n *\n * When a pushState is detected that does NOT carry our\n * {@link HISTORY_KEY} marker, all open overlay entries are closed\n * and the stack is cleared. The synthetic entry's marker is\n * intentionally NOT stripped — the capture-phase popstate listener\n * uses it to identify and auto-skip the stranded entry when the\n * user later navigates past it.\n */\nfunction patchHistory(): void {\n\tif (historyPatched) return\n\tif (typeof window === \"undefined\") return\n\thistoryPatched = true\n\tconst origPush = window.history.pushState.bind(window.history)\n\twindow.history.pushState = (\n\t\tdata: unknown,\n\t\tunused: string,\n\t\turl?: string | URL | null,\n\t): void => {\n\t\tconst state = data as Record<string, unknown> | null\n\t\tif (state !== null && typeof state === \"object\" && HISTORY_KEY in state) {\n\t\t\t// Our own synthetic push — pass through.\n\t\t\torigPush(data, unused, url)\n\t\t\treturn\n\t\t}\n\t\t// Real navigation — close all overlays so the stack is\n\t\t// cleared synchronously (before React's async effects).\n\t\t// The synthetic entry remains in history with its marker\n\t\t// intact; the capture-phase popstate listener will\n\t\t// auto-skip it later.\n\t\tif (overlayStack.length > 0) {\n\t\t\tcloseAllOverlays()\n\t\t}\n\t\torigPush(data, unused, url)\n\t}\n}\n\n/**\n * Closes every active overlay in the stack (top-down) and clears\n * the stack. Called when a real navigation is detected so that\n * overlay state doesn't leak across route boundaries.\n */\nfunction closeAllOverlays(): void {\n\twhile (overlayStack.length > 0) {\n\t\tconst entry = overlayStack.pop()\n\t\tif (entry?.close !== undefined) {\n\t\t\tconst fn = entry.close\n\t\t\tentry.close = undefined\n\t\t\tfn()\n\t\t}\n\t}\n}\n\n/**\n * Installs popstate listeners and patches history.pushState.\n *\n * Two listeners are installed:\n *\n * 1. **Capture-phase** — runs BEFORE any bubbling-phase listeners\n * (including TanStack Router's popstate handler). Inspects the\n * state at the history pointer:\n *\n * - **Active entry** (id found in overlayStack): the back\n * gesture landed on a synthetic entry that belongs to an\n * overlay still below the top of the stack. Pop and close the\n * top overlay (the one the user is actually dismissing) and\n * block the event from reaching the router via\n * `stopImmediatePropagation()`.\n *\n * - **Phantom entry** (id in `seenSyntheticIds` but NOT in\n * overlayStack): a synthetic history slot that remains after\n * the overlay was closed via route navigation. Blocks the\n * event AND auto-skips by calling `history.back()` to advance\n * the pointer past the phantom, so the router never sees its\n * stale URL.\n *\n * 2. **Bubbling-phase** — closes the top overlay when the back\n * gesture returns to the base route state (no synthetic marker),\n * which happens for the last open overlay.\n */\nfunction ensurePopListener(): void {\n\tif (popListenerInstalled) return\n\tif (typeof window === \"undefined\") return\n\tpopListenerInstalled = true\n\n\t// Capture-phase gate: block synthetic popstate from reaching\n\t// the router (or any other bubbling-phase listener on window).\n\twindow.addEventListener(\n\t\t\"popstate\",\n\t\t(e: PopStateEvent) => {\n\t\t\tif (suppressPopCount > 0) return // let the counter handle it\n\t\t\tconst state = e.state as Record<string, unknown> | null\n\t\t\tif (state !== null && typeof state === \"object\" && HISTORY_KEY in state) {\n\t\t\t\tconst id = state[HISTORY_KEY]\n\t\t\t\t// Active entry still in the stack → the back gesture\n\t\t\t\t// landed on a synthetic entry of an overlay below the\n\t\t\t\t// top. Close the top overlay and hide the event from\n\t\t\t\t// the router.\n\t\t\t\tif (\n\t\t\t\t\ttypeof id === \"number\" &&\n\t\t\t\t\toverlayStack.some((entry) => entry.id === id)\n\t\t\t\t) {\n\t\t\t\t\tcloseTopOverlay()\n\t\t\t\t\te.stopImmediatePropagation()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Phantom entry: a synthetic history slot that\n\t\t\t\t// remains after the overlay was closed via\n\t\t\t\t// route navigation. Block the event from the\n\t\t\t\t// router AND auto-skip by calling history.back()\n\t\t\t\t// to move the pointer past the phantom.\n\t\t\t\te.stopImmediatePropagation()\n\t\t\t\tif (typeof id === \"number\" && seenSyntheticIds.has(id)) {\n\t\t\t\t\twindow.history.back()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\ttrue, // capture phase\n\t)\n\n\twindow.addEventListener(\"popstate\", handlePop)\n\tpatchHistory()\n}\n\nfunction closeTopOverlay(): void {\n\tconst top = overlayStack.pop()\n\tif (top === undefined) return\n\t// `close` may be cleared if this entry was dismissed via UI from a\n\t// non-top position; the popstate then just unwinds a stale\n\t// synthetic history slot and there is nothing to close.\n\tif (top.close === undefined) return\n\t// Clear close before calling it so the resulting effect sees\n\t// idRef as undefined (back-gesture close) and skips the\n\t// back() + replaceState() UI-close path.\n\tconst closeFn = top.close\n\ttop.close = undefined\n\tcloseFn()\n}\n\nfunction handlePop(): void {\n\tif (suppressPopCount > 0) {\n\t\tsuppressPopCount--\n\t\treturn\n\t}\n\tcloseTopOverlay()\n}\n\n/**\n * Removes the entry with the given `id` from the overlay stack.\n *\n * When the entry is **not** the top of the stack, it is \"parked\"\n * (left in place with its close callback cleared) so the synthetic\n * history slot survives until swept.\n *\n * @returns An object describing what was removed:\n * - `removed`: whether the entry was found.\n * - `parkedBelow`: how many parked (close=undefined) entries were\n * found below the removed entry. These must be unwound alongside\n * the active entry to prevent dead back-presses.\n */\nfunction removeFromStack(id: number): {\n\treadonly removed: boolean\n\treadonly parkedBelow: number\n} {\n\tconst idx = overlayStack.findIndex((e) => e.id === id)\n\tif (idx === -1) return { removed: false, parkedBelow: 0 }\n\tconst wasTop = idx === overlayStack.length - 1\n\tif (!wasTop) {\n\t\t// Park: keep the history slot but neutralise the callback.\n\t\tconst entry = overlayStack[idx]\n\t\tif (entry !== undefined) entry.close = undefined\n\t\treturn { removed: true, parkedBelow: 0 }\n\t}\n\t// Top entry — remove it and count ALL parked entries below so\n\t// their orphaned synthetic history slots are unwound in the same\n\t// batch as the active entry's slot.\n\toverlayStack.pop()\n\tconst remaining = overlayStack.splice(0, overlayStack.length)\n\tconst active = remaining.filter((e) => e.close !== undefined)\n\tconst parkedBelow = remaining.length - active.length\n\toverlayStack.push(...active)\n\treturn { removed: true, parkedBelow }\n}\n\n/**\n * Cleans up the synthetic history entry after an overlay closes via\n * UI (not back gesture, not route navigation).\n *\n * `history.back()` is asynchronous — it queues a history traversal\n * task. The popstate event fires only after the traversal completes\n * (pointer has moved to the target entry). By replacing the state\n * inside the popstate handler, we ensure:\n *\n * 1. The pointer is at the base entry (not the synthetic one).\n * 2. `replaceState` truncates forward history, eliminating the\n * phantom synthetic entry that `back()` alone would leave behind.\n *\n * **Route-change safety**: When a navigation-resolver is registered\n * (via {@link setNavigationResolver}), `scheduleStateRestore` uses\n * the router's deterministic \"navigation resolved\" signal to know\n * when `history.state` reflects the new route. When no resolver is\n * available (e.g. tests), it falls back to `requestAnimationFrame`,\n * which runs after the microtask queue where the router flushes its\n * pending `pushState` calls.\n *\n * - If `history.state` still carries our marker → normal UI close,\n * proceed with the `back()` + `replaceState()` cleanup.\n * - If `history.state` no longer carries our marker → a route\n * change has pushed a real entry on top. Skip the `back()` call\n * to avoid racing with the router's push. The capture-phase\n * popstate listener will auto-skip the stranded phantom entry.\n */\nfunction scheduleStateRestore(\n\tid: number,\n\tbaseState: unknown,\n\tparkedBelow: number,\n): void {\n\tfunction check(): void {\n\t\tconst currentState = window.history.state as Record<string, unknown> | null\n\n\t\t// The history pointer is no longer on this overlay's own\n\t\t// synthetic entry (route change, or another overlay has\n\t\t// already pushed/popped on top). Do NOT call history.back()\n\t\t// — it would race with the current top entry and navigate\n\t\t// to the wrong place. Just sweep parked entries from the\n\t\t// stack but do NOT prune `seenSyntheticIds`; any stranded\n\t\t// synthetic entries will be auto-skipped by the capture-phase\n\t\t// listener when the user navigates past them.\n\t\tif (\n\t\t\tcurrentState === null ||\n\t\t\ttypeof currentState !== \"object\" ||\n\t\t\t!(HISTORY_KEY in currentState) ||\n\t\t\tcurrentState[HISTORY_KEY] !== id\n\t\t) {\n\t\t\tsweepParkedEntries()\n\t\t\treturn\n\t\t}\n\n\t\tconst totalBacks = 1 + parkedBelow\n\t\tlet done = false\n\n\t\tfunction restore(): void {\n\t\t\tif (done) return\n\t\t\tdone = true\n\t\t\twindow.removeEventListener(\"popstate\", onPop)\n\t\t\twindow.history.replaceState(baseState, \"\")\n\t\t\t// replaceState truncates forward history, eliminating\n\t\t\t// phantom entries. Safe to prune their IDs from\n\t\t\t// `seenSyntheticIds` (excluding IDs still in the\n\t\t\t// active stack).\n\t\t\tconst sweptIds = sweepParkedEntries()\n\t\t\tcleanupSeenIds(sweptIds)\n\t\t}\n\n\t\tfunction onPop(): void {\n\t\t\tif (suppressPopCount > 0) {\n\t\t\t\tsuppressPopCount--\n\t\t\t\treturn\n\t\t\t}\n\t\t\trestore()\n\t\t}\n\n\t\twindow.addEventListener(\"popstate\", onPop)\n\n\t\tsuppressPopCount += totalBacks\n\t\tfor (let i = 0; i < totalBacks; i++) {\n\t\t\twindow.history.back()\n\t\t}\n\t}\n\n\t// Use both the router's deterministic \"navigation resolved\" signal\n\t// and a requestAnimationFrame fallback. In the common UI-close case\n\t// (e.g. clicking outside a dropdown) there is no route change, so\n\t// the resolver never fires. Without the fallback the synthetic\n\t// history entry would be left behind as a phantom.\n\tlet disposed = false\n\tlet rafId: number | undefined\n\tlet unsub: (() => void) | undefined\n\n\tfunction run(): void {\n\t\tif (disposed) return\n\t\tdisposed = true\n\t\tunsub?.()\n\t\tif (rafId !== undefined) {\n\t\t\tcancelAnimationFrame(rafId)\n\t\t}\n\t\tcheck()\n\t}\n\n\tif (navigationResolver !== undefined) {\n\t\tunsub = navigationResolver(run)\n\t}\n\n\trafId = requestAnimationFrame(run)\n}\n\n/**\n * Sweeps all parked entries (those with no close callback) from the\n * overlay stack. Called after `replaceState` truncates forward\n * history, since parked entries no longer have corresponding history\n * entries and would cause dead back-presses.\n *\n * @returns The IDs of entries that were removed, used by the caller\n * to prune `seenSyntheticIds`.\n */\nfunction sweepParkedEntries(): readonly number[] {\n\tconst sweptIds: number[] = []\n\tfor (let i = overlayStack.length - 1; i >= 0; i--) {\n\t\tconst entry = overlayStack[i]\n\t\tif (entry !== undefined && entry.close === undefined) {\n\t\t\tsweptIds.push(entry.id)\n\t\t\toverlayStack.splice(i, 1)\n\t\t}\n\t}\n\treturn sweptIds\n}\n\n/**\n * Removes swept IDs from `seenSyntheticIds` to prevent unbounded\n * growth over the lifetime of the SPA session.\n *\n * After `replaceState` truncates forward history, phantom entries\n * for swept parked overlays are eliminated. Their IDs can safely\n * be removed from `seenSyntheticIds` — unless the same ID belongs\n * to an entry still active in the stack (which happens when a\n * lower-ID entry is parked but a higher-ID entry above it is still\n * open and not swept).\n */\nfunction cleanupSeenIds(sweptIds: readonly number[]): void {\n\tconst activeIds = new Set(overlayStack.map((e) => e.id))\n\tfor (const id of sweptIds) {\n\t\tif (!activeIds.has(id)) {\n\t\t\tseenSyntheticIds.delete(id)\n\t\t}\n\t}\n}\n\n/**\n * On mobile (< sm), when an overlay (dialog/sheet/popover) opens we push a\n * synthetic history entry so the device back gesture closes it instead of\n * navigating away from the page.\n *\n * **Closing via UI** calls `history.back()` to navigate past the\n * synthetic entry, then uses `replaceState` (inside the resulting\n * popstate handler) to restore the original history state.\n *\n * **Closing via back gesture** is handled by the popstate listener,\n * which pops the stack entry and calls the close callback.\n *\n * **Closing via route navigation**: the `pushState` interceptor\n * closes all overlays synchronously but does NOT strip the synthetic\n * marker. The React effect's `scheduleStateRestore` waits for the\n * router's navigation-resolved signal (or falls back to\n * `requestAnimationFrame`); by then the router's push has completed\n * and the marker is gone from `history.state`, so `back()` is\n * skipped. The stranded synthetic entry is later auto-skipped by the\n * capture-phase popstate listener when the user navigates past it.\n *\n * Multiple overlays open at once stack: pressing back closes the most\n * recently opened one first, matching the visual stacking order.\n *\n * No-op on non-mobile viewports and when no `onOpenChange` is provided\n * (uncontrolled overlays cannot be closed externally).\n */\nexport function useMobileBackToClose(\n\topen: boolean | undefined,\n\tonOpenChange: ((open: boolean) => void) | undefined,\n): void {\n\tconst idRef = useRef<number | undefined>(undefined)\n\tconst onChangeRef = useRef(onOpenChange)\n\tonChangeRef.current = onOpenChange\n\n\tuseEffect(() => {\n\t\tif (typeof window === \"undefined\") return\n\t\tif (onChangeRef.current === undefined) return\n\t\tif (!isBelowMd()) return\n\t\tensurePopListener()\n\n\t\tif (open === true && idRef.current === undefined) {\n\t\t\tconst id = ++nextOverlayId\n\t\t\tidRef.current = id\n\t\t\tseenSyntheticIds.add(id)\n\t\t\t// Capture the base state BEFORE pushing so we can restore\n\t\t\t// it on close (via replaceState) to eliminate phantoms.\n\t\t\tconst baseState = window.history.state ?? null\n\t\t\twindow.history.pushState(\n\t\t\t\t{\n\t\t\t\t\t...((baseState as Record<string, unknown>) ?? {}),\n\t\t\t\t\t[HISTORY_KEY]: id,\n\t\t\t\t},\n\t\t\t\t\"\",\n\t\t\t)\n\t\t\toverlayStack.push({\n\t\t\t\tid,\n\t\t\t\tbaseState,\n\t\t\t\tclose() {\n\t\t\t\t\tidRef.current = undefined\n\t\t\t\t\tonChangeRef.current?.(false)\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tif (open !== true && idRef.current !== undefined) {\n\t\t\tconst id = idRef.current\n\t\t\tidRef.current = undefined\n\t\t\tconst entryIdx = overlayStack.findIndex((e) => e.id === id)\n\t\t\tconst entry = entryIdx !== -1 ? overlayStack[entryIdx] : undefined\n\t\t\tconst { removed, parkedBelow } = removeFromStack(id)\n\t\t\tif (removed && entry !== undefined) {\n\t\t\t\tscheduleStateRestore(id, entry.baseState, parkedBelow)\n\t\t\t}\n\t\t}\n\t}, [open])\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (idRef.current !== undefined) {\n\t\t\t\tconst id = idRef.current\n\t\t\t\tidRef.current = undefined\n\t\t\t\tconst entryIdx = overlayStack.findIndex((e) => e.id === id)\n\t\t\t\tconst entry = entryIdx !== -1 ? overlayStack[entryIdx] : undefined\n\t\t\t\tconst { removed, parkedBelow } = removeFromStack(id)\n\t\t\t\tif (removed && entry !== undefined) {\n\t\t\t\t\tscheduleStateRestore(id, entry.baseState, parkedBelow)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}, [])\n}\n","import * as React from \"react\"\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\"\n\nimport { cn } from \"@hoardodile/ui/lib/utils\"\nimport { useMobileBackToClose } from \"@hoardodile/ui/hooks/useMobileBackToClose\"\n\nfunction Popover({\n open,\n defaultOpen,\n onOpenChange,\n closeOnBlur = false,\n ...props\n}: Omit<PopoverPrimitive.Root.Props, \"onOpenChange\"> & {\n /**\n * `eventDetails` is absent for closes triggered by the mobile back\n * gesture (a synthetic close with no underlying Base UI event).\n */\n onOpenChange?: (\n open: boolean,\n eventDetails?: PopoverPrimitive.Root.ChangeEventDetails,\n ) => void\n} & {\n /**\n * Close the popover when the window loses focus. Needed for surfaces\n * that overlay a cross-origin iframe: Base UI closes on a parent-document\n * `pointerdown`, which a click inside a sandboxed plugin iframe never\n * reaches, so without this the plugin click leaves the popover open.\n * Opt-in so app-wide popovers keep their current behavior.\n */\n closeOnBlur?: boolean\n}) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(\n defaultOpen ?? false,\n )\n const isControlled = open !== undefined\n const currentOpen = isControlled ? open : uncontrolledOpen\n const handleOpenChangeRef = React.useRef(handleOpenChange)\n handleOpenChangeRef.current = handleOpenChange\n function handleOpenChange(\n next: boolean,\n eventDetails?: PopoverPrimitive.Root.ChangeEventDetails,\n ) {\n if (!isControlled) setUncontrolledOpen(next)\n onOpenChange?.(next, eventDetails)\n }\n useMobileBackToClose(currentOpen, handleOpenChange)\n // A sandboxed plugin iframe click moves focus into the iframe and blurs\n // the host window; that blur is the only parent-side signal for a\n // cross-origin iframe interaction, so close on it when opted in.\n React.useEffect(() => {\n if (!currentOpen || !closeOnBlur) return\n const onWindowBlur = () => handleOpenChangeRef.current(false)\n window.addEventListener(\"blur\", onWindowBlur)\n return () => window.removeEventListener(\"blur\", onWindowBlur)\n }, [currentOpen, closeOnBlur])\n return (\n <PopoverPrimitive.Root\n data-slot=\"popover\"\n open={currentOpen}\n onOpenChange={handleOpenChange}\n {...props}\n />\n )\n}\n\nfunction PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />\n}\n\nfunction PopoverContent({\n className,\n align = \"center\",\n alignOffset = 0,\n side = \"bottom\",\n sideOffset = 4,\n container,\n ...props\n}: PopoverPrimitive.Popup.Props &\n Pick<\n PopoverPrimitive.Positioner.Props,\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\n > & {\n /**\n * Override the portal target. Used by the video player so popovers\n * stay attached to the fullscreen container; otherwise they'd be\n * portalled to `document.body` and rendered behind the fullscreen\n * surface, where every tap registers as outside-click and dismisses\n * the popover immediately.\n */\n container?: PopoverPrimitive.Portal.Props[\"container\"]\n }) {\n return (\n <PopoverPrimitive.Portal container={container}>\n <PopoverPrimitive.Positioner\n align={align}\n alignOffset={alignOffset}\n side={side}\n sideOffset={sideOffset}\n className=\"isolate z-50\"\n >\n <PopoverPrimitive.Popup\n data-slot=\"popover-content\"\n className={cn(\n \"z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-md bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n className\n )}\n {...props}\n />\n </PopoverPrimitive.Positioner>\n </PopoverPrimitive.Portal>\n )\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"popover-header\"\n className={cn(\"flex flex-col gap-1 text-sm\", className)}\n {...props}\n />\n )\n}\n\nfunction PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {\n return (\n <PopoverPrimitive.Title\n data-slot=\"popover-title\"\n className={cn(\"font-medium\", className)}\n {...props}\n />\n )\n}\n\nfunction PopoverDescription({\n className,\n ...props\n}: PopoverPrimitive.Description.Props) {\n return (\n <PopoverPrimitive.Description\n data-slot=\"popover-description\"\n className={cn(\"text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nexport {\n Popover,\n PopoverContent,\n PopoverDescription,\n PopoverHeader,\n PopoverTitle,\n PopoverTrigger,\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/popover.tsx"],"names":["PopoverPrimitive"],"mappings":"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","file":"popover.js","sourcesContent":["import * as React from \"react\"\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\"\n\nimport { cn } from \"@hoardodile/ui/lib/utils\"\nimport { useMobileBackToClose } from \"@hoardodile/ui/hooks/useMobileBackToClose\"\n\nfunction Popover({\n open,\n defaultOpen,\n onOpenChange,\n closeOnBlur = false,\n ...props\n}: Omit<PopoverPrimitive.Root.Props, \"onOpenChange\"> & {\n /**\n * `eventDetails` is absent for closes triggered by the mobile back\n * gesture (a synthetic close with no underlying Base UI event).\n */\n onOpenChange?: (\n open: boolean,\n eventDetails?: PopoverPrimitive.Root.ChangeEventDetails,\n ) => void\n} & {\n /**\n * Close the popover when the window loses focus. Needed for surfaces\n * that overlay a cross-origin iframe: Base UI closes on a parent-document\n * `pointerdown`, which a click inside a sandboxed plugin iframe never\n * reaches, so without this the plugin click leaves the popover open.\n * Opt-in so app-wide popovers keep their current behavior.\n */\n closeOnBlur?: boolean\n}) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(\n defaultOpen ?? false,\n )\n const isControlled = open !== undefined\n const currentOpen = isControlled ? open : uncontrolledOpen\n const handleOpenChangeRef = React.useRef(handleOpenChange)\n handleOpenChangeRef.current = handleOpenChange\n function handleOpenChange(\n next: boolean,\n eventDetails?: PopoverPrimitive.Root.ChangeEventDetails,\n ) {\n if (!isControlled) setUncontrolledOpen(next)\n onOpenChange?.(next, eventDetails)\n }\n useMobileBackToClose(currentOpen, handleOpenChange)\n // A sandboxed plugin iframe click moves focus into the iframe and blurs\n // the host window; that blur is the only parent-side signal for a\n // cross-origin iframe interaction, so close on it when opted in.\n React.useEffect(() => {\n if (!currentOpen || !closeOnBlur) return\n const onWindowBlur = () => handleOpenChangeRef.current(false)\n window.addEventListener(\"blur\", onWindowBlur)\n return () => window.removeEventListener(\"blur\", onWindowBlur)\n }, [currentOpen, closeOnBlur])\n return (\n <PopoverPrimitive.Root\n data-slot=\"popover\"\n open={currentOpen}\n onOpenChange={handleOpenChange}\n {...props}\n />\n )\n}\n\nfunction PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />\n}\n\nfunction PopoverContent({\n className,\n align = \"center\",\n alignOffset = 0,\n side = \"bottom\",\n sideOffset = 4,\n container,\n ...props\n}: PopoverPrimitive.Popup.Props &\n Pick<\n PopoverPrimitive.Positioner.Props,\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\n > & {\n /**\n * Override the portal target. Used by the video player so popovers\n * stay attached to the fullscreen container; otherwise they'd be\n * portalled to `document.body` and rendered behind the fullscreen\n * surface, where every tap registers as outside-click and dismisses\n * the popover immediately.\n */\n container?: PopoverPrimitive.Portal.Props[\"container\"]\n }) {\n return (\n <PopoverPrimitive.Portal container={container}>\n <PopoverPrimitive.Positioner\n align={align}\n alignOffset={alignOffset}\n side={side}\n sideOffset={sideOffset}\n className=\"isolate z-50\"\n >\n <PopoverPrimitive.Popup\n data-slot=\"popover-content\"\n className={cn(\n \"z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-md bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n className\n )}\n {...props}\n />\n </PopoverPrimitive.Positioner>\n </PopoverPrimitive.Portal>\n )\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"popover-header\"\n className={cn(\"flex flex-col gap-1 text-sm\", className)}\n {...props}\n />\n )\n}\n\nfunction PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {\n return (\n <PopoverPrimitive.Title\n data-slot=\"popover-title\"\n className={cn(\"font-medium\", className)}\n {...props}\n />\n )\n}\n\nfunction PopoverDescription({\n className,\n ...props\n}: PopoverPrimitive.Description.Props) {\n return (\n <PopoverPrimitive.Description\n data-slot=\"popover-description\"\n className={cn(\"text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nexport {\n Popover,\n PopoverContent,\n PopoverDescription,\n PopoverHeader,\n PopoverTitle,\n PopoverTrigger,\n}\n"]}
|