@inshorts/glint-ui 0.5.3 → 0.6.0
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/LICENSE +13 -0
- package/README.md +3 -9
- package/dist/alert.cjs +96 -0
- package/dist/alert.cjs.map +1 -0
- package/dist/alert.d.cts +29 -0
- package/dist/alert.d.ts +29 -0
- package/dist/alert.js +10 -0
- package/dist/alert.js.map +1 -0
- package/dist/autocomplete.cjs +578 -0
- package/dist/autocomplete.cjs.map +1 -0
- package/dist/autocomplete.d.cts +56 -0
- package/dist/autocomplete.d.ts +56 -0
- package/dist/autocomplete.js +11 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/badge.cjs +89 -0
- package/dist/badge.cjs.map +1 -0
- package/dist/badge.d.cts +25 -0
- package/dist/badge.d.ts +25 -0
- package/dist/badge.js +10 -0
- package/dist/badge.js.map +1 -0
- package/dist/breadcrumb.cjs +88 -0
- package/dist/breadcrumb.cjs.map +1 -0
- package/dist/breadcrumb.d.cts +14 -0
- package/dist/breadcrumb.d.ts +14 -0
- package/dist/breadcrumb.js +8 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/button.cjs +143 -0
- package/dist/button.cjs.map +1 -0
- package/dist/button.d.cts +34 -0
- package/dist/button.d.ts +34 -0
- package/dist/button.js +9 -0
- package/dist/button.js.map +1 -0
- package/dist/card.cjs +111 -0
- package/dist/card.cjs.map +1 -0
- package/dist/card.d.cts +15 -0
- package/dist/card.d.ts +15 -0
- package/dist/card.js +18 -0
- package/dist/card.js.map +1 -0
- package/dist/checkbox.cjs +66 -0
- package/dist/checkbox.cjs.map +1 -0
- package/{src/checkbox.tsx → dist/checkbox.d.cts} +5 -22
- package/dist/checkbox.d.ts +25 -0
- package/dist/checkbox.js +10 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-43ZUFY63.js +85 -0
- package/dist/chunk-43ZUFY63.js.map +1 -0
- package/dist/chunk-67ACNS42.js +29 -0
- package/dist/chunk-67ACNS42.js.map +1 -0
- package/dist/chunk-6URKHMCF.js +76 -0
- package/dist/chunk-6URKHMCF.js.map +1 -0
- package/dist/chunk-ASU6CUTZ.js +85 -0
- package/dist/chunk-ASU6CUTZ.js.map +1 -0
- package/dist/chunk-EWSBFU54.js +53 -0
- package/dist/chunk-EWSBFU54.js.map +1 -0
- package/dist/chunk-G4ZQUSAJ.js +38 -0
- package/dist/chunk-G4ZQUSAJ.js.map +1 -0
- package/dist/chunk-GMCUXEFI.js +67 -0
- package/dist/chunk-GMCUXEFI.js.map +1 -0
- package/dist/chunk-HBBOR43A.js +85 -0
- package/dist/chunk-HBBOR43A.js.map +1 -0
- package/dist/chunk-HCA5ODCD.js +11 -0
- package/dist/chunk-HCA5ODCD.js.map +1 -0
- package/dist/chunk-HYRJYUPI.js +26 -0
- package/dist/chunk-HYRJYUPI.js.map +1 -0
- package/dist/chunk-IRCTGJQB.js +26 -0
- package/dist/chunk-IRCTGJQB.js.map +1 -0
- package/dist/chunk-JDE5XYPT.js +30 -0
- package/dist/chunk-JDE5XYPT.js.map +1 -0
- package/dist/chunk-JSJEEKR3.js +59 -0
- package/dist/chunk-JSJEEKR3.js.map +1 -0
- package/dist/chunk-KR3BCFP7.js +85 -0
- package/dist/chunk-KR3BCFP7.js.map +1 -0
- package/dist/chunk-MQT5RAQV.js +202 -0
- package/dist/chunk-MQT5RAQV.js.map +1 -0
- package/dist/chunk-MUPW2TFJ.js +102 -0
- package/dist/chunk-MUPW2TFJ.js.map +1 -0
- package/dist/chunk-PM2VE7DJ.js +100 -0
- package/dist/chunk-PM2VE7DJ.js.map +1 -0
- package/dist/chunk-PPYR3O3L.js +44 -0
- package/dist/chunk-PPYR3O3L.js.map +1 -0
- package/dist/chunk-PUTC52SI.js +98 -0
- package/dist/chunk-PUTC52SI.js.map +1 -0
- package/dist/chunk-PVK4WQJZ.js +513 -0
- package/dist/chunk-PVK4WQJZ.js.map +1 -0
- package/dist/chunk-PWAKTEXF.js +18 -0
- package/dist/chunk-PWAKTEXF.js.map +1 -0
- package/dist/chunk-QTFWORA7.js +26 -0
- package/dist/chunk-QTFWORA7.js.map +1 -0
- package/dist/chunk-RUPL2H6V.js +49 -0
- package/dist/chunk-RUPL2H6V.js.map +1 -0
- package/dist/chunk-S7PVZAPL.js +28 -0
- package/dist/chunk-S7PVZAPL.js.map +1 -0
- package/dist/chunk-SQDV2AKJ.js +49 -0
- package/dist/chunk-SQDV2AKJ.js.map +1 -0
- package/dist/chunk-T2KXVH3O.js +297 -0
- package/dist/chunk-T2KXVH3O.js.map +1 -0
- package/dist/chunk-T4WCCHJQ.js +29 -0
- package/dist/chunk-T4WCCHJQ.js.map +1 -0
- package/dist/chunk-UC3ZIQPE.js +93 -0
- package/dist/chunk-UC3ZIQPE.js.map +1 -0
- package/dist/chunk-UE7JY7VR.js +28 -0
- package/dist/chunk-UE7JY7VR.js.map +1 -0
- package/dist/chunk-WVNRFO5F.js +70 -0
- package/dist/chunk-WVNRFO5F.js.map +1 -0
- package/dist/chunk-Y5WCT6XN.js +93 -0
- package/dist/chunk-Y5WCT6XN.js.map +1 -0
- package/dist/chunk-YDICVETX.js +151 -0
- package/dist/chunk-YDICVETX.js.map +1 -0
- package/dist/chunk-YSU4VWRD.js +90 -0
- package/dist/chunk-YSU4VWRD.js.map +1 -0
- package/dist/chunk-ZFSQVMLJ.js +126 -0
- package/dist/chunk-ZFSQVMLJ.js.map +1 -0
- package/dist/chunk-ZHATD73Y.js +46 -0
- package/dist/chunk-ZHATD73Y.js.map +1 -0
- package/dist/chunk-ZLAA4DGG.js +111 -0
- package/dist/chunk-ZLAA4DGG.js.map +1 -0
- package/dist/chunk-ZP252OJZ.js +56 -0
- package/dist/chunk-ZP252OJZ.js.map +1 -0
- package/dist/clipboard.cjs +125 -0
- package/dist/clipboard.cjs.map +1 -0
- package/dist/clipboard.d.cts +52 -0
- package/dist/clipboard.d.ts +52 -0
- package/dist/clipboard.js +23 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/collapsible.cjs +94 -0
- package/dist/collapsible.cjs.map +1 -0
- package/dist/collapsible.d.cts +21 -0
- package/dist/collapsible.d.ts +21 -0
- package/dist/collapsible.js +9 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/confirm-dialog.cjs +369 -0
- package/dist/confirm-dialog.cjs.map +1 -0
- package/dist/confirm-dialog.d.cts +16 -0
- package/dist/confirm-dialog.d.ts +16 -0
- package/dist/confirm-dialog.js +13 -0
- package/dist/confirm-dialog.js.map +1 -0
- package/dist/copy-button.cjs +102 -0
- package/dist/copy-button.cjs.map +1 -0
- package/dist/copy-button.d.cts +18 -0
- package/dist/copy-button.d.ts +18 -0
- package/dist/copy-button.js +10 -0
- package/dist/copy-button.js.map +1 -0
- package/dist/data-table.cjs +140 -0
- package/dist/data-table.cjs.map +1 -0
- package/dist/data-table.d.cts +81 -0
- package/dist/data-table.d.ts +81 -0
- package/dist/data-table.js +8 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-range-picker/dateRange.cjs +138 -0
- package/dist/date-range-picker/dateRange.cjs.map +1 -0
- package/dist/date-range-picker/dateRange.d.cts +57 -0
- package/dist/date-range-picker/dateRange.d.ts +57 -0
- package/dist/date-range-picker/dateRange.js +31 -0
- package/dist/date-range-picker/dateRange.js.map +1 -0
- package/dist/date-range-picker/index.cjs +375 -0
- package/dist/date-range-picker/index.cjs.map +1 -0
- package/dist/date-range-picker/index.d.cts +18 -0
- package/dist/date-range-picker/index.d.ts +18 -0
- package/dist/date-range-picker/index.js +10 -0
- package/dist/date-range-picker/index.js.map +1 -0
- package/dist/dialogLayer.cjs +121 -0
- package/dist/dialogLayer.cjs.map +1 -0
- package/dist/dialogLayer.d.cts +11 -0
- package/dist/dialogLayer.d.ts +11 -0
- package/dist/dialogLayer.js +8 -0
- package/dist/dialogLayer.js.map +1 -0
- package/dist/feature-access-gate.cjs +160 -0
- package/dist/feature-access-gate.cjs.map +1 -0
- package/dist/feature-access-gate.d.cts +14 -0
- package/dist/feature-access-gate.d.ts +14 -0
- package/dist/feature-access-gate.js +11 -0
- package/dist/feature-access-gate.js.map +1 -0
- package/dist/field.cjs +58 -0
- package/dist/field.cjs.map +1 -0
- package/dist/field.d.cts +23 -0
- package/dist/field.d.ts +23 -0
- package/dist/field.js +8 -0
- package/dist/field.js.map +1 -0
- package/dist/image-uploader.cjs +293 -0
- package/dist/image-uploader.cjs.map +1 -0
- package/dist/image-uploader.d.cts +34 -0
- package/dist/image-uploader.d.ts +34 -0
- package/dist/image-uploader.js +12 -0
- package/dist/image-uploader.js.map +1 -0
- package/dist/index.cjs +3012 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/input.cjs +58 -0
- package/dist/input.cjs.map +1 -0
- package/dist/input.d.cts +7 -0
- package/dist/input.d.ts +7 -0
- package/dist/input.js +10 -0
- package/dist/input.js.map +1 -0
- package/dist/label.cjs +67 -0
- package/dist/label.cjs.map +1 -0
- package/dist/label.d.cts +7 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.js +9 -0
- package/dist/label.js.map +1 -0
- package/dist/modal.cjs +212 -0
- package/dist/modal.cjs.map +1 -0
- package/dist/modal.d.cts +39 -0
- package/dist/modal.d.ts +39 -0
- package/dist/modal.js +12 -0
- package/dist/modal.js.map +1 -0
- package/dist/page-header.cjs +78 -0
- package/dist/page-header.cjs.map +1 -0
- package/dist/page-header.d.cts +35 -0
- package/dist/page-header.d.ts +35 -0
- package/dist/page-header.js +8 -0
- package/dist/page-header.js.map +1 -0
- package/dist/pagination.cjs +237 -0
- package/dist/pagination.cjs.map +1 -0
- package/dist/pagination.d.cts +15 -0
- package/dist/pagination.d.ts +15 -0
- package/dist/pagination.js +11 -0
- package/dist/pagination.js.map +1 -0
- package/dist/panel-brand-mark.cjs +59 -0
- package/dist/panel-brand-mark.cjs.map +1 -0
- package/dist/panel-brand-mark.d.cts +13 -0
- package/dist/panel-brand-mark.d.ts +13 -0
- package/dist/panel-brand-mark.js +8 -0
- package/dist/panel-brand-mark.js.map +1 -0
- package/dist/panel-sign-in.cjs +256 -0
- package/dist/panel-sign-in.cjs.map +1 -0
- package/dist/panel-sign-in.d.cts +31 -0
- package/dist/panel-sign-in.d.ts +31 -0
- package/dist/panel-sign-in.js +14 -0
- package/dist/panel-sign-in.js.map +1 -0
- package/dist/segmented-control.cjs +126 -0
- package/dist/segmented-control.cjs.map +1 -0
- package/dist/segmented-control.d.cts +60 -0
- package/dist/segmented-control.d.ts +60 -0
- package/dist/segmented-control.js +9 -0
- package/dist/segmented-control.js.map +1 -0
- package/dist/select.cjs +648 -0
- package/dist/select.cjs.map +1 -0
- package/dist/select.d.cts +30 -0
- package/dist/select.d.ts +30 -0
- package/dist/select.js +12 -0
- package/dist/select.js.map +1 -0
- package/dist/separator.cjs +70 -0
- package/dist/separator.cjs.map +1 -0
- package/dist/separator.d.cts +13 -0
- package/dist/separator.d.ts +13 -0
- package/dist/separator.js +9 -0
- package/dist/separator.js.map +1 -0
- package/dist/side-drawer.cjs +321 -0
- package/dist/side-drawer.cjs.map +1 -0
- package/dist/side-drawer.d.cts +21 -0
- package/dist/side-drawer.d.ts +21 -0
- package/dist/side-drawer.js +13 -0
- package/dist/side-drawer.js.map +1 -0
- package/dist/spinner.cjs +57 -0
- package/dist/spinner.cjs.map +1 -0
- package/{src/spinner.tsx → dist/spinner.d.cts} +11 -22
- package/dist/spinner.d.ts +27 -0
- package/dist/spinner.js +8 -0
- package/dist/spinner.js.map +1 -0
- package/dist/spreadsheet-grid.cjs +238 -0
- package/dist/spreadsheet-grid.cjs.map +1 -0
- package/dist/spreadsheet-grid.d.cts +48 -0
- package/dist/spreadsheet-grid.d.ts +48 -0
- package/dist/spreadsheet-grid.js +8 -0
- package/dist/spreadsheet-grid.js.map +1 -0
- package/dist/state-panel.cjs +86 -0
- package/dist/state-panel.cjs.map +1 -0
- package/dist/state-panel.d.cts +23 -0
- package/dist/state-panel.d.ts +23 -0
- package/dist/state-panel.js +9 -0
- package/dist/state-panel.js.map +1 -0
- package/dist/switch.cjs +87 -0
- package/dist/switch.cjs.map +1 -0
- package/dist/switch.d.cts +13 -0
- package/dist/switch.d.ts +13 -0
- package/dist/switch.js +9 -0
- package/dist/switch.js.map +1 -0
- package/dist/table.cjs +139 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +16 -0
- package/dist/table.d.ts +16 -0
- package/dist/table.js +22 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.cjs +168 -0
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.d.cts +59 -0
- package/dist/tabs.d.ts +59 -0
- package/dist/tabs.js +11 -0
- package/dist/tabs.js.map +1 -0
- package/dist/textarea.cjs +66 -0
- package/dist/textarea.cjs.map +1 -0
- package/dist/textarea.d.cts +18 -0
- package/dist/textarea.d.ts +18 -0
- package/dist/textarea.js +10 -0
- package/dist/textarea.js.map +1 -0
- package/dist/utils.cjs +35 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +5 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/package.json +395 -19
- package/src/alert.tsx +0 -74
- package/src/autocomplete.tsx +0 -666
- package/src/badge.tsx +0 -60
- package/src/breadcrumb.tsx +0 -71
- package/src/button.tsx +0 -113
- package/src/card.tsx +0 -79
- package/src/clipboard.ts +0 -144
- package/src/collapsible.tsx +0 -63
- package/src/confirm-dialog.tsx +0 -84
- package/src/copy-button.tsx +0 -58
- package/src/data-table.tsx +0 -190
- package/src/date-range-picker/dateRange.ts +0 -142
- package/src/date-range-picker/index.tsx +0 -366
- package/src/dialogLayer.ts +0 -142
- package/src/feature-access-gate.tsx +0 -38
- package/src/field.tsx +0 -56
- package/src/image-uploader.tsx +0 -181
- package/src/index.ts +0 -37
- package/src/input.tsx +0 -16
- package/src/label.tsx +0 -23
- package/src/modal.tsx +0 -117
- package/src/page-header.tsx +0 -75
- package/src/pagination.tsx +0 -116
- package/src/panel-brand-mark.tsx +0 -32
- package/src/panel-sign-in.tsx +0 -134
- package/src/segmented-control.tsx +0 -155
- package/src/select.tsx +0 -105
- package/src/separator.tsx +0 -32
- package/src/side-drawer.tsx +0 -87
- package/src/spreadsheet-grid.tsx +0 -264
- package/src/state-panel.tsx +0 -46
- package/src/switch.tsx +0 -46
- package/src/table.tsx +0 -110
- package/src/tabs.tsx +0 -201
- package/src/textarea.tsx +0 -32
- package/src/utils.ts +0 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/side-drawer.tsx","../src/button.tsx","../src/spinner.tsx","../src/utils.ts","../src/dialogLayer.ts","../src/modal.tsx"],"sourcesContent":["\"use client\";\n\nimport { X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { Button } from \"./button\";\nimport { useDialogLayer } from \"./dialogLayer\";\nimport { useHasMounted } from \"./modal\";\nimport { cn } from \"./utils\";\n\ninterface SideDrawerProps {\n isOpen: boolean;\n title: string;\n onClose: () => void;\n children: React.ReactNode;\n /** Tailwind width class for the panel. Defaults to a reading-width column. */\n panelClassName?: string;\n}\n\n/**\n * A panel that slides in from the right.\n *\n * For content that belongs beside the page rather than on top of it — a queue\n * you glance at and dismiss, not a decision you have to answer. Unlike\n * `Modal`, it does not centre or dim the whole page to a stop: the table\n * underneath stays readable, which is the point of looking at both.\n */\nexport function SideDrawer({\n isOpen,\n title,\n onClose,\n children,\n panelClassName,\n}: SideDrawerProps) {\n const mounted = useHasMounted();\n const headingId = React.useId();\n const panelRef = React.useRef<HTMLElement>(null);\n\n useDialogLayer({\n // Gated on `mounted` too: before that the panel is not rendered, so there\n // would be nothing to move focus into.\n active: isOpen && mounted,\n containerRef: panelRef,\n onEscape: onClose,\n });\n\n if (!mounted || !isOpen) return null;\n\n return createPortal(\n <>\n <div\n aria-hidden\n onClick={onClose}\n className=\"fixed inset-0 z-[90] bg-slate-950/30\"\n />\n <aside\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={headingId}\n tabIndex={-1}\n className={cn(\n \"border-border bg-background fixed inset-y-0 right-0 z-[95] flex flex-col border-l shadow-2xl outline-none\",\n panelClassName ?? \"w-full max-w-md\",\n )}\n >\n <div className=\"border-border flex items-center justify-between gap-3 border-b px-4 py-3\">\n <h2 id={headingId} className=\"truncate text-base font-semibold\">\n {title}\n </h2>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Close panel\"\n onClick={onClose}\n >\n <X className=\"h-4 w-4\" aria-hidden />\n </Button>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto p-4\">{children}</div>\n </aside>\n </>,\n document.body,\n );\n}\n","import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Slot } from \"@radix-ui/react-slot\";\n\nimport { Spinner } from \"./spinner\";\nimport { cn } from \"./utils\";\n\nconst buttonVariants = cva(\n \"inline-flex h-9 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md px-3 text-sm font-medium transition duration-200 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n secondary: \"bg-muted text-foreground hover:bg-muted/80\",\n ghost: \"hover:bg-muted\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n outline: \"border border-border bg-background hover:bg-muted\",\n },\n size: {\n sm: \"h-8 px-2.5\",\n md: \"h-9 px-3\",\n lg: \"h-10 px-4\",\n icon: \"h-9 w-9 px-0\",\n // Square, matching `sm`'s height — for an icon-only control sitting in\n // a table row or beside `sm` buttons, where `icon` is a row taller.\n \"icon-sm\": \"h-8 w-8 px-0\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n /**\n * Work is in flight: shows a spinner, disables the button, and sets\n * `aria-busy`.\n *\n * This exists because the panel had 272 disabled buttons and only 22 that\n * said anything about why — the rest went dead on click with no spinner and\n * no label change, which reads as a broken button rather than a busy one.\n * Making the feedback part of `disabled` rather than something each caller\n * remembers separately is the only way that ratio stays fixed.\n *\n * The label is kept rather than swapped. \"Saving…\" loses which button you\n * pressed, and the spinner already says it is working. An icon-only button\n * is the exception — there is no room beside the glyph, so the spinner takes\n * its place; the `aria-label` is what names the button either way.\n *\n * \"Icon-only\" means an icon `size`. A square button that grew a spinner\n * beside its glyph would not just look wrong, it would resize mid-request\n * and shove whatever sits next to it sideways.\n */\n loading?: boolean;\n}\n\n/** Sizes with no room for a spinner beside the glyph. */\nconst ICON_SIZES = new Set<string>([\"icon\", \"icon-sm\"]);\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant,\n size,\n asChild = false,\n loading,\n disabled,\n children,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n\n // `asChild` renders someone else's element, which may not take a spinner or\n // a disabled attribute — so the busy treatment is only applied to a real\n // button, and passing both is a mistake worth not papering over.\n if (asChild) {\n return (\n <Comp\n ref={ref}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n >\n {children}\n </Comp>\n );\n }\n\n return (\n <button\n ref={ref}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n >\n {loading ? <Spinner size=\"sm\" /> : null}\n {loading && size != null && ICON_SIZES.has(size) ? null : children}\n </button>\n );\n },\n);\n\nButton.displayName = \"Button\";\n","import * as React from \"react\";\nimport { Loader2 } from \"lucide-react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The one spinner.\n *\n * There were five spellings of `animate-spin` across twelve files — `h-4 w-4`,\n * `h-5 w-5`, `size-4`, bare, and a hand-drawn ring in `ProtectedRoute` — so the\n * same \"working\" signal came out a different size depending on which panel you\n * were on.\n *\n * It is `aria-hidden` on purpose. A spinner is decoration; what a screen reader\n * needs is the surrounding control's `aria-busy` or the panel's status text,\n * and announcing \"loading\" twice is worse than once. `Button` and `StatePanel`\n * both supply that.\n */\nconst SIZES = {\n sm: \"size-3.5\",\n md: \"size-4\",\n lg: \"size-5\",\n xl: \"size-10 border-2\",\n} as const;\n\nexport interface SpinnerProps extends React.SVGProps<SVGSVGElement> {\n size?: keyof typeof SIZES;\n}\n\nexport function Spinner({ size = \"md\", className, ...props }: SpinnerProps) {\n return (\n <Loader2\n aria-hidden\n className={cn(\"animate-spin\", SIZES[size], className)}\n {...props}\n />\n );\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * The keyboard and focus behaviour every dialog needs, in one place.\n *\n * `Modal` and `SideDrawer` each had a copy of the scroll lock and an Escape\n * handler, and neither did anything about focus: opening a dialog left focus\n * on the button behind it, Tab walked the page underneath — through the\n * full-screen backdrop button, among other things — and closing left focus\n * wherever it had wandered. That is the whole keyboard story for thirty\n * dialogs, so it is fixed once here rather than thirty times.\n */\n\n/**\n * Open dialogs, innermost last.\n *\n * Nesting is real here — \"Create audience group\" opens on top of \"Send\n * notification\" — and without an ordering both would answer the same Escape\n * and fight over the same Tab. Only the top layer acts.\n */\nconst layers: symbol[] = [];\n\nconst FOCUSABLE = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\",\");\n\n/**\n * Deliberately not `offsetParent` or `getClientRects`: the first is null for\n * anything `position: fixed`, which a dialog panel usually is, and the second\n * is empty for *everything* under a test renderer that does no layout — which\n * would collapse the trap to \"no focusable elements\" in every test.\n */\nfunction isVisible(element: HTMLElement): boolean {\n if (element.closest(\"[hidden]\")) return false;\n if (element.getAttribute(\"aria-hidden\") === \"true\") return false;\n return typeof element.checkVisibility === \"function\"\n ? element.checkVisibility()\n : true;\n}\n\nfunction focusableWithin(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n isVisible,\n );\n}\n\nexport function useDialogLayer({\n active,\n containerRef,\n onEscape,\n}: {\n active: boolean;\n /** The panel. Focus is moved into it and kept inside it. */\n containerRef: React.RefObject<HTMLElement | null>;\n /** Called on Escape, only while this is the top layer. Omit to ignore it. */\n onEscape?: () => void;\n}): void {\n // Read through a ref so a caller passing a fresh closure every render does\n // not tear the listener down and rebuild it on every keystroke.\n const escapeRef = React.useRef(onEscape);\n React.useEffect(() => {\n escapeRef.current = onEscape;\n });\n\n React.useEffect(() => {\n if (!active) return;\n\n const layer = Symbol(\"dialog-layer\");\n layers.push(layer);\n const isTopLayer = () => layers[layers.length - 1] === layer;\n\n const previouslyFocused =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n\n const container = containerRef.current;\n // Something inside may have claimed focus already — an `autoFocus` input —\n // and that choice is better than any this could make.\n if (container && !container.contains(document.activeElement)) {\n const [first] = focusableWithin(container);\n // The panel itself, not its first control: the first control is a Delete\n // button often enough that focusing it would put a destructive action\n // one Enter away from an accidental keypress.\n (container.hasAttribute(\"tabindex\") ? container : first)?.focus();\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (!isTopLayer()) return;\n\n if (event.key === \"Escape\") {\n escapeRef.current?.();\n return;\n }\n\n if (event.key !== \"Tab\") return;\n const panel = containerRef.current;\n if (!panel) return;\n\n const items = focusableWithin(panel);\n if (items.length === 0) {\n event.preventDefault();\n panel.focus();\n return;\n }\n\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = document.activeElement;\n const outside = !panel.contains(active);\n\n if (event.shiftKey && (active === first || outside)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || outside)) {\n event.preventDefault();\n first.focus();\n }\n }\n\n document.addEventListener(\"keydown\", handleKeyDown);\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n document.body.style.overflow = previousOverflow;\n const index = layers.indexOf(layer);\n if (index !== -1) layers.splice(index, 1);\n // Back where it came from, so closing a dialog opened by keyboard does\n // not drop the caret at the top of the document.\n if (previouslyFocused?.isConnected) previouslyFocused.focus();\n };\n }, [active, containerRef]);\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { useDialogLayer } from \"./dialogLayer\";\nimport { cn } from \"./utils\";\n\nconst emptySubscribe = () => () => {};\n\n/**\n * Returns false during SSR and the first client render, then true once mounted.\n * `useSyncExternalStore` is the React-recommended way to read client-only state\n * without a setState-in-effect (which triggers cascading renders).\n */\nexport function useHasMounted(): boolean {\n return React.useSyncExternalStore(\n emptySubscribe,\n () => true,\n () => false,\n );\n}\n\ninterface ModalProps {\n isOpen: boolean;\n /** id of the element labelling the dialog; wired to aria-labelledby. */\n labelledBy: string;\n /**\n * Optional id(s) of elements describing the dialog (APG alertdialog body).\n * Space-separated when multiple.\n */\n describedBy?: string;\n /** Accessible label for the backdrop dismiss button. */\n closeLabel: string;\n onClose: () => void;\n children: React.ReactNode;\n /**\n * When false, Escape and backdrop clicks do not close the modal (e.g. while a\n * save is in flight). Defaults to true.\n */\n dismissible?: boolean;\n /** Use \"alertdialog\" for destructive confirmations. Defaults to \"dialog\". */\n role?: \"dialog\" | \"alertdialog\";\n /** Tailwind max-width class for the panel. Defaults to \"max-w-md\". */\n panelClassName?: string;\n}\n\n/**\n * Shared modal shell: renders a portal-backed, scroll-locked, Escape-dismissible\n * overlay with a centered panel. Callers provide the panel body (heading, form,\n * footer). Consolidates the boilerplate previously copied across every dialog.\n */\nexport function Modal({\n isOpen,\n labelledBy,\n describedBy,\n closeLabel,\n onClose,\n children,\n dismissible = true,\n role = \"dialog\",\n panelClassName,\n}: ModalProps) {\n const mounted = useHasMounted();\n const panelRef = React.useRef<HTMLDivElement>(null);\n\n useDialogLayer({\n // Gated on `mounted` too: before that the panel is not rendered, so there\n // would be nothing to move focus into.\n active: isOpen && mounted,\n containerRef: panelRef,\n onEscape: dismissible ? onClose : undefined,\n });\n\n if (!mounted || !isOpen) return null;\n\n return createPortal(\n /*\n `overflow-y-auto` on the overlay, `my-auto` on the panel — not\n `items-center`. A flex item centred with `align-items` overflows equally\n in both directions once it is taller than its container, and the half\n above the top edge cannot be scrolled to: with the page behind it locked,\n an over-tall dialog was simply unreachable at both ends. Auto margins\n centre a short panel the same way and collapse to zero on a tall one,\n which scrolls.\n */\n <div className=\"fixed inset-0 z-[100] flex justify-center overflow-y-auto p-4\">\n {/* `fixed`, not `absolute`: inside a scrolling overlay an absolute\n backdrop scrolls away with the content and leaves the rest of a tall\n dialog sitting on an undimmed page. */}\n <button\n type=\"button\"\n aria-label={closeLabel}\n tabIndex={-1}\n className=\"fixed inset-0 bg-slate-950/50\"\n onClick={() => {\n if (dismissible) onClose();\n }}\n />\n <div\n ref={panelRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={labelledBy}\n aria-describedby={describedBy}\n tabIndex={-1}\n className={cn(\n \"border-border bg-background relative z-10 my-auto w-full rounded-lg border shadow-2xl outline-none\",\n panelClassName ?? \"max-w-md p-6\",\n )}\n >\n {children}\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,IAAAA,uBAAkB;AAClB,IAAAC,SAAuB;AACvB,IAAAC,oBAA6B;;;ACJ7B,YAAuB;AACvB,sCAAuC;AACvC,wBAAqB;;;ACDrB,0BAAwB;;;ACDxB,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;AD0BI;AAbJ,IAAM,QAAQ;AAAA,EACZ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAMO,SAAS,QAAQ,EAAE,OAAO,MAAM,WAAW,GAAG,MAAM,GAAiB;AAC1E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,MACX,WAAW,GAAG,gBAAgB,MAAM,IAAI,GAAG,SAAS;AAAA,MACnD,GAAG;AAAA;AAAA,EACN;AAEJ;;;ADkDQ,IAAAC,sBAAA;AAhFR,IAAM,qBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,WAAW;AAAA,QACX,OAAO;AAAA,QACP,aACE;AAAA,QACF,SAAS;AAAA,MACX;AAAA,MACA,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA;AAAA;AAAA,QAGN,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AA8BA,IAAM,aAAa,oBAAI,IAAY,CAAC,QAAQ,SAAS,CAAC;AAE/C,IAAM,SAAe;AAAA,EAC1B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,OAAO,UAAU,yBAAO;AAK9B,QAAI,SAAS;AACX,aACE;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,WAAW,GAAG,eAAe,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AAAA,UACzD,GAAG;AAAA,UAEH;AAAA;AAAA,MACH;AAAA,IAEJ;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,UAAU,YAAY;AAAA,QACtB,aAAW,WAAW;AAAA,QACtB,WAAW,GAAG,eAAe,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AAAA,QACzD,GAAG;AAAA,QAEH;AAAA,oBAAU,6CAAC,WAAQ,MAAK,MAAK,IAAK;AAAA,UAClC,WAAW,QAAQ,QAAQ,WAAW,IAAI,IAAI,IAAI,OAAO;AAAA;AAAA;AAAA,IAC5D;AAAA,EAEJ;AACF;AAEA,OAAO,cAAc;;;AG9GrB,IAAAC,SAAuB;AAoBvB,IAAM,SAAmB,CAAC;AAE1B,IAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAQV,SAAS,UAAU,SAA+B;AAChD,MAAI,QAAQ,QAAQ,UAAU,EAAG,QAAO;AACxC,MAAI,QAAQ,aAAa,aAAa,MAAM,OAAQ,QAAO;AAC3D,SAAO,OAAO,QAAQ,oBAAoB,aACtC,QAAQ,gBAAgB,IACxB;AACN;AAEA,SAAS,gBAAgB,WAAuC;AAC9D,SAAO,MAAM,KAAK,UAAU,iBAA8B,SAAS,CAAC,EAAE;AAAA,IACpE;AAAA,EACF;AACF;AAEO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAMS;AAGP,QAAM,YAAkB,cAAO,QAAQ;AACvC,EAAM,iBAAU,MAAM;AACpB,cAAU,UAAU;AAAA,EACtB,CAAC;AAED,EAAM,iBAAU,MAAM;AACpB,QAAI,CAAC,OAAQ;AAEb,UAAM,QAAQ,uBAAO,cAAc;AACnC,WAAO,KAAK,KAAK;AACjB,UAAM,aAAa,MAAM,OAAO,OAAO,SAAS,CAAC,MAAM;AAEvD,UAAM,oBACJ,SAAS,yBAAyB,cAC9B,SAAS,gBACT;AAEN,UAAM,YAAY,aAAa;AAG/B,QAAI,aAAa,CAAC,UAAU,SAAS,SAAS,aAAa,GAAG;AAC5D,YAAM,CAAC,KAAK,IAAI,gBAAgB,SAAS;AAIzC,OAAC,UAAU,aAAa,UAAU,IAAI,YAAY,QAAQ,MAAM;AAAA,IAClE;AAEA,aAAS,cAAc,OAAsB;AAC3C,UAAI,CAAC,WAAW,EAAG;AAEnB,UAAI,MAAM,QAAQ,UAAU;AAC1B,kBAAU,UAAU;AACpB;AAAA,MACF;AAEA,UAAI,MAAM,QAAQ,MAAO;AACzB,YAAM,QAAQ,aAAa;AAC3B,UAAI,CAAC,MAAO;AAEZ,YAAM,QAAQ,gBAAgB,KAAK;AACnC,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,eAAe;AACrB,cAAM,MAAM;AACZ;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,YAAMC,UAAS,SAAS;AACxB,YAAM,UAAU,CAAC,MAAM,SAASA,OAAM;AAEtC,UAAI,MAAM,aAAaA,YAAW,SAAS,UAAU;AACnD,cAAM,eAAe;AACrB,aAAK,MAAM;AAAA,MACb,WAAW,CAAC,MAAM,aAAaA,YAAW,QAAQ,UAAU;AAC1D,cAAM,eAAe;AACrB,cAAM,MAAM;AAAA,MACd;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,aAAa;AAClD,UAAM,mBAAmB,SAAS,KAAK,MAAM;AAC7C,aAAS,KAAK,MAAM,WAAW;AAE/B,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,aAAa;AACrD,eAAS,KAAK,MAAM,WAAW;AAC/B,YAAM,QAAQ,OAAO,QAAQ,KAAK;AAClC,UAAI,UAAU,GAAI,QAAO,OAAO,OAAO,CAAC;AAGxC,UAAI,mBAAmB,YAAa,mBAAkB,MAAM;AAAA,IAC9D;AAAA,EACF,GAAG,CAAC,QAAQ,YAAY,CAAC;AAC3B;;;AC3IA,IAAAC,SAAuB;AACvB,uBAA6B;AAmFzB,IAAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA;AAAA;AA9EJ,IAAM,iBAAiB,MAAM,MAAM;AAAC;AAO7B,SAAS,gBAAyB;AACvC,SAAa;AAAA,IACX;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACF;;;AL6BI,IAAAC,sBAAA;AAtBG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAoB;AAClB,QAAM,UAAU,cAAc;AAC9B,QAAM,YAAkB,aAAM;AAC9B,QAAM,WAAiB,cAAoB,IAAI;AAE/C,iBAAe;AAAA;AAAA;AAAA,IAGb,QAAQ,UAAU;AAAA,IAClB,cAAc;AAAA,IACd,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,CAAC,WAAW,CAAC,OAAQ,QAAO;AAEhC,aAAO;AAAA,IACL,8EACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,eAAW;AAAA,UACX,SAAS;AAAA,UACT,WAAU;AAAA;AAAA,MACZ;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAW;AAAA,UACX,mBAAiB;AAAA,UACjB,UAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA,kBAAkB;AAAA,UACpB;AAAA,UAEA;AAAA,0DAAC,SAAI,WAAU,4EACb;AAAA,2DAAC,QAAG,IAAI,WAAW,WAAU,oCAC1B,iBACH;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,cAAW;AAAA,kBACX,SAAS;AAAA,kBAET,uDAAC,0BAAE,WAAU,WAAU,eAAW,MAAC;AAAA;AAAA,cACrC;AAAA,eACF;AAAA,YAEA,6CAAC,SAAI,WAAU,sCAAsC,UAAS;AAAA;AAAA;AAAA,MAChE;AAAA,OACF;AAAA,IACA,SAAS;AAAA,EACX;AACF;","names":["import_lucide_react","React","import_react_dom","import_jsx_runtime","React","active","React","import_jsx_runtime","import_jsx_runtime"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface SideDrawerProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
title: string;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
/** Tailwind width class for the panel. Defaults to a reading-width column. */
|
|
9
|
+
panelClassName?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A panel that slides in from the right.
|
|
13
|
+
*
|
|
14
|
+
* For content that belongs beside the page rather than on top of it — a queue
|
|
15
|
+
* you glance at and dismiss, not a decision you have to answer. Unlike
|
|
16
|
+
* `Modal`, it does not centre or dim the whole page to a stop: the table
|
|
17
|
+
* underneath stays readable, which is the point of looking at both.
|
|
18
|
+
*/
|
|
19
|
+
declare function SideDrawer({ isOpen, title, onClose, children, panelClassName, }: SideDrawerProps): React.ReactPortal | null;
|
|
20
|
+
|
|
21
|
+
export { SideDrawer };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface SideDrawerProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
title: string;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
/** Tailwind width class for the panel. Defaults to a reading-width column. */
|
|
9
|
+
panelClassName?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A panel that slides in from the right.
|
|
13
|
+
*
|
|
14
|
+
* For content that belongs beside the page rather than on top of it — a queue
|
|
15
|
+
* you glance at and dismiss, not a decision you have to answer. Unlike
|
|
16
|
+
* `Modal`, it does not centre or dim the whole page to a stop: the table
|
|
17
|
+
* underneath stays readable, which is the point of looking at both.
|
|
18
|
+
*/
|
|
19
|
+
declare function SideDrawer({ isOpen, title, onClose, children, panelClassName, }: SideDrawerProps): React.ReactPortal | null;
|
|
20
|
+
|
|
21
|
+
export { SideDrawer };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
SideDrawer
|
|
4
|
+
} from "./chunk-43ZUFY63.js";
|
|
5
|
+
import "./chunk-YSU4VWRD.js";
|
|
6
|
+
import "./chunk-HBBOR43A.js";
|
|
7
|
+
import "./chunk-KR3BCFP7.js";
|
|
8
|
+
import "./chunk-UE7JY7VR.js";
|
|
9
|
+
import "./chunk-HCA5ODCD.js";
|
|
10
|
+
export {
|
|
11
|
+
SideDrawer
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=side-drawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/spinner.cjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/spinner.tsx
|
|
21
|
+
var spinner_exports = {};
|
|
22
|
+
__export(spinner_exports, {
|
|
23
|
+
Spinner: () => Spinner
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(spinner_exports);
|
|
26
|
+
var import_lucide_react = require("lucide-react");
|
|
27
|
+
|
|
28
|
+
// src/utils.ts
|
|
29
|
+
var import_clsx = require("clsx");
|
|
30
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
31
|
+
function cn(...inputs) {
|
|
32
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/spinner.tsx
|
|
36
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
37
|
+
var SIZES = {
|
|
38
|
+
sm: "size-3.5",
|
|
39
|
+
md: "size-4",
|
|
40
|
+
lg: "size-5",
|
|
41
|
+
xl: "size-10 border-2"
|
|
42
|
+
};
|
|
43
|
+
function Spinner({ size = "md", className, ...props }) {
|
|
44
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
45
|
+
import_lucide_react.Loader2,
|
|
46
|
+
{
|
|
47
|
+
"aria-hidden": true,
|
|
48
|
+
className: cn("animate-spin", SIZES[size], className),
|
|
49
|
+
...props
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
54
|
+
0 && (module.exports = {
|
|
55
|
+
Spinner
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=spinner.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/spinner.tsx","../src/utils.ts"],"sourcesContent":["import * as React from \"react\";\nimport { Loader2 } from \"lucide-react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The one spinner.\n *\n * There were five spellings of `animate-spin` across twelve files — `h-4 w-4`,\n * `h-5 w-5`, `size-4`, bare, and a hand-drawn ring in `ProtectedRoute` — so the\n * same \"working\" signal came out a different size depending on which panel you\n * were on.\n *\n * It is `aria-hidden` on purpose. A spinner is decoration; what a screen reader\n * needs is the surrounding control's `aria-busy` or the panel's status text,\n * and announcing \"loading\" twice is worse than once. `Button` and `StatePanel`\n * both supply that.\n */\nconst SIZES = {\n sm: \"size-3.5\",\n md: \"size-4\",\n lg: \"size-5\",\n xl: \"size-10 border-2\",\n} as const;\n\nexport interface SpinnerProps extends React.SVGProps<SVGSVGElement> {\n size?: keyof typeof SIZES;\n}\n\nexport function Spinner({ size = \"md\", className, ...props }: SpinnerProps) {\n return (\n <Loader2\n aria-hidden\n className={cn(\"animate-spin\", SIZES[size], className)}\n {...props}\n />\n );\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,0BAAwB;;;ACDxB,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;AD0BI;AAbJ,IAAM,QAAQ;AAAA,EACZ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAMO,SAAS,QAAQ,EAAE,OAAO,MAAM,WAAW,GAAG,MAAM,GAAiB;AAC1E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,MACX,WAAW,GAAG,gBAAgB,MAAM,IAAI,GAAG,SAAS;AAAA,MACnD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { Loader2 } from "lucide-react";
|
|
3
|
-
|
|
4
|
-
import { cn } from "./utils";
|
|
1
|
+
import * as React from 'react';
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* The one spinner.
|
|
@@ -16,23 +13,15 @@ import { cn } from "./utils";
|
|
|
16
13
|
* and announcing "loading" twice is worse than once. `Button` and `StatePanel`
|
|
17
14
|
* both supply that.
|
|
18
15
|
*/
|
|
19
|
-
const SIZES
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
size?: keyof typeof SIZES;
|
|
16
|
+
declare const SIZES: {
|
|
17
|
+
readonly sm: "size-3.5";
|
|
18
|
+
readonly md: "size-4";
|
|
19
|
+
readonly lg: "size-5";
|
|
20
|
+
readonly xl: "size-10 border-2";
|
|
21
|
+
};
|
|
22
|
+
interface SpinnerProps extends React.SVGProps<SVGSVGElement> {
|
|
23
|
+
size?: keyof typeof SIZES;
|
|
28
24
|
}
|
|
25
|
+
declare function Spinner({ size, className, ...props }: SpinnerProps): React.JSX.Element;
|
|
29
26
|
|
|
30
|
-
export
|
|
31
|
-
return (
|
|
32
|
-
<Loader2
|
|
33
|
-
aria-hidden
|
|
34
|
-
className={cn("animate-spin", SIZES[size], className)}
|
|
35
|
-
{...props}
|
|
36
|
-
/>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
27
|
+
export { Spinner, type SpinnerProps };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one spinner.
|
|
5
|
+
*
|
|
6
|
+
* There were five spellings of `animate-spin` across twelve files — `h-4 w-4`,
|
|
7
|
+
* `h-5 w-5`, `size-4`, bare, and a hand-drawn ring in `ProtectedRoute` — so the
|
|
8
|
+
* same "working" signal came out a different size depending on which panel you
|
|
9
|
+
* were on.
|
|
10
|
+
*
|
|
11
|
+
* It is `aria-hidden` on purpose. A spinner is decoration; what a screen reader
|
|
12
|
+
* needs is the surrounding control's `aria-busy` or the panel's status text,
|
|
13
|
+
* and announcing "loading" twice is worse than once. `Button` and `StatePanel`
|
|
14
|
+
* both supply that.
|
|
15
|
+
*/
|
|
16
|
+
declare const SIZES: {
|
|
17
|
+
readonly sm: "size-3.5";
|
|
18
|
+
readonly md: "size-4";
|
|
19
|
+
readonly lg: "size-5";
|
|
20
|
+
readonly xl: "size-10 border-2";
|
|
21
|
+
};
|
|
22
|
+
interface SpinnerProps extends React.SVGProps<SVGSVGElement> {
|
|
23
|
+
size?: keyof typeof SIZES;
|
|
24
|
+
}
|
|
25
|
+
declare function Spinner({ size, className, ...props }: SpinnerProps): React.JSX.Element;
|
|
26
|
+
|
|
27
|
+
export { Spinner, type SpinnerProps };
|
package/dist/spinner.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
"use client";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// src/spreadsheet-grid.tsx
|
|
33
|
+
var spreadsheet_grid_exports = {};
|
|
34
|
+
__export(spreadsheet_grid_exports, {
|
|
35
|
+
SpreadsheetGrid: () => SpreadsheetGrid
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(spreadsheet_grid_exports);
|
|
38
|
+
var React = __toESM(require("react"), 1);
|
|
39
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
+
function isInRange(range, r, c) {
|
|
41
|
+
if (!range) return false;
|
|
42
|
+
return r >= range.r1 && r <= range.r2 && c >= range.c1 && c <= range.c2;
|
|
43
|
+
}
|
|
44
|
+
function SpreadsheetGrid({
|
|
45
|
+
columns,
|
|
46
|
+
rows,
|
|
47
|
+
rowKey,
|
|
48
|
+
loading,
|
|
49
|
+
emptyMessage = "Nothing to show.",
|
|
50
|
+
loadingMessage = "Loading\u2026",
|
|
51
|
+
ariaLabel = "Data grid",
|
|
52
|
+
range,
|
|
53
|
+
onRangeChange,
|
|
54
|
+
onCopy
|
|
55
|
+
}) {
|
|
56
|
+
const gridRef = React.useRef(null);
|
|
57
|
+
const anchorRef = React.useRef(null);
|
|
58
|
+
const focusRef = React.useRef(null);
|
|
59
|
+
const [dragging, setDragging] = React.useState(false);
|
|
60
|
+
const setSelection = React.useCallback(
|
|
61
|
+
(anchor, focus) => {
|
|
62
|
+
anchorRef.current = anchor;
|
|
63
|
+
focusRef.current = focus;
|
|
64
|
+
onRangeChange({ anchor, focus });
|
|
65
|
+
},
|
|
66
|
+
[onRangeChange]
|
|
67
|
+
);
|
|
68
|
+
const selectCell = React.useCallback(
|
|
69
|
+
(r, c, extend) => {
|
|
70
|
+
if (extend && anchorRef.current)
|
|
71
|
+
setSelection(anchorRef.current, { r, c });
|
|
72
|
+
else setSelection({ r, c }, { r, c });
|
|
73
|
+
},
|
|
74
|
+
[setSelection]
|
|
75
|
+
);
|
|
76
|
+
const selectColumn = React.useCallback(
|
|
77
|
+
(c, extend) => {
|
|
78
|
+
const lastRow = Math.max(rows.length - 1, 0);
|
|
79
|
+
const anchorCol = extend && anchorRef.current ? anchorRef.current.c : c;
|
|
80
|
+
setSelection({ r: 0, c: anchorCol }, { r: lastRow, c });
|
|
81
|
+
},
|
|
82
|
+
[rows.length, setSelection]
|
|
83
|
+
);
|
|
84
|
+
const selectRow = React.useCallback(
|
|
85
|
+
(r, extend) => {
|
|
86
|
+
const lastCol = columns.length - 1;
|
|
87
|
+
const anchorRow = extend && anchorRef.current ? anchorRef.current.r : r;
|
|
88
|
+
setSelection({ r: anchorRow, c: 0 }, { r, c: lastCol });
|
|
89
|
+
},
|
|
90
|
+
[columns.length, setSelection]
|
|
91
|
+
);
|
|
92
|
+
const selectAll = React.useCallback(() => {
|
|
93
|
+
if (!rows.length || !columns.length) return;
|
|
94
|
+
setSelection({ r: 0, c: 0 }, { r: rows.length - 1, c: columns.length - 1 });
|
|
95
|
+
}, [rows.length, columns.length, setSelection]);
|
|
96
|
+
const scrollCellIntoView = React.useCallback((r, c) => {
|
|
97
|
+
const cell = gridRef.current?.querySelector(`[data-cell="${r}-${c}"]`);
|
|
98
|
+
cell?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
99
|
+
}, []);
|
|
100
|
+
const moveFocus = React.useCallback(
|
|
101
|
+
(dr, dc, extend) => {
|
|
102
|
+
if (!rows.length || !columns.length) return;
|
|
103
|
+
const base = focusRef.current || { r: 0, c: 0 };
|
|
104
|
+
const r = Math.min(Math.max(base.r + dr, 0), rows.length - 1);
|
|
105
|
+
const c = Math.min(Math.max(base.c + dc, 0), columns.length - 1);
|
|
106
|
+
selectCell(r, c, extend);
|
|
107
|
+
scrollCellIntoView(r, c);
|
|
108
|
+
},
|
|
109
|
+
[rows.length, columns.length, selectCell, scrollCellIntoView]
|
|
110
|
+
);
|
|
111
|
+
React.useEffect(() => {
|
|
112
|
+
if (!dragging) return;
|
|
113
|
+
const stop = () => setDragging(false);
|
|
114
|
+
window.addEventListener("mouseup", stop);
|
|
115
|
+
return () => window.removeEventListener("mouseup", stop);
|
|
116
|
+
}, [dragging]);
|
|
117
|
+
const handleKeyDown = (event) => {
|
|
118
|
+
const meta = event.metaKey || event.ctrlKey;
|
|
119
|
+
if (meta && event.key.toLowerCase() === "c") {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
onCopy();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (meta && event.key.toLowerCase() === "a") {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
selectAll();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
switch (event.key) {
|
|
130
|
+
case "ArrowUp":
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
moveFocus(-1, 0, event.shiftKey);
|
|
133
|
+
break;
|
|
134
|
+
case "ArrowDown":
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
moveFocus(1, 0, event.shiftKey);
|
|
137
|
+
break;
|
|
138
|
+
case "ArrowLeft":
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
moveFocus(0, -1, event.shiftKey);
|
|
141
|
+
break;
|
|
142
|
+
case "ArrowRight":
|
|
143
|
+
case "Tab":
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
moveFocus(0, 1, event.shiftKey && event.key !== "Tab");
|
|
146
|
+
break;
|
|
147
|
+
case "Escape":
|
|
148
|
+
setSelection(null, null);
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
155
|
+
"div",
|
|
156
|
+
{
|
|
157
|
+
ref: gridRef,
|
|
158
|
+
tabIndex: 0,
|
|
159
|
+
role: "grid",
|
|
160
|
+
"aria-label": ariaLabel,
|
|
161
|
+
onKeyDown: handleKeyDown,
|
|
162
|
+
className: "border-border bg-background/80 overflow-x-auto rounded-lg border outline-none",
|
|
163
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "w-full text-sm whitespace-nowrap select-none", children: [
|
|
164
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { className: "text-muted-foreground border-border border-b text-left", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
166
|
+
"th",
|
|
167
|
+
{
|
|
168
|
+
scope: "col",
|
|
169
|
+
className: "w-10 cursor-pointer px-2 py-2 text-center font-medium",
|
|
170
|
+
title: "Select everything",
|
|
171
|
+
onMouseDown: () => selectAll(),
|
|
172
|
+
children: "#"
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
columns.map((column, c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
176
|
+
"th",
|
|
177
|
+
{
|
|
178
|
+
scope: "col",
|
|
179
|
+
className: "cursor-pointer px-3 py-2 font-medium",
|
|
180
|
+
title: `Select the ${column.title} column`,
|
|
181
|
+
onMouseDown: (event) => selectColumn(c, event.shiftKey),
|
|
182
|
+
children: column.title
|
|
183
|
+
},
|
|
184
|
+
column.key
|
|
185
|
+
))
|
|
186
|
+
] }) }),
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
188
|
+
"td",
|
|
189
|
+
{
|
|
190
|
+
colSpan: columns.length + 1,
|
|
191
|
+
className: "text-muted-foreground px-3 py-6 text-center",
|
|
192
|
+
children: loading ? loadingMessage : emptyMessage
|
|
193
|
+
}
|
|
194
|
+
) }) : rows.map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
195
|
+
"tr",
|
|
196
|
+
{
|
|
197
|
+
className: "border-border border-b",
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
200
|
+
"td",
|
|
201
|
+
{
|
|
202
|
+
className: "text-muted-foreground w-10 cursor-pointer px-2 py-2 text-center text-xs",
|
|
203
|
+
title: "Select this row",
|
|
204
|
+
onMouseDown: (event) => selectRow(r, event.shiftKey),
|
|
205
|
+
children: r + 1
|
|
206
|
+
}
|
|
207
|
+
),
|
|
208
|
+
columns.map((column, c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
209
|
+
"td",
|
|
210
|
+
{
|
|
211
|
+
"data-cell": `${r}-${c}`,
|
|
212
|
+
onMouseDown: (event) => {
|
|
213
|
+
selectCell(r, c, event.shiftKey);
|
|
214
|
+
setDragging(true);
|
|
215
|
+
gridRef.current?.focus();
|
|
216
|
+
},
|
|
217
|
+
onMouseEnter: () => {
|
|
218
|
+
if (dragging) setSelection(anchorRef.current, { r, c });
|
|
219
|
+
},
|
|
220
|
+
className: `px-3 py-2 ${column.className || ""} ${isInRange(range, r, c) ? "bg-primary/15" : ""}`,
|
|
221
|
+
title: column.className?.includes("truncate") ? column.value(row) : void 0,
|
|
222
|
+
children: column.render ? column.render(row) : column.value(row)
|
|
223
|
+
},
|
|
224
|
+
column.key
|
|
225
|
+
))
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
rowKey ? rowKey(row, r) : String(r)
|
|
229
|
+
)) })
|
|
230
|
+
] })
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
235
|
+
0 && (module.exports = {
|
|
236
|
+
SpreadsheetGrid
|
|
237
|
+
});
|
|
238
|
+
//# sourceMappingURL=spreadsheet-grid.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/spreadsheet-grid.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport { type CellRange, type CellRef } from \"./clipboard\";\n\n/**\n * A column knows three things: its heading, the text it contributes to a copy\n * or CSV export, and optionally how to draw itself.\n *\n * Keeping `value` and `render` together is what stops an exported sheet from\n * drifting from the visible grid.\n */\nexport type GridColumn<Row> = {\n key: string;\n title: string;\n /** Text used for clipboard and CSV. Never JSX. */\n value: (row: Row) => string;\n /** Defaults to `value`. Use for links, tiles, truncation. */\n render?: (row: Row) => React.ReactNode;\n /** Extra classes for the <td>. */\n className?: string;\n};\n\nexport interface SpreadsheetGridProps<Row> {\n columns: GridColumn<Row>[];\n rows: Row[];\n /** Stable React key per row. Defaults to the row index. */\n rowKey?: (row: Row, index: number) => string;\n loading?: boolean;\n emptyMessage?: string;\n loadingMessage?: string;\n ariaLabel?: string;\n /** Lifted so a toolbar can copy and describe the current selection. */\n range: CellRange | null;\n onRangeChange: (selection: {\n anchor: CellRef | null;\n focus: CellRef | null;\n }) => void;\n onCopy: () => void;\n}\n\nfunction isInRange(range: CellRange | null, r: number, c: number): boolean {\n if (!range) return false;\n return r >= range.r1 && r <= range.r2 && c >= range.c1 && c <= range.c2;\n}\n\n/**\n * Spreadsheet-like grid: click-drag a range, shift+click to extend, arrows to\n * move, click a heading to take the column, click the row number to take the\n * row, Cmd/Ctrl+A for everything, Escape to clear, Cmd/Ctrl+C to copy.\n *\n * Ported from video-shorts-panel's ExcelTable, which several panels share for\n * exactly this reason — a table people paste into a sheet.\n */\nexport function SpreadsheetGrid<Row>({\n columns,\n rows,\n rowKey,\n loading,\n emptyMessage = \"Nothing to show.\",\n loadingMessage = \"Loading…\",\n ariaLabel = \"Data grid\",\n range,\n onRangeChange,\n onCopy,\n}: SpreadsheetGridProps<Row>) {\n const gridRef = React.useRef<HTMLDivElement>(null);\n const anchorRef = React.useRef<CellRef | null>(null);\n const focusRef = React.useRef<CellRef | null>(null);\n const [dragging, setDragging] = React.useState(false);\n\n const setSelection = React.useCallback(\n (anchor: CellRef | null, focus: CellRef | null) => {\n anchorRef.current = anchor;\n focusRef.current = focus;\n onRangeChange({ anchor, focus });\n },\n [onRangeChange],\n );\n\n const selectCell = React.useCallback(\n (r: number, c: number, extend?: boolean) => {\n if (extend && anchorRef.current)\n setSelection(anchorRef.current, { r, c });\n else setSelection({ r, c }, { r, c });\n },\n [setSelection],\n );\n\n const selectColumn = React.useCallback(\n (c: number, extend?: boolean) => {\n const lastRow = Math.max(rows.length - 1, 0);\n const anchorCol = extend && anchorRef.current ? anchorRef.current.c : c;\n setSelection({ r: 0, c: anchorCol }, { r: lastRow, c });\n },\n [rows.length, setSelection],\n );\n\n const selectRow = React.useCallback(\n (r: number, extend?: boolean) => {\n const lastCol = columns.length - 1;\n const anchorRow = extend && anchorRef.current ? anchorRef.current.r : r;\n setSelection({ r: anchorRow, c: 0 }, { r, c: lastCol });\n },\n [columns.length, setSelection],\n );\n\n const selectAll = React.useCallback(() => {\n if (!rows.length || !columns.length) return;\n setSelection({ r: 0, c: 0 }, { r: rows.length - 1, c: columns.length - 1 });\n }, [rows.length, columns.length, setSelection]);\n\n const scrollCellIntoView = React.useCallback((r: number, c: number) => {\n const cell = gridRef.current?.querySelector(`[data-cell=\"${r}-${c}\"]`);\n cell?.scrollIntoView({ block: \"nearest\", inline: \"nearest\" });\n }, []);\n\n const moveFocus = React.useCallback(\n (dr: number, dc: number, extend?: boolean) => {\n if (!rows.length || !columns.length) return;\n const base = focusRef.current || { r: 0, c: 0 };\n const r = Math.min(Math.max(base.r + dr, 0), rows.length - 1);\n const c = Math.min(Math.max(base.c + dc, 0), columns.length - 1);\n selectCell(r, c, extend);\n scrollCellIntoView(r, c);\n },\n [rows.length, columns.length, selectCell, scrollCellIntoView],\n );\n\n // Dragging must end even if the pointer is released outside the grid.\n React.useEffect(() => {\n if (!dragging) return;\n const stop = () => setDragging(false);\n window.addEventListener(\"mouseup\", stop);\n return () => window.removeEventListener(\"mouseup\", stop);\n }, [dragging]);\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n const meta = event.metaKey || event.ctrlKey;\n if (meta && event.key.toLowerCase() === \"c\") {\n event.preventDefault();\n onCopy();\n return;\n }\n if (meta && event.key.toLowerCase() === \"a\") {\n event.preventDefault();\n selectAll();\n return;\n }\n switch (event.key) {\n case \"ArrowUp\":\n event.preventDefault();\n moveFocus(-1, 0, event.shiftKey);\n break;\n case \"ArrowDown\":\n event.preventDefault();\n moveFocus(1, 0, event.shiftKey);\n break;\n case \"ArrowLeft\":\n event.preventDefault();\n moveFocus(0, -1, event.shiftKey);\n break;\n case \"ArrowRight\":\n case \"Tab\":\n event.preventDefault();\n moveFocus(0, 1, event.shiftKey && event.key !== \"Tab\");\n break;\n case \"Escape\":\n setSelection(null, null);\n break;\n default:\n break;\n }\n };\n\n return (\n <div\n ref={gridRef}\n tabIndex={0}\n role=\"grid\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className=\"border-border bg-background/80 overflow-x-auto rounded-lg border outline-none\"\n >\n <table className=\"w-full text-sm whitespace-nowrap select-none\">\n <thead className=\"text-muted-foreground border-border border-b text-left\">\n <tr>\n <th\n scope=\"col\"\n className=\"w-10 cursor-pointer px-2 py-2 text-center font-medium\"\n title=\"Select everything\"\n onMouseDown={() => selectAll()}\n >\n #\n </th>\n {columns.map((column, c) => (\n <th\n key={column.key}\n scope=\"col\"\n className=\"cursor-pointer px-3 py-2 font-medium\"\n title={`Select the ${column.title} column`}\n onMouseDown={(event) => selectColumn(c, event.shiftKey)}\n >\n {column.title}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.length === 0 ? (\n <tr>\n <td\n colSpan={columns.length + 1}\n className=\"text-muted-foreground px-3 py-6 text-center\"\n >\n {loading ? loadingMessage : emptyMessage}\n </td>\n </tr>\n ) : (\n rows.map((row, r) => (\n <tr\n key={rowKey ? rowKey(row, r) : String(r)}\n className=\"border-border border-b\"\n >\n <td\n className=\"text-muted-foreground w-10 cursor-pointer px-2 py-2 text-center text-xs\"\n title=\"Select this row\"\n onMouseDown={(event) => selectRow(r, event.shiftKey)}\n >\n {r + 1}\n </td>\n {columns.map((column, c) => (\n <td\n key={column.key}\n data-cell={`${r}-${c}`}\n onMouseDown={(event) => {\n selectCell(r, c, event.shiftKey);\n setDragging(true);\n gridRef.current?.focus();\n }}\n onMouseEnter={() => {\n if (dragging) setSelection(anchorRef.current, { r, c });\n }}\n className={`px-3 py-2 ${column.className || \"\"} ${\n isInRange(range, r, c) ? \"bg-primary/15\" : \"\"\n }`}\n title={\n column.className?.includes(\"truncate\")\n ? column.value(row)\n : undefined\n }\n >\n {column.render ? column.render(row) : column.value(row)}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { CellRange, CellRef } from './clipboard.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A column knows three things: its heading, the text it contributes to a copy
|
|
6
|
+
* or CSV export, and optionally how to draw itself.
|
|
7
|
+
*
|
|
8
|
+
* Keeping `value` and `render` together is what stops an exported sheet from
|
|
9
|
+
* drifting from the visible grid.
|
|
10
|
+
*/
|
|
11
|
+
type GridColumn<Row> = {
|
|
12
|
+
key: string;
|
|
13
|
+
title: string;
|
|
14
|
+
/** Text used for clipboard and CSV. Never JSX. */
|
|
15
|
+
value: (row: Row) => string;
|
|
16
|
+
/** Defaults to `value`. Use for links, tiles, truncation. */
|
|
17
|
+
render?: (row: Row) => React.ReactNode;
|
|
18
|
+
/** Extra classes for the <td>. */
|
|
19
|
+
className?: string;
|
|
20
|
+
};
|
|
21
|
+
interface SpreadsheetGridProps<Row> {
|
|
22
|
+
columns: GridColumn<Row>[];
|
|
23
|
+
rows: Row[];
|
|
24
|
+
/** Stable React key per row. Defaults to the row index. */
|
|
25
|
+
rowKey?: (row: Row, index: number) => string;
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
emptyMessage?: string;
|
|
28
|
+
loadingMessage?: string;
|
|
29
|
+
ariaLabel?: string;
|
|
30
|
+
/** Lifted so a toolbar can copy and describe the current selection. */
|
|
31
|
+
range: CellRange | null;
|
|
32
|
+
onRangeChange: (selection: {
|
|
33
|
+
anchor: CellRef | null;
|
|
34
|
+
focus: CellRef | null;
|
|
35
|
+
}) => void;
|
|
36
|
+
onCopy: () => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Spreadsheet-like grid: click-drag a range, shift+click to extend, arrows to
|
|
40
|
+
* move, click a heading to take the column, click the row number to take the
|
|
41
|
+
* row, Cmd/Ctrl+A for everything, Escape to clear, Cmd/Ctrl+C to copy.
|
|
42
|
+
*
|
|
43
|
+
* Ported from video-shorts-panel's ExcelTable, which several panels share for
|
|
44
|
+
* exactly this reason — a table people paste into a sheet.
|
|
45
|
+
*/
|
|
46
|
+
declare function SpreadsheetGrid<Row>({ columns, rows, rowKey, loading, emptyMessage, loadingMessage, ariaLabel, range, onRangeChange, onCopy, }: SpreadsheetGridProps<Row>): React.JSX.Element;
|
|
47
|
+
|
|
48
|
+
export { type GridColumn, SpreadsheetGrid, type SpreadsheetGridProps };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { CellRange, CellRef } from './clipboard.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A column knows three things: its heading, the text it contributes to a copy
|
|
6
|
+
* or CSV export, and optionally how to draw itself.
|
|
7
|
+
*
|
|
8
|
+
* Keeping `value` and `render` together is what stops an exported sheet from
|
|
9
|
+
* drifting from the visible grid.
|
|
10
|
+
*/
|
|
11
|
+
type GridColumn<Row> = {
|
|
12
|
+
key: string;
|
|
13
|
+
title: string;
|
|
14
|
+
/** Text used for clipboard and CSV. Never JSX. */
|
|
15
|
+
value: (row: Row) => string;
|
|
16
|
+
/** Defaults to `value`. Use for links, tiles, truncation. */
|
|
17
|
+
render?: (row: Row) => React.ReactNode;
|
|
18
|
+
/** Extra classes for the <td>. */
|
|
19
|
+
className?: string;
|
|
20
|
+
};
|
|
21
|
+
interface SpreadsheetGridProps<Row> {
|
|
22
|
+
columns: GridColumn<Row>[];
|
|
23
|
+
rows: Row[];
|
|
24
|
+
/** Stable React key per row. Defaults to the row index. */
|
|
25
|
+
rowKey?: (row: Row, index: number) => string;
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
emptyMessage?: string;
|
|
28
|
+
loadingMessage?: string;
|
|
29
|
+
ariaLabel?: string;
|
|
30
|
+
/** Lifted so a toolbar can copy and describe the current selection. */
|
|
31
|
+
range: CellRange | null;
|
|
32
|
+
onRangeChange: (selection: {
|
|
33
|
+
anchor: CellRef | null;
|
|
34
|
+
focus: CellRef | null;
|
|
35
|
+
}) => void;
|
|
36
|
+
onCopy: () => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Spreadsheet-like grid: click-drag a range, shift+click to extend, arrows to
|
|
40
|
+
* move, click a heading to take the column, click the row number to take the
|
|
41
|
+
* row, Cmd/Ctrl+A for everything, Escape to clear, Cmd/Ctrl+C to copy.
|
|
42
|
+
*
|
|
43
|
+
* Ported from video-shorts-panel's ExcelTable, which several panels share for
|
|
44
|
+
* exactly this reason — a table people paste into a sheet.
|
|
45
|
+
*/
|
|
46
|
+
declare function SpreadsheetGrid<Row>({ columns, rows, rowKey, loading, emptyMessage, loadingMessage, ariaLabel, range, onRangeChange, onCopy, }: SpreadsheetGridProps<Row>): React.JSX.Element;
|
|
47
|
+
|
|
48
|
+
export { type GridColumn, SpreadsheetGrid, type SpreadsheetGridProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|