@dowel-ui/react 0.4.0 → 0.7.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/README.md +9 -5
- package/dist/blocks/admin-dashboard/meta.js +31 -0
- package/dist/blocks/admin-dashboard/meta.js.map +1 -0
- package/dist/blocks/agent-console/meta.js +27 -0
- package/dist/blocks/agent-console/meta.js.map +1 -0
- package/dist/blocks/ai-dashboard/meta.js +25 -0
- package/dist/blocks/ai-dashboard/meta.js.map +1 -0
- package/dist/blocks/ai-workspace/meta.js +33 -0
- package/dist/blocks/ai-workspace/meta.js.map +1 -0
- package/dist/blocks/analytics/meta.js +24 -0
- package/dist/blocks/analytics/meta.js.map +1 -0
- package/dist/blocks/billing/meta.js +25 -0
- package/dist/blocks/billing/meta.js.map +1 -0
- package/dist/blocks/command-center/meta.js +28 -0
- package/dist/blocks/command-center/meta.js.map +1 -0
- package/dist/blocks/crm/meta.js +32 -0
- package/dist/blocks/crm/meta.js.map +1 -0
- package/dist/blocks/onboarding/meta.js +23 -0
- package/dist/blocks/onboarding/meta.js.map +1 -0
- package/dist/components/accordion/accordion.js +1 -1
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/activity-feed/activity-feed.js +1 -1
- package/dist/components/activity-feed/activity-feed.js.map +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -1
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts +52 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js +249 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts +94 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.js +91 -0
- package/dist/components/ai-extraction-review/extraction-model.js.map +1 -0
- package/dist/components/ai-extraction-review/index.d.ts +3 -0
- package/dist/components/ai-extraction-review/index.js +3 -0
- package/dist/components/ai-extraction-review/meta.js +17 -0
- package/dist/components/ai-extraction-review/meta.js.map +1 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.js +1 -1
- package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -1
- package/dist/components/ai-response/ai-response.js +2 -2
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts +72 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts.map +1 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js +126 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js.map +1 -0
- package/dist/components/ai-suggested-value/index.d.ts +2 -0
- package/dist/components/ai-suggested-value/index.js +2 -0
- package/dist/components/ai-suggested-value/meta.js +17 -0
- package/dist/components/ai-suggested-value/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.js +2 -2
- package/dist/components/ai-tool/ai-tool.js.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.d.ts +52 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb/breadcrumb.js +141 -0
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +2 -0
- package/dist/components/breadcrumb/index.js +2 -0
- package/dist/components/breadcrumb/meta.js +17 -0
- package/dist/components/breadcrumb/meta.js.map +1 -0
- package/dist/components/calendar/calendar.js +2 -2
- package/dist/components/calendar/calendar.js.map +1 -1
- package/dist/components/code-block/code-block.js +1 -1
- package/dist/components/code-block/code-block.js.map +1 -1
- package/dist/components/collapsible/collapsible.d.ts +30 -0
- package/dist/components/collapsible/collapsible.d.ts.map +1 -0
- package/dist/components/collapsible/collapsible.js +38 -0
- package/dist/components/collapsible/collapsible.js.map +1 -0
- package/dist/components/collapsible/index.d.ts +2 -0
- package/dist/components/collapsible/index.js +2 -0
- package/dist/components/collapsible/meta.js +17 -0
- package/dist/components/collapsible/meta.js.map +1 -0
- package/dist/components/combobox/combobox.js +2 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/command/command.js +1 -1
- package/dist/components/command/command.js.map +1 -1
- package/dist/components/confirm-typed/confirm-typed.d.ts +47 -0
- package/dist/components/confirm-typed/confirm-typed.d.ts.map +1 -0
- package/dist/components/confirm-typed/confirm-typed.js +106 -0
- package/dist/components/confirm-typed/confirm-typed.js.map +1 -0
- package/dist/components/confirm-typed/index.d.ts +2 -0
- package/dist/components/confirm-typed/index.js +2 -0
- package/dist/components/confirm-typed/meta.js +17 -0
- package/dist/components/confirm-typed/meta.js.map +1 -0
- package/dist/components/cron-editor/cron-editor.d.ts +36 -0
- package/dist/components/cron-editor/cron-editor.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-editor.js +493 -0
- package/dist/components/cron-editor/cron-editor.js.map +1 -0
- package/dist/components/cron-editor/cron-expression.d.ts +82 -0
- package/dist/components/cron-editor/cron-expression.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-expression.js +352 -0
- package/dist/components/cron-editor/cron-expression.js.map +1 -0
- package/dist/components/cron-editor/index.d.ts +3 -0
- package/dist/components/cron-editor/index.js +3 -0
- package/dist/components/cron-editor/meta.js +17 -0
- package/dist/components/cron-editor/meta.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts.map +1 -1
- package/dist/components/data-table/data-table.js +4 -1
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/dialog/dialog.js +2 -2
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/diff-viewer/diff-viewer.js +4 -4
- package/dist/components/diff-viewer/diff-viewer.js.map +1 -1
- package/dist/components/direction/direction.d.ts +35 -0
- package/dist/components/direction/direction.d.ts.map +1 -0
- package/dist/components/direction/direction.js +14 -0
- package/dist/components/direction/direction.js.map +1 -0
- package/dist/components/direction/index.d.ts +2 -0
- package/dist/components/direction/index.js +2 -0
- package/dist/components/direction/meta.js +17 -0
- package/dist/components/direction/meta.js.map +1 -0
- package/dist/components/dns-record/dns-record.d.ts +61 -0
- package/dist/components/dns-record/dns-record.d.ts.map +1 -0
- package/dist/components/dns-record/dns-record.js +227 -0
- package/dist/components/dns-record/dns-record.js.map +1 -0
- package/dist/components/dns-record/index.d.ts +2 -0
- package/dist/components/dns-record/index.js +2 -0
- package/dist/components/dns-record/meta.js +17 -0
- package/dist/components/dns-record/meta.js.map +1 -0
- package/dist/components/drawer/drawer.js +1 -1
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.js +9 -8
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/input/input.js +1 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/log-viewer/log-viewer.js +2 -2
- package/dist/components/log-viewer/log-viewer.js.map +1 -1
- package/dist/components/meter/meter.js +1 -1
- package/dist/components/meter/meter.js.map +1 -1
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +3 -2
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/permission-matrix/index.d.ts +3 -0
- package/dist/components/permission-matrix/index.js +3 -0
- package/dist/components/permission-matrix/meta.js +17 -0
- package/dist/components/permission-matrix/meta.js.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts +46 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.js +199 -0
- package/dist/components/permission-matrix/permission-matrix.js.map +1 -0
- package/dist/components/permission-matrix/permission-model.d.ts +53 -0
- package/dist/components/permission-matrix/permission-model.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-model.js +64 -0
- package/dist/components/permission-matrix/permission-model.js.map +1 -0
- package/dist/components/record-diff/record-diff.js +4 -4
- package/dist/components/record-diff/record-diff.js.map +1 -1
- package/dist/components/secret-field/index.d.ts +2 -0
- package/dist/components/secret-field/index.js +2 -0
- package/dist/components/secret-field/meta.js +17 -0
- package/dist/components/secret-field/meta.js.map +1 -0
- package/dist/components/secret-field/secret-field.d.ts +58 -0
- package/dist/components/secret-field/secret-field.d.ts.map +1 -0
- package/dist/components/secret-field/secret-field.js +168 -0
- package/dist/components/secret-field/secret-field.js.map +1 -0
- package/dist/components/select/select.js +2 -2
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/session-expiry/index.d.ts +2 -0
- package/dist/components/session-expiry/index.js +2 -0
- package/dist/components/session-expiry/meta.js +17 -0
- package/dist/components/session-expiry/meta.js.map +1 -0
- package/dist/components/session-expiry/session-expiry.d.ts +64 -0
- package/dist/components/session-expiry/session-expiry.d.ts.map +1 -0
- package/dist/components/session-expiry/session-expiry.js +185 -0
- package/dist/components/session-expiry/session-expiry.js.map +1 -0
- package/dist/components/sheet/sheet.d.ts.map +1 -1
- package/dist/components/sheet/sheet.js +2 -2
- package/dist/components/sheet/sheet.js.map +1 -1
- package/dist/components/shortcut-recorder/index.d.ts +3 -0
- package/dist/components/shortcut-recorder/index.js +3 -0
- package/dist/components/shortcut-recorder/meta.js +17 -0
- package/dist/components/shortcut-recorder/meta.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts +67 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.js +168 -0
- package/dist/components/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts +47 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js +212 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/meta.js +17 -0
- package/dist/components/sidebar/meta.js.map +1 -0
- package/dist/components/sidebar/sidebar.d.ts +69 -0
- package/dist/components/sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.js +256 -0
- package/dist/components/sidebar/sidebar.js.map +1 -0
- package/dist/components/sync-status/index.d.ts +2 -0
- package/dist/components/sync-status/index.js +2 -0
- package/dist/components/sync-status/meta.js +17 -0
- package/dist/components/sync-status/meta.js.map +1 -0
- package/dist/components/sync-status/sync-status.d.ts +65 -0
- package/dist/components/sync-status/sync-status.d.ts.map +1 -0
- package/dist/components/sync-status/sync-status.js +103 -0
- package/dist/components/sync-status/sync-status.js.map +1 -0
- package/dist/components/table/table.js +2 -2
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/tags-input/tags-input.js +1 -1
- package/dist/components/tags-input/tags-input.js.map +1 -1
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +2 -0
- package/dist/components/textarea/meta.js +17 -0
- package/dist/components/textarea/meta.js.map +1 -0
- package/dist/components/textarea/textarea.d.ts +43 -0
- package/dist/components/textarea/textarea.d.ts.map +1 -0
- package/dist/components/textarea/textarea.js +119 -0
- package/dist/components/textarea/textarea.js.map +1 -0
- package/dist/components/time-range-picker/time-range-picker.js +3 -3
- package/dist/components/time-range-picker/time-range-picker.js.map +1 -1
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +2 -2
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/index.d.ts +36 -2
- package/dist/index.js +22 -3
- package/dist/lib/styles.d.ts +16 -1
- package/dist/lib/styles.d.ts.map +1 -1
- package/dist/lib/styles.js +16 -1
- package/dist/lib/styles.js.map +1 -1
- package/dist/registry/blocks.d.ts.map +1 -1
- package/dist/registry/blocks.js +27 -9
- package/dist/registry/blocks.js.map +1 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +84 -54
- package/dist/registry/components.js.map +1 -1
- package/dist/registry/schema.d.ts +8 -0
- package/dist/registry/schema.d.ts.map +1 -1
- package/dist/registry/schema.js.map +1 -1
- package/package.json +5 -4
- package/src/components/accordion/accordion.tsx +1 -1
- package/src/components/activity-feed/activity-feed.tsx +1 -1
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-extraction-review/ai-extraction-review.tsx +492 -0
- package/src/components/ai-extraction-review/extraction-model.ts +159 -0
- package/src/components/ai-extraction-review/index.ts +13 -0
- package/src/components/ai-extraction-review/meta.ts +24 -0
- package/src/components/ai-prompt-input/ai-prompt-input.tsx +1 -1
- package/src/components/ai-reasoning/ai-reasoning.tsx +1 -1
- package/src/components/ai-response/ai-response.tsx +2 -2
- package/src/components/ai-suggested-value/ai-suggested-value.tsx +295 -0
- package/src/components/ai-suggested-value/index.ts +6 -0
- package/src/components/ai-suggested-value/meta.ts +24 -0
- package/src/components/ai-tool/ai-tool.tsx +2 -2
- package/src/components/breadcrumb/breadcrumb.tsx +157 -0
- package/src/components/breadcrumb/index.ts +10 -0
- package/src/components/breadcrumb/meta.ts +19 -0
- package/src/components/calendar/calendar.tsx +2 -2
- package/src/components/code-block/code-block.tsx +1 -1
- package/src/components/collapsible/collapsible.tsx +63 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/collapsible/meta.ts +16 -0
- package/src/components/combobox/combobox.tsx +2 -2
- package/src/components/command/command.tsx +1 -1
- package/src/components/confirm-typed/confirm-typed.tsx +191 -0
- package/src/components/confirm-typed/index.ts +1 -0
- package/src/components/confirm-typed/meta.ts +22 -0
- package/src/components/cron-editor/cron-editor.tsx +676 -0
- package/src/components/cron-editor/cron-expression.ts +552 -0
- package/src/components/cron-editor/index.ts +23 -0
- package/src/components/cron-editor/meta.ts +23 -0
- package/src/components/data-table/data-table.tsx +14 -3
- package/src/components/dialog/dialog.tsx +2 -2
- package/src/components/diff-viewer/diff-viewer.tsx +4 -4
- package/src/components/direction/direction.tsx +38 -0
- package/src/components/direction/index.ts +1 -0
- package/src/components/direction/meta.ts +19 -0
- package/src/components/dns-record/dns-record.tsx +315 -0
- package/src/components/dns-record/index.ts +7 -0
- package/src/components/dns-record/meta.ts +21 -0
- package/src/components/drawer/drawer.tsx +1 -1
- package/src/components/dropdown-menu/dropdown-menu.tsx +14 -8
- package/src/components/input/input.tsx +1 -1
- package/src/components/log-viewer/log-viewer.tsx +2 -2
- package/src/components/meter/meter.tsx +1 -1
- package/src/components/pagination/pagination.tsx +13 -2
- package/src/components/permission-matrix/index.ts +14 -0
- package/src/components/permission-matrix/meta.ts +23 -0
- package/src/components/permission-matrix/permission-matrix.tsx +368 -0
- package/src/components/permission-matrix/permission-model.ts +120 -0
- package/src/components/record-diff/record-diff.tsx +4 -4
- package/src/components/secret-field/index.ts +6 -0
- package/src/components/secret-field/meta.ts +21 -0
- package/src/components/secret-field/secret-field.tsx +284 -0
- package/src/components/select/select.tsx +2 -2
- package/src/components/session-expiry/index.ts +9 -0
- package/src/components/session-expiry/meta.ts +23 -0
- package/src/components/session-expiry/session-expiry.tsx +321 -0
- package/src/components/sheet/sheet.tsx +8 -4
- package/src/components/shortcut-recorder/index.ts +19 -0
- package/src/components/shortcut-recorder/meta.ts +22 -0
- package/src/components/shortcut-recorder/shortcut-model.ts +204 -0
- package/src/components/shortcut-recorder/shortcut-recorder.tsx +336 -0
- package/src/components/sidebar/index.ts +19 -0
- package/src/components/sidebar/meta.ts +25 -0
- package/src/components/sidebar/sidebar.tsx +374 -0
- package/src/components/sync-status/index.ts +9 -0
- package/src/components/sync-status/meta.ts +21 -0
- package/src/components/sync-status/sync-status.tsx +217 -0
- package/src/components/table/table.tsx +3 -3
- package/src/components/tags-input/tags-input.tsx +1 -1
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/meta.ts +21 -0
- package/src/components/textarea/textarea.tsx +210 -0
- package/src/components/time-range-picker/time-range-picker.tsx +3 -3
- package/src/components/toast/toast.tsx +14 -6
- package/src/index.ts +15 -0
- package/src/lib/styles.ts +16 -0
- package/src/registry/blocks.ts +18 -0
- package/src/registry/components.ts +30 -0
- package/src/registry/schema.ts +8 -0
- package/src/blocks/admin-users/admin-users.tsx +0 -251
- package/src/blocks/admin-users/index.ts +0 -1
- package/src/blocks/admin-users/meta.ts +0 -27
- package/src/blocks/ai-chat/ai-chat.tsx +0 -280
- package/src/blocks/ai-chat/index.ts +0 -7
- package/src/blocks/ai-chat/meta.ts +0 -32
- package/src/blocks/dashboard/dashboard.tsx +0 -192
- package/src/blocks/dashboard/index.ts +0 -6
- package/src/blocks/dashboard/meta.ts +0 -18
- package/src/blocks/forgot-password/forgot-password.tsx +0 -158
- package/src/blocks/forgot-password/index.ts +0 -1
- package/src/blocks/forgot-password/meta.ts +0 -18
- package/src/blocks/login/index.ts +0 -1
- package/src/blocks/login/login.tsx +0 -178
- package/src/blocks/login/meta.ts +0 -18
- package/src/blocks/pricing/index.ts +0 -1
- package/src/blocks/pricing/meta.ts +0 -18
- package/src/blocks/pricing/pricing.tsx +0 -176
- package/src/blocks/settings/index.ts +0 -6
- package/src/blocks/settings/meta.ts +0 -29
- package/src/blocks/settings/settings.tsx +0 -317
- package/src/blocks/signup/index.ts +0 -1
- package/src/blocks/signup/meta.ts +0 -18
- package/src/blocks/signup/signup.tsx +0 -240
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet.js","names":["SheetPrimitive"],"sources":["../../../src/components/sheet/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Dialog as SheetPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A panel that enters from an edge of the viewport.\n *\n * Built on the same modal primitive as Dialog, so focus management, Escape\n * handling and page inerting are identical; only the placement and motion\n * differ. Reach for Sheet when the content is a secondary surface — filters,\n * details, navigation — rather than a decision that must be answered.\n */\nexport const Sheet = SheetPrimitive.Root;\nexport const SheetTrigger = SheetPrimitive.Trigger;\nexport const SheetPortal = SheetPrimitive.Portal;\nexport const SheetClose = SheetPrimitive.Close;\n\nexport function SheetOverlay({\n className,\n ...props\n}: ComponentPropsWithRef<typeof SheetPrimitive.Overlay>) {\n return (\n <SheetPrimitive.Overlay\n data-slot=\"sheet-overlay\"\n className={cn(\n \"fixed inset-0 z-[var(--z-overlay)] bg-overlay\",\n \"data-[state=closed]:animate-overlay-out data-[state=open]:animate-overlay-in\",\n className,\n )}\n {...props}\n />\n );\n}\n\nconst sheetVariants = cva(\n cn(\n \"fixed z-[var(--z-drawer)] flex flex-col gap-4 bg-card p-6 text-card-foreground shadow-xl\",\n \"data-[state=closed]:animate-slide-out data-[state=open]:animate-slide-in\",\n ),\n {\n variants: {\n // --slide-x/--slide-y feed the shared slide keyframes, so all four sides\n // reuse one animation pair instead of eight bespoke keyframes.\n side: {\n top: \"inset-x-0 top-0 border-b [--slide-y:-100%]\",\n bottom: \"inset-x-0 bottom-0 border-t [--slide-y:100%]\",\n left: \"inset-y-0 left-0 h-full w-3/4 max-w-sm border-r [--slide-x:-100%]\"
|
|
1
|
+
{"version":3,"file":"sheet.js","names":["SheetPrimitive"],"sources":["../../../src/components/sheet/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Dialog as SheetPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A panel that enters from an edge of the viewport.\n *\n * Built on the same modal primitive as Dialog, so focus management, Escape\n * handling and page inerting are identical; only the placement and motion\n * differ. Reach for Sheet when the content is a secondary surface — filters,\n * details, navigation — rather than a decision that must be answered.\n */\nexport const Sheet = SheetPrimitive.Root;\nexport const SheetTrigger = SheetPrimitive.Trigger;\nexport const SheetPortal = SheetPrimitive.Portal;\nexport const SheetClose = SheetPrimitive.Close;\n\nexport function SheetOverlay({\n className,\n ...props\n}: ComponentPropsWithRef<typeof SheetPrimitive.Overlay>) {\n return (\n <SheetPrimitive.Overlay\n data-slot=\"sheet-overlay\"\n className={cn(\n \"fixed inset-0 z-[var(--z-overlay)] bg-overlay\",\n \"data-[state=closed]:animate-overlay-out data-[state=open]:animate-overlay-in\",\n className,\n )}\n {...props}\n />\n );\n}\n\nconst sheetVariants = cva(\n cn(\n \"fixed z-[var(--z-drawer)] flex flex-col gap-4 bg-card p-6 text-card-foreground shadow-xl\",\n \"data-[state=closed]:animate-slide-out data-[state=open]:animate-slide-in\",\n ),\n {\n variants: {\n // --slide-x/--slide-y feed the shared slide keyframes, so all four sides\n // reuse one animation pair instead of eight bespoke keyframes.\n side: {\n top: \"inset-x-0 top-0 border-b [--slide-y:-100%]\",\n bottom: \"inset-x-0 bottom-0 border-t [--slide-y:100%]\",\n // rtl-ok: the variant is named `left`, and a sheet asked for on the\n // left that opens on the right in Arabic is an API telling a lie. The\n // logical version of this is a `start`/`end` side, which is a rename\n // rather than a restyle.\n left: \"inset-y-0 left-0 h-full w-3/4 max-w-sm border-r [--slide-x:-100%]\", // rtl-ok: named side\n right: \"inset-y-0 right-0 h-full w-3/4 max-w-sm border-l [--slide-x:100%]\", // rtl-ok: named side\n },\n },\n defaultVariants: {\n side: \"right\",\n },\n },\n);\n\nexport interface SheetContentProps\n extends\n ComponentPropsWithRef<typeof SheetPrimitive.Content>,\n VariantProps<typeof sheetVariants> {\n showCloseButton?: boolean;\n}\n\nexport function SheetContent({\n className,\n children,\n side,\n showCloseButton = true,\n ...props\n}: SheetContentProps) {\n return (\n <SheetPortal>\n <SheetOverlay />\n <SheetPrimitive.Content\n data-slot=\"sheet-content\"\n className={cn(sheetVariants({ side }), className)}\n {...props}\n >\n {children}\n {showCloseButton ? (\n <SheetPrimitive.Close\n data-slot=\"sheet-close\"\n aria-label=\"Close\"\n className={cn(\n \"absolute end-4 top-4 grid size-7 place-items-center rounded-md text-muted-foreground\",\n \"transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground\",\n \"[&_svg]:size-4\",\n focusRing,\n )}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"m6 6 12 12M18 6 6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </SheetPrimitive.Close>\n ) : null}\n </SheetPrimitive.Content>\n </SheetPortal>\n );\n}\n\nexport function SheetHeader({ className, ...props }: ComponentPropsWithRef<\"div\">) {\n return (\n <div\n data-slot=\"sheet-header\"\n className={cn(\"flex flex-col gap-1.5 pe-8 text-start\", className)}\n {...props}\n />\n );\n}\n\nexport function SheetFooter({ className, ...props }: ComponentPropsWithRef<\"div\">) {\n return (\n <div\n data-slot=\"sheet-footer\"\n className={cn(\n \"mt-auto flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function SheetTitle({\n className,\n ...props\n}: ComponentPropsWithRef<typeof SheetPrimitive.Title>) {\n return (\n <SheetPrimitive.Title\n data-slot=\"sheet-title\"\n className={cn(\"text-lg leading-tight font-semibold tracking-tight\", className)}\n {...props}\n />\n );\n}\n\nexport function SheetDescription({\n className,\n ...props\n}: ComponentPropsWithRef<typeof SheetPrimitive.Description>) {\n return (\n <SheetPrimitive.Description\n data-slot=\"sheet-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport { sheetVariants };\n"],"mappings":";;;;;;;;;;;;;;;AAiBA,MAAa,QAAQA,OAAe;AACpC,MAAa,eAAeA,OAAe;AAC3C,MAAa,cAAcA,OAAe;AAC1C,MAAa,aAAaA,OAAe;AAEzC,SAAgB,aAAa,EAC3B,WACA,GAAG,SACoD;CACvD,OACE,oBAACA,OAAe,SAAhB;EACE,aAAU;EACV,WAAW,GACT,iDACA,gFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,MAAM,gBAAgB,IACpB,GACE,4FACA,0EACF,GACA;CACE,UAAU,EAGR,MAAM;EACJ,KAAK;EACL,QAAQ;EAKR,MAAM;EACN,OAAO;CACT,EACF;CACA,iBAAiB,EACf,MAAM,QACR;AACF,CACF;AASA,SAAgB,aAAa,EAC3B,WACA,UACA,MACA,kBAAkB,MAClB,GAAG,SACiB;CACpB,OACE,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,cAAD,CAAe,CAAA,GACf,qBAACA,OAAe,SAAhB;EACE,aAAU;EACV,WAAW,GAAG,cAAc,EAAE,KAAK,CAAC,GAAG,SAAS;EAChD,GAAI;EAHN,UAAA,CAKG,UACA,kBACC,oBAACA,OAAe,OAAhB;GACE,aAAU;GACV,cAAW;GACX,WAAW,GACT,wFACA,2FACA,kBACA,SACF;GAEA,UAAA,oBAAC,OAAD;IAAK,SAAQ;IAAY,MAAK;IAAO,eAAY;IAC/C,UAAA,oBAAC,QAAD;KACE,GAAE;KACF,QAAO;KACP,aAAY;KACZ,eAAc;IACf,CAAA;GACE,CAAA;EACe,CAAA,IACpB,IACkB;CACb,CAAA,CAAA,EAAA,CAAA;AAEjB;AAEA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAAuC;CACjF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,yCAAyC,SAAS;EAChE,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAAuC;CACjF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,kEACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,WAAW,EACzB,WACA,GAAG,SACkD;CACrD,OACE,oBAACA,OAAe,OAAhB;EACE,aAAU;EACV,WAAW,GAAG,sDAAsD,SAAS;EAC7E,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,iBAAiB,EAC/B,WACA,GAAG,SACwD;CAC3D,OACE,oBAACA,OAAe,aAAhB;EACE,aAAU;EACV,WAAW,GAAG,iCAAiC,SAAS;EACxD,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Platform, RESERVED_KEYS, Shortcut, describeShortcut, detectPlatform, formatShortcut, needsModifier, parseShortcut, serializeShortcut, shortcutFromKey, shortcutParts, shortcutsEqual } from "./shortcut-model.js";
|
|
2
|
+
import { ShortcutRecorder, ShortcutRecorderProps, TakenShortcut } from "./shortcut-recorder.js";
|
|
3
|
+
export { type Platform, RESERVED_KEYS, type Shortcut, ShortcutRecorder, type ShortcutRecorderProps, type TakenShortcut, describeShortcut, detectPlatform, formatShortcut, needsModifier, parseShortcut, serializeShortcut, shortcutFromKey, shortcutParts, shortcutsEqual };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { RESERVED_KEYS, describeShortcut, detectPlatform, formatShortcut, needsModifier, parseShortcut, serializeShortcut, shortcutFromKey, shortcutParts, shortcutsEqual } from "./shortcut-model.js";
|
|
2
|
+
import { ShortcutRecorder } from "./shortcut-recorder.js";
|
|
3
|
+
export { RESERVED_KEYS, ShortcutRecorder, describeShortcut, detectPlatform, formatShortcut, needsModifier, parseShortcut, serializeShortcut, shortcutFromKey, shortcutParts, shortcutsEqual };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/shortcut-recorder/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "shortcut-recorder",
|
|
5
|
+
title: "Shortcut Recorder",
|
|
6
|
+
description: "Press the keys you want: records a chord, platform-aware, and says when it clashes.",
|
|
7
|
+
category: "form",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["shortcut-model.ts", "shortcut-recorder.tsx"],
|
|
12
|
+
a11y: "The recorder is a button named by what the shortcut does and described by its value in words — Command Shift K — while the key caps on it are hidden from assistive technology, since ⌘⇧K read aloud is noise. While recording it is aria-pressed and its description says what to do; Tab always leaves and Escape always cancels, so it is never a keyboard trap. Every outcome is announced through a status region present from the start: recorded, refused with the reason, clashing with which command, cancelled, cleared. Symbols are shown on a Mac and words elsewhere, and the description uses the platform's own names for its keys."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/shortcut-recorder/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"shortcut-recorder\",\n title: \"Shortcut Recorder\",\n description:\n \"Press the keys you want: records a chord, platform-aware, and says when it clashes.\",\n category: \"form\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [],\n files: [\"shortcut-model.ts\", \"shortcut-recorder.tsx\"],\n a11y:\n \"The recorder is a button named by what the shortcut does and described by its value in \" +\n \"words — Command Shift K — while the key caps on it are hidden from assistive technology, \" +\n \"since ⌘⇧K read aloud is noise. While recording it is aria-pressed and its description \" +\n \"says what to do; Tab always leaves and Escape always cancels, so it is never a keyboard \" +\n \"trap. Every outcome is announced through a status region present from the start: recorded, \" +\n \"refused with the reason, clashing with which command, cancelled, cleared. Symbols are shown \" +\n \"on a Mac and words elsewhere, and the description uses the platform's own names for its \" +\n \"keys.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC;CACvB,OAAO,CAAC,qBAAqB,uBAAuB;CACpD,MACE;AAQJ,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/components/shortcut-recorder/shortcut-model.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A keyboard shortcut as a value: parsed from the string an app stores,
|
|
4
|
+
* built from a keydown, and written back out for a Mac, for everyone else,
|
|
5
|
+
* and for the ear.
|
|
6
|
+
*
|
|
7
|
+
* Pure, so the modifier canon, the platform rendering and the conflict rule
|
|
8
|
+
* are tested without a keyboard, and so the same `parseShortcut` can run on
|
|
9
|
+
* the server that validates a saved binding.
|
|
10
|
+
*
|
|
11
|
+
* The stored form is platform-neutral: `Mod+Shift+K`, where `Mod` is Command
|
|
12
|
+
* on a Mac and Control elsewhere. That is the one decision every app makes
|
|
13
|
+
* and few make explicitly — a binding saved as `Ctrl+K` on Windows is wrong
|
|
14
|
+
* the moment the same account opens on a Mac.
|
|
15
|
+
*/
|
|
16
|
+
type Platform = "mac" | "other";
|
|
17
|
+
interface Shortcut {
|
|
18
|
+
/** The non-modifier key, canonical: `K`, `1`, `ArrowUp`, `F5`, `Enter`, `Space`. */
|
|
19
|
+
key: string;
|
|
20
|
+
/** Command on a Mac, Control elsewhere. */
|
|
21
|
+
mod: boolean;
|
|
22
|
+
/** Control, held explicitly — on a Mac this is a different key from Mod. */
|
|
23
|
+
ctrl: boolean;
|
|
24
|
+
alt: boolean;
|
|
25
|
+
shift: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Keys that cannot be a shortcut, because they already mean something. */
|
|
28
|
+
declare const RESERVED_KEYS: Set<string>;
|
|
29
|
+
/**
|
|
30
|
+
* `Mod+Shift+K` to a shortcut. Accepts the spellings people use — `Cmd`,
|
|
31
|
+
* `Command`, `Ctrl`, `Option` — and throws on an empty or modifier-only one.
|
|
32
|
+
*/
|
|
33
|
+
declare function parseShortcut(text: string): Shortcut;
|
|
34
|
+
/** The stored, platform-neutral form. Modifiers in a fixed order, so equal shortcuts are equal strings. */
|
|
35
|
+
declare function serializeShortcut(shortcut: Shortcut): string;
|
|
36
|
+
declare function shortcutsEqual(a: Shortcut, b: Shortcut): boolean;
|
|
37
|
+
interface KeyLike {
|
|
38
|
+
key: string;
|
|
39
|
+
code?: string;
|
|
40
|
+
metaKey: boolean;
|
|
41
|
+
ctrlKey: boolean;
|
|
42
|
+
altKey: boolean;
|
|
43
|
+
shiftKey: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The shortcut a keydown amounts to, or null while only modifiers are held.
|
|
47
|
+
*
|
|
48
|
+
* Letters and digits come from `code`, not `key`: with Shift or Option held,
|
|
49
|
+
* `key` is `K`, `˚` or `∆` depending on the layout and the platform, and a
|
|
50
|
+
* shortcut recorded as Option-∆ is one nobody can read back. `Mod` is Meta
|
|
51
|
+
* on a Mac and Control elsewhere; the other one, when held, is kept as
|
|
52
|
+
* itself.
|
|
53
|
+
*/
|
|
54
|
+
declare function shortcutFromKey(event: KeyLike, platform: Platform): Shortcut | null;
|
|
55
|
+
/** Printable keys need a modifier: a bare K as a shortcut fires while someone types a sentence. */
|
|
56
|
+
declare function needsModifier(shortcut: Shortcut): boolean;
|
|
57
|
+
/** The parts to draw, in order: symbols on a Mac, words elsewhere. */
|
|
58
|
+
declare function shortcutParts(shortcut: Shortcut, platform: Platform): string[];
|
|
59
|
+
/** "⌘⇧K" on a Mac, "Ctrl+Shift+K" elsewhere. */
|
|
60
|
+
declare function formatShortcut(shortcut: Shortcut, platform: Platform): string;
|
|
61
|
+
/** "Command Shift K" — words, for the ear, whatever the screen shows. */
|
|
62
|
+
declare function describeShortcut(shortcut: Shortcut, platform: Platform): string;
|
|
63
|
+
/** Whether the browser is on a Mac, by the only signal it gives. */
|
|
64
|
+
declare function detectPlatform(): Platform;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { Platform, RESERVED_KEYS, Shortcut, describeShortcut, detectPlatform, formatShortcut, needsModifier, parseShortcut, serializeShortcut, shortcutFromKey, shortcutParts, shortcutsEqual };
|
|
67
|
+
//# sourceMappingURL=shortcut-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcut-model.d.ts","names":[],"sources":["../../../src/components/shortcut-recorder/shortcut-model.ts"],"mappings":";;;;;;;;;;;;;;;KAeY;UAEK;;EAEf;;EAEA;;EAEA;EACA;EACA;;;cAMW,eAAa;;;;;iBAgCV,cAAc,eAAe;;iBAsB7B,kBAAkB,UAAU;iBAU5B,eAAe,GAAG,UAAU,GAAG;UAIrC;EACR;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;iBAYc,gBAAgB,OAAO,SAAS,UAAU,WAAW;;iBAoBrD,cAAc,UAAU;;iBA2BxB,cAAc,UAAU,UAAU,UAAU;;iBAe5C,eAAe,UAAU,UAAU,UAAU;;iBAK7C,iBAAiB,UAAU,UAAU,UAAU;;iBAa/C,kBAAkB"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
//#region src/components/shortcut-recorder/shortcut-model.ts
|
|
2
|
+
const MODIFIER_KEYS = /* @__PURE__ */ new Set([
|
|
3
|
+
"Meta",
|
|
4
|
+
"Control",
|
|
5
|
+
"Alt",
|
|
6
|
+
"Shift",
|
|
7
|
+
"OS",
|
|
8
|
+
"AltGraph",
|
|
9
|
+
"Fn"
|
|
10
|
+
]);
|
|
11
|
+
/** Keys that cannot be a shortcut, because they already mean something. */
|
|
12
|
+
const RESERVED_KEYS = /* @__PURE__ */ new Set(["Tab", "Escape"]);
|
|
13
|
+
const KEY_ALIASES = {
|
|
14
|
+
" ": "Space",
|
|
15
|
+
spacebar: "Space",
|
|
16
|
+
esc: "Escape",
|
|
17
|
+
return: "Enter",
|
|
18
|
+
del: "Delete",
|
|
19
|
+
up: "ArrowUp",
|
|
20
|
+
down: "ArrowDown",
|
|
21
|
+
left: "ArrowLeft",
|
|
22
|
+
right: "ArrowRight",
|
|
23
|
+
cmd: "Mod",
|
|
24
|
+
command: "Mod",
|
|
25
|
+
meta: "Mod",
|
|
26
|
+
control: "Ctrl",
|
|
27
|
+
option: "Alt"
|
|
28
|
+
};
|
|
29
|
+
/** The canonical key name for a token someone typed or stored. */
|
|
30
|
+
function canonicalKey(token) {
|
|
31
|
+
const alias = KEY_ALIASES[token.toLowerCase()];
|
|
32
|
+
if (alias) return alias;
|
|
33
|
+
if (token.length === 1) return token.toUpperCase();
|
|
34
|
+
return token.charAt(0).toUpperCase() + token.slice(1);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* `Mod+Shift+K` to a shortcut. Accepts the spellings people use — `Cmd`,
|
|
38
|
+
* `Command`, `Ctrl`, `Option` — and throws on an empty or modifier-only one.
|
|
39
|
+
*/
|
|
40
|
+
function parseShortcut(text) {
|
|
41
|
+
const tokens = text.split("+").map((token) => token.trim()).filter(Boolean);
|
|
42
|
+
if (tokens.length === 0) throw new Error("Enter a shortcut.");
|
|
43
|
+
const shortcut = {
|
|
44
|
+
key: "",
|
|
45
|
+
mod: false,
|
|
46
|
+
ctrl: false,
|
|
47
|
+
alt: false,
|
|
48
|
+
shift: false
|
|
49
|
+
};
|
|
50
|
+
for (const token of tokens) {
|
|
51
|
+
const canonical = canonicalKey(token);
|
|
52
|
+
if (canonical === "Mod") shortcut.mod = true;
|
|
53
|
+
else if (canonical === "Ctrl") shortcut.ctrl = true;
|
|
54
|
+
else if (canonical === "Alt") shortcut.alt = true;
|
|
55
|
+
else if (canonical === "Shift") shortcut.shift = true;
|
|
56
|
+
else if (shortcut.key) throw new Error(`"${text}" has two keys; a shortcut has one.`);
|
|
57
|
+
else shortcut.key = canonical;
|
|
58
|
+
}
|
|
59
|
+
if (!shortcut.key) throw new Error(`"${text}" is only modifiers; it needs a key.`);
|
|
60
|
+
return shortcut;
|
|
61
|
+
}
|
|
62
|
+
/** The stored, platform-neutral form. Modifiers in a fixed order, so equal shortcuts are equal strings. */
|
|
63
|
+
function serializeShortcut(shortcut) {
|
|
64
|
+
const parts = [];
|
|
65
|
+
if (shortcut.mod) parts.push("Mod");
|
|
66
|
+
if (shortcut.ctrl) parts.push("Ctrl");
|
|
67
|
+
if (shortcut.alt) parts.push("Alt");
|
|
68
|
+
if (shortcut.shift) parts.push("Shift");
|
|
69
|
+
parts.push(shortcut.key);
|
|
70
|
+
return parts.join("+");
|
|
71
|
+
}
|
|
72
|
+
function shortcutsEqual(a, b) {
|
|
73
|
+
return serializeShortcut(a) === serializeShortcut(b);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The shortcut a keydown amounts to, or null while only modifiers are held.
|
|
77
|
+
*
|
|
78
|
+
* Letters and digits come from `code`, not `key`: with Shift or Option held,
|
|
79
|
+
* `key` is `K`, `˚` or `∆` depending on the layout and the platform, and a
|
|
80
|
+
* shortcut recorded as Option-∆ is one nobody can read back. `Mod` is Meta
|
|
81
|
+
* on a Mac and Control elsewhere; the other one, when held, is kept as
|
|
82
|
+
* itself.
|
|
83
|
+
*/
|
|
84
|
+
function shortcutFromKey(event, platform) {
|
|
85
|
+
if (MODIFIER_KEYS.has(event.key)) return null;
|
|
86
|
+
let key;
|
|
87
|
+
const code = event.code ?? "";
|
|
88
|
+
if (/^Key[A-Z]$/.test(code)) key = code.slice(3);
|
|
89
|
+
else if (/^Digit[0-9]$/.test(code)) key = code.slice(5);
|
|
90
|
+
else key = canonicalKey(event.key);
|
|
91
|
+
const mac = platform === "mac";
|
|
92
|
+
return {
|
|
93
|
+
key,
|
|
94
|
+
mod: mac ? event.metaKey : event.ctrlKey,
|
|
95
|
+
ctrl: mac ? event.ctrlKey : false,
|
|
96
|
+
alt: event.altKey,
|
|
97
|
+
shift: event.shiftKey
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Printable keys need a modifier: a bare K as a shortcut fires while someone types a sentence. */
|
|
101
|
+
function needsModifier(shortcut) {
|
|
102
|
+
return (shortcut.key.length === 1 || shortcut.key === "Space") && !shortcut.mod && !shortcut.ctrl && !shortcut.alt;
|
|
103
|
+
}
|
|
104
|
+
const MAC_SYMBOLS = {
|
|
105
|
+
Mod: "⌘",
|
|
106
|
+
Ctrl: "⌃",
|
|
107
|
+
Alt: "⌥",
|
|
108
|
+
Shift: "⇧",
|
|
109
|
+
Enter: "↩",
|
|
110
|
+
Backspace: "⌫",
|
|
111
|
+
Delete: "⌦",
|
|
112
|
+
ArrowUp: "↑",
|
|
113
|
+
ArrowDown: "↓",
|
|
114
|
+
ArrowLeft: "←",
|
|
115
|
+
ArrowRight: "→",
|
|
116
|
+
Escape: "⎋",
|
|
117
|
+
Space: "Space"
|
|
118
|
+
};
|
|
119
|
+
const SPOKEN = {
|
|
120
|
+
mac: {
|
|
121
|
+
Mod: "Command",
|
|
122
|
+
Ctrl: "Control",
|
|
123
|
+
Alt: "Option",
|
|
124
|
+
Shift: "Shift"
|
|
125
|
+
},
|
|
126
|
+
other: {
|
|
127
|
+
Mod: "Control",
|
|
128
|
+
Ctrl: "Control",
|
|
129
|
+
Alt: "Alt",
|
|
130
|
+
Shift: "Shift"
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
/** The parts to draw, in order: symbols on a Mac, words elsewhere. */
|
|
134
|
+
function shortcutParts(shortcut, platform) {
|
|
135
|
+
const parts = [];
|
|
136
|
+
const name = (modifier) => platform === "mac" ? MAC_SYMBOLS[modifier] ?? modifier : SPOKEN.other[modifier] ?? modifier;
|
|
137
|
+
if (shortcut.mod) parts.push(name("Mod"));
|
|
138
|
+
if (shortcut.ctrl && !(platform === "other" && shortcut.mod)) parts.push(name("Ctrl"));
|
|
139
|
+
if (shortcut.alt) parts.push(name("Alt"));
|
|
140
|
+
if (shortcut.shift) parts.push(name("Shift"));
|
|
141
|
+
parts.push(platform === "mac" ? MAC_SYMBOLS[shortcut.key] ?? shortcut.key : shortcut.key);
|
|
142
|
+
return parts;
|
|
143
|
+
}
|
|
144
|
+
/** "⌘⇧K" on a Mac, "Ctrl+Shift+K" elsewhere. */
|
|
145
|
+
function formatShortcut(shortcut, platform) {
|
|
146
|
+
return shortcutParts(shortcut, platform).join(platform === "mac" ? "" : "+");
|
|
147
|
+
}
|
|
148
|
+
/** "Command Shift K" — words, for the ear, whatever the screen shows. */
|
|
149
|
+
function describeShortcut(shortcut, platform) {
|
|
150
|
+
const words = [];
|
|
151
|
+
const spoken = SPOKEN[platform];
|
|
152
|
+
if (shortcut.mod) words.push(spoken.Mod ?? "Mod");
|
|
153
|
+
if (shortcut.ctrl && !(platform === "other" && shortcut.mod)) words.push(spoken.Ctrl ?? "Ctrl");
|
|
154
|
+
if (shortcut.alt) words.push(spoken.Alt ?? "Alt");
|
|
155
|
+
if (shortcut.shift) words.push(spoken.Shift ?? "Shift");
|
|
156
|
+
words.push(shortcut.key.replace(/^Arrow/, "Arrow "));
|
|
157
|
+
return words.join(" ");
|
|
158
|
+
}
|
|
159
|
+
/** Whether the browser is on a Mac, by the only signal it gives. */
|
|
160
|
+
function detectPlatform() {
|
|
161
|
+
if (typeof navigator === "undefined") return "other";
|
|
162
|
+
const hint = navigator.userAgentData?.platform ?? navigator.platform;
|
|
163
|
+
return /mac|iphone|ipad|ipod/i.test(hint) ? "mac" : "other";
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { RESERVED_KEYS, describeShortcut, detectPlatform, formatShortcut, needsModifier, parseShortcut, serializeShortcut, shortcutFromKey, shortcutParts, shortcutsEqual };
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=shortcut-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcut-model.js","names":[],"sources":["../../../src/components/shortcut-recorder/shortcut-model.ts"],"sourcesContent":["/**\n * A keyboard shortcut as a value: parsed from the string an app stores,\n * built from a keydown, and written back out for a Mac, for everyone else,\n * and for the ear.\n *\n * Pure, so the modifier canon, the platform rendering and the conflict rule\n * are tested without a keyboard, and so the same `parseShortcut` can run on\n * the server that validates a saved binding.\n *\n * The stored form is platform-neutral: `Mod+Shift+K`, where `Mod` is Command\n * on a Mac and Control elsewhere. That is the one decision every app makes\n * and few make explicitly — a binding saved as `Ctrl+K` on Windows is wrong\n * the moment the same account opens on a Mac.\n */\n\nexport type Platform = \"mac\" | \"other\";\n\nexport interface Shortcut {\n /** The non-modifier key, canonical: `K`, `1`, `ArrowUp`, `F5`, `Enter`, `Space`. */\n key: string;\n /** Command on a Mac, Control elsewhere. */\n mod: boolean;\n /** Control, held explicitly — on a Mac this is a different key from Mod. */\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n}\n\nconst MODIFIER_KEYS = new Set([\"Meta\", \"Control\", \"Alt\", \"Shift\", \"OS\", \"AltGraph\", \"Fn\"]);\n\n/** Keys that cannot be a shortcut, because they already mean something. */\nexport const RESERVED_KEYS = new Set([\"Tab\", \"Escape\"]);\n\nconst KEY_ALIASES: Record<string, string> = {\n \" \": \"Space\",\n spacebar: \"Space\",\n esc: \"Escape\",\n return: \"Enter\",\n del: \"Delete\",\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n cmd: \"Mod\",\n command: \"Mod\",\n meta: \"Mod\",\n control: \"Ctrl\",\n option: \"Alt\",\n};\n\n/** The canonical key name for a token someone typed or stored. */\nfunction canonicalKey(token: string): string {\n const alias = KEY_ALIASES[token.toLowerCase()];\n if (alias) return alias;\n if (token.length === 1) return token.toUpperCase();\n // ArrowUp, F5, Enter, Backspace, Delete, Home, End, PageUp…\n return token.charAt(0).toUpperCase() + token.slice(1);\n}\n\n/**\n * `Mod+Shift+K` to a shortcut. Accepts the spellings people use — `Cmd`,\n * `Command`, `Ctrl`, `Option` — and throws on an empty or modifier-only one.\n */\nexport function parseShortcut(text: string): Shortcut {\n const tokens = text\n .split(\"+\")\n .map((token) => token.trim())\n .filter(Boolean);\n if (tokens.length === 0) throw new Error(\"Enter a shortcut.\");\n\n const shortcut: Shortcut = { key: \"\", mod: false, ctrl: false, alt: false, shift: false };\n for (const token of tokens) {\n const canonical = canonicalKey(token);\n if (canonical === \"Mod\") shortcut.mod = true;\n else if (canonical === \"Ctrl\") shortcut.ctrl = true;\n else if (canonical === \"Alt\") shortcut.alt = true;\n else if (canonical === \"Shift\") shortcut.shift = true;\n else if (shortcut.key) throw new Error(`\"${text}\" has two keys; a shortcut has one.`);\n else shortcut.key = canonical;\n }\n if (!shortcut.key) throw new Error(`\"${text}\" is only modifiers; it needs a key.`);\n return shortcut;\n}\n\n/** The stored, platform-neutral form. Modifiers in a fixed order, so equal shortcuts are equal strings. */\nexport function serializeShortcut(shortcut: Shortcut): string {\n const parts: string[] = [];\n if (shortcut.mod) parts.push(\"Mod\");\n if (shortcut.ctrl) parts.push(\"Ctrl\");\n if (shortcut.alt) parts.push(\"Alt\");\n if (shortcut.shift) parts.push(\"Shift\");\n parts.push(shortcut.key);\n return parts.join(\"+\");\n}\n\nexport function shortcutsEqual(a: Shortcut, b: Shortcut): boolean {\n return serializeShortcut(a) === serializeShortcut(b);\n}\n\ninterface KeyLike {\n key: string;\n code?: string;\n metaKey: boolean;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n}\n\n/**\n * The shortcut a keydown amounts to, or null while only modifiers are held.\n *\n * Letters and digits come from `code`, not `key`: with Shift or Option held,\n * `key` is `K`, `˚` or `∆` depending on the layout and the platform, and a\n * shortcut recorded as Option-∆ is one nobody can read back. `Mod` is Meta\n * on a Mac and Control elsewhere; the other one, when held, is kept as\n * itself.\n */\nexport function shortcutFromKey(event: KeyLike, platform: Platform): Shortcut | null {\n if (MODIFIER_KEYS.has(event.key)) return null;\n\n let key: string;\n const code = event.code ?? \"\";\n if (/^Key[A-Z]$/.test(code)) key = code.slice(3);\n else if (/^Digit[0-9]$/.test(code)) key = code.slice(5);\n else key = canonicalKey(event.key);\n\n const mac = platform === \"mac\";\n return {\n key,\n mod: mac ? event.metaKey : event.ctrlKey,\n ctrl: mac ? event.ctrlKey : false,\n alt: event.altKey,\n shift: event.shiftKey,\n };\n}\n\n/** Printable keys need a modifier: a bare K as a shortcut fires while someone types a sentence. */\nexport function needsModifier(shortcut: Shortcut): boolean {\n const printable = shortcut.key.length === 1 || shortcut.key === \"Space\";\n return printable && !shortcut.mod && !shortcut.ctrl && !shortcut.alt;\n}\n\nconst MAC_SYMBOLS: Record<string, string> = {\n Mod: \"⌘\",\n Ctrl: \"⌃\",\n Alt: \"⌥\",\n Shift: \"⇧\",\n Enter: \"↩\",\n Backspace: \"⌫\",\n Delete: \"⌦\",\n ArrowUp: \"↑\",\n ArrowDown: \"↓\",\n ArrowLeft: \"←\",\n ArrowRight: \"→\",\n Escape: \"⎋\",\n Space: \"Space\",\n};\n\nconst SPOKEN: Record<Platform, Record<string, string>> = {\n mac: { Mod: \"Command\", Ctrl: \"Control\", Alt: \"Option\", Shift: \"Shift\" },\n other: { Mod: \"Control\", Ctrl: \"Control\", Alt: \"Alt\", Shift: \"Shift\" },\n};\n\n/** The parts to draw, in order: symbols on a Mac, words elsewhere. */\nexport function shortcutParts(shortcut: Shortcut, platform: Platform): string[] {\n const parts: string[] = [];\n const name = (modifier: string) =>\n platform === \"mac\"\n ? (MAC_SYMBOLS[modifier] ?? modifier)\n : (SPOKEN.other[modifier] ?? modifier);\n if (shortcut.mod) parts.push(name(\"Mod\"));\n if (shortcut.ctrl && !(platform === \"other\" && shortcut.mod)) parts.push(name(\"Ctrl\"));\n if (shortcut.alt) parts.push(name(\"Alt\"));\n if (shortcut.shift) parts.push(name(\"Shift\"));\n parts.push(platform === \"mac\" ? (MAC_SYMBOLS[shortcut.key] ?? shortcut.key) : shortcut.key);\n return parts;\n}\n\n/** \"⌘⇧K\" on a Mac, \"Ctrl+Shift+K\" elsewhere. */\nexport function formatShortcut(shortcut: Shortcut, platform: Platform): string {\n return shortcutParts(shortcut, platform).join(platform === \"mac\" ? \"\" : \"+\");\n}\n\n/** \"Command Shift K\" — words, for the ear, whatever the screen shows. */\nexport function describeShortcut(shortcut: Shortcut, platform: Platform): string {\n const words: string[] = [];\n const spoken = SPOKEN[platform];\n if (shortcut.mod) words.push(spoken.Mod ?? \"Mod\");\n if (shortcut.ctrl && !(platform === \"other\" && shortcut.mod))\n words.push(spoken.Ctrl ?? \"Ctrl\");\n if (shortcut.alt) words.push(spoken.Alt ?? \"Alt\");\n if (shortcut.shift) words.push(spoken.Shift ?? \"Shift\");\n words.push(shortcut.key.replace(/^Arrow/, \"Arrow \"));\n return words.join(\" \");\n}\n\n/** Whether the browser is on a Mac, by the only signal it gives. */\nexport function detectPlatform(): Platform {\n if (typeof navigator === \"undefined\") return \"other\";\n const hint =\n (navigator as { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform;\n return /mac|iphone|ipad|ipod/i.test(hint) ? \"mac\" : \"other\";\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Platform } from "./shortcut-model.js";
|
|
2
|
+
import { ComponentPropsWithRef } from "react";
|
|
3
|
+
//#region src/components/shortcut-recorder/shortcut-recorder.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Press the keys you want.
|
|
6
|
+
*
|
|
7
|
+
* Linear, Slack, VS Code, Figma and Superhuman each wrote one, and no
|
|
8
|
+
* component library ships the recorder — only the `<kbd>` that displays the
|
|
9
|
+
* result. The recorder is the hard part: a button that, when pressed, stops
|
|
10
|
+
* being a button and starts being a keyboard listener, and has to come back.
|
|
11
|
+
*
|
|
12
|
+
* Three things it does that a listener bolted onto an input does not. It
|
|
13
|
+
* reads letters from `code`, not `key`, so Option-K records as Option K and
|
|
14
|
+
* not as the ˚ the Mac produced. It stores `Mod`, not Command or Control,
|
|
15
|
+
* so a binding saved on one machine is right on the other. And it refuses a
|
|
16
|
+
* bare printable key by saying so, because a shortcut that fires while
|
|
17
|
+
* someone types a sentence is the bug every app that allowed it later fixed.
|
|
18
|
+
*
|
|
19
|
+
* Tab and Escape are never recorded: Tab leaves, Escape cancels, and a
|
|
20
|
+
* recorder that captures both is a keyboard trap with a nice label. A
|
|
21
|
+
* chord that another command already uses is said, with that command's
|
|
22
|
+
* name, and applied only if the person says to use it anyway.
|
|
23
|
+
*/
|
|
24
|
+
interface TakenShortcut {
|
|
25
|
+
/** Stored form: `Mod+K`. */
|
|
26
|
+
shortcut: string;
|
|
27
|
+
/** What it does: "Search". */
|
|
28
|
+
label: string;
|
|
29
|
+
}
|
|
30
|
+
interface ShortcutRecorderProps extends Omit<ComponentPropsWithRef<"div">, "onChange" | "defaultValue"> {
|
|
31
|
+
/** What the shortcut triggers: "Open search". Names the control. */
|
|
32
|
+
label: string;
|
|
33
|
+
/** Stored form, `Mod+Shift+K`, or null for none. Controlled. */
|
|
34
|
+
value?: string | null;
|
|
35
|
+
defaultValue?: string | null;
|
|
36
|
+
onChange?: (value: string | null) => void;
|
|
37
|
+
/** Shortcuts other commands already use. A clash is said, and applied only on request. */
|
|
38
|
+
taken?: TakenShortcut[];
|
|
39
|
+
/** Mac shows ⌘⇧K; everything else shows Ctrl+Shift+K. Detected if omitted. */
|
|
40
|
+
platform?: Platform;
|
|
41
|
+
/** Printable keys need a modifier. On by default, for the reason in the source. */
|
|
42
|
+
requireModifier?: boolean;
|
|
43
|
+
}
|
|
44
|
+
declare function ShortcutRecorder({ className, label, value: valueProp, defaultValue, onChange, taken, platform: platformProp, requireModifier, ...props }: ShortcutRecorderProps): import("react").JSX.Element;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { ShortcutRecorder, ShortcutRecorderProps, TakenShortcut };
|
|
47
|
+
//# sourceMappingURL=shortcut-recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcut-recorder.d.ts","names":[],"sources":["../../../src/components/shortcut-recorder/shortcut-recorder.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;UA+CiB;;EAEf;;EAEA;;UAGe,8BAA8B,KAC7C;;EAIA;;EAEA;EACA;EACA,YAAY;;EAEZ,QAAQ;;EAER,WAAW;;EAEX;;iBAUc,mBACd,WACA,OACA,OAAO,WACP,cACA,UACA,OACA,UAAU,cACV,oBACG,SACF,wCAAqB,IAAA"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { disabledStyles, focusRing } from "../../lib/styles.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { describeShortcut, detectPlatform, needsModifier, parseShortcut, serializeShortcut, shortcutFromKey, shortcutParts, shortcutsEqual } from "./shortcut-model.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useId, useState, useSyncExternalStore } from "react";
|
|
7
|
+
//#region src/components/shortcut-recorder/shortcut-recorder.tsx
|
|
8
|
+
const subscribeToNothing = () => () => void 0;
|
|
9
|
+
const readPlatform = () => detectPlatform();
|
|
10
|
+
const readNeutralPlatform = () => "other";
|
|
11
|
+
function ShortcutRecorder({ className, label, value: valueProp, defaultValue = null, onChange, taken = [], platform: platformProp, requireModifier = true, ...props }) {
|
|
12
|
+
const id = useId();
|
|
13
|
+
const detected = useSyncExternalStore(subscribeToNothing, readPlatform, readNeutralPlatform);
|
|
14
|
+
const platform = platformProp ?? detected;
|
|
15
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
16
|
+
const value = valueProp === void 0 ? uncontrolled : valueProp;
|
|
17
|
+
const current = value ? safeParse(value) : null;
|
|
18
|
+
const [recording, setRecording] = useState(false);
|
|
19
|
+
/** Modifiers held mid-chord, shown so the person sees the recorder listening. */
|
|
20
|
+
const [held, setHeld] = useState(null);
|
|
21
|
+
const [conflict, setConflict] = useState(null);
|
|
22
|
+
const [announcement, setAnnouncement] = useState("");
|
|
23
|
+
const commit = (next) => {
|
|
24
|
+
const serialized = next ? serializeShortcut(next) : null;
|
|
25
|
+
setUncontrolled(serialized);
|
|
26
|
+
onChange?.(serialized);
|
|
27
|
+
};
|
|
28
|
+
const stop = () => {
|
|
29
|
+
setRecording(false);
|
|
30
|
+
setHeld(null);
|
|
31
|
+
};
|
|
32
|
+
const onKeyDown = (event) => {
|
|
33
|
+
if (!recording) return;
|
|
34
|
+
if (event.key === "Tab") {
|
|
35
|
+
stop();
|
|
36
|
+
setAnnouncement("Cancelled.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
if (event.key === "Escape") {
|
|
41
|
+
stop();
|
|
42
|
+
setAnnouncement("Cancelled.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if ((event.key === "Backspace" || event.key === "Delete") && !hasModifier(event)) {
|
|
46
|
+
stop();
|
|
47
|
+
commit(null);
|
|
48
|
+
setAnnouncement("Cleared.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const chord = shortcutFromKey(event.nativeEvent, platform);
|
|
52
|
+
if (!chord) {
|
|
53
|
+
setHeld({
|
|
54
|
+
key: "",
|
|
55
|
+
mod: platform === "mac" ? event.metaKey : event.ctrlKey,
|
|
56
|
+
ctrl: platform === "mac" ? event.ctrlKey : false,
|
|
57
|
+
alt: event.altKey,
|
|
58
|
+
shift: event.shiftKey
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (requireModifier && needsModifier(chord)) {
|
|
63
|
+
setAnnouncement(`${describeShortcut(chord, platform)} needs a modifier — it would fire while typing. Try again.`);
|
|
64
|
+
setHeld(null);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const clash = taken.find((entry) => shortcutsEqual(safeParse(entry.shortcut) ?? chord, chord));
|
|
68
|
+
stop();
|
|
69
|
+
if (clash && !(current && shortcutsEqual(current, chord))) {
|
|
70
|
+
setConflict({
|
|
71
|
+
shortcut: chord,
|
|
72
|
+
with: clash
|
|
73
|
+
});
|
|
74
|
+
setAnnouncement(`${describeShortcut(chord, platform)} is already used by ${clash.label}.`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
commit(chord);
|
|
78
|
+
setAnnouncement(`Recorded ${describeShortcut(chord, platform)}.`);
|
|
79
|
+
};
|
|
80
|
+
const onKeyUp = (event) => {
|
|
81
|
+
if (!recording || !held) return;
|
|
82
|
+
setHeld({
|
|
83
|
+
key: "",
|
|
84
|
+
mod: platform === "mac" ? event.metaKey : event.ctrlKey,
|
|
85
|
+
ctrl: platform === "mac" ? event.ctrlKey : false,
|
|
86
|
+
alt: event.altKey,
|
|
87
|
+
shift: event.shiftKey
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const shown = recording ? held : current;
|
|
91
|
+
const description = recording ? "Press the keys you want. Escape cancels, Backspace clears." : current ? `${describeShortcut(current, platform)}. Press to change.` : "Not set. Press to record.";
|
|
92
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
93
|
+
"data-slot": "shortcut-recorder",
|
|
94
|
+
"data-state": recording ? "recording" : conflict ? "conflict" : current ? "set" : "empty",
|
|
95
|
+
className: cn("flex flex-col gap-1.5", className),
|
|
96
|
+
...props,
|
|
97
|
+
children: [
|
|
98
|
+
/* @__PURE__ */ jsx("span", {
|
|
99
|
+
id: `${id}-label`,
|
|
100
|
+
className: "text-xs font-medium",
|
|
101
|
+
children: label
|
|
102
|
+
}),
|
|
103
|
+
/* @__PURE__ */ jsxs("div", {
|
|
104
|
+
className: "flex flex-wrap items-center gap-2",
|
|
105
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
106
|
+
type: "button",
|
|
107
|
+
"aria-labelledby": `${id}-label`,
|
|
108
|
+
"aria-describedby": `${id}-description`,
|
|
109
|
+
"aria-pressed": recording,
|
|
110
|
+
className: cn("flex h-9 min-w-32 items-center justify-center gap-1 rounded-md border px-3 font-mono text-sm", recording ? "border-primary bg-primary/5 text-muted-foreground" : "border-input bg-background hover:bg-accent", focusRing, disabledStyles),
|
|
111
|
+
onClick: () => {
|
|
112
|
+
if (recording) return;
|
|
113
|
+
setConflict(null);
|
|
114
|
+
setRecording(true);
|
|
115
|
+
setAnnouncement("");
|
|
116
|
+
},
|
|
117
|
+
onKeyDown,
|
|
118
|
+
onKeyUp,
|
|
119
|
+
onBlur: () => {
|
|
120
|
+
if (recording) {
|
|
121
|
+
stop();
|
|
122
|
+
setAnnouncement("Cancelled.");
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
126
|
+
"aria-hidden": "true",
|
|
127
|
+
className: "flex items-center gap-1",
|
|
128
|
+
children: shown && (shown.key || shown.mod || shown.ctrl || shown.alt || shown.shift) ? shortcutParts(shown, platform).filter(Boolean).map((part, index) => /* @__PURE__ */ jsx("kbd", {
|
|
129
|
+
className: "rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-xs",
|
|
130
|
+
children: part
|
|
131
|
+
}, index)) : recording ? /* @__PURE__ */ jsx("span", {
|
|
132
|
+
className: "text-xs",
|
|
133
|
+
children: "Press keys…"
|
|
134
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
135
|
+
className: "text-xs text-muted-foreground",
|
|
136
|
+
children: "Not set"
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
}), current && !recording ? /* @__PURE__ */ jsx("button", {
|
|
140
|
+
type: "button",
|
|
141
|
+
"aria-label": `Clear — ${label}`,
|
|
142
|
+
className: cn("rounded-md border border-input bg-background px-2.5 py-1 text-xs font-medium hover:bg-accent", focusRing),
|
|
143
|
+
onClick: () => {
|
|
144
|
+
commit(null);
|
|
145
|
+
setConflict(null);
|
|
146
|
+
setAnnouncement("Cleared.");
|
|
147
|
+
},
|
|
148
|
+
children: "Clear"
|
|
149
|
+
}) : null]
|
|
150
|
+
}),
|
|
151
|
+
/* @__PURE__ */ jsx("p", {
|
|
152
|
+
id: `${id}-description`,
|
|
153
|
+
className: "text-xs text-muted-foreground",
|
|
154
|
+
children: description
|
|
155
|
+
}),
|
|
156
|
+
conflict ? /* @__PURE__ */ jsxs("div", {
|
|
157
|
+
"data-slot": "shortcut-conflict",
|
|
158
|
+
className: "flex flex-wrap items-center gap-2 rounded-md border border-warning/50 bg-warning/5 px-2.5 py-1.5 text-xs",
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ jsxs("span", {
|
|
161
|
+
className: "flex-1 text-warning",
|
|
162
|
+
children: [
|
|
163
|
+
describeShortcut(conflict.shortcut, platform),
|
|
164
|
+
" is already used by",
|
|
165
|
+
" ",
|
|
166
|
+
/* @__PURE__ */ jsx("strong", { children: conflict.with.label }),
|
|
167
|
+
"."
|
|
168
|
+
]
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ jsx("button", {
|
|
171
|
+
type: "button",
|
|
172
|
+
className: cn("rounded-md border border-input bg-background px-2 py-0.5 font-medium hover:bg-accent", focusRing),
|
|
173
|
+
onClick: () => {
|
|
174
|
+
commit(conflict.shortcut);
|
|
175
|
+
setConflict(null);
|
|
176
|
+
setAnnouncement(`Recorded ${describeShortcut(conflict.shortcut, platform)}. ${conflict.with.label} no longer has a shortcut.`);
|
|
177
|
+
},
|
|
178
|
+
children: "Use anyway"
|
|
179
|
+
}),
|
|
180
|
+
/* @__PURE__ */ jsx("button", {
|
|
181
|
+
type: "button",
|
|
182
|
+
className: cn("rounded-md border border-input bg-background px-2 py-0.5 font-medium hover:bg-accent", focusRing),
|
|
183
|
+
onClick: () => {
|
|
184
|
+
setConflict(null);
|
|
185
|
+
},
|
|
186
|
+
children: "Keep the old one"
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
}) : null,
|
|
190
|
+
/* @__PURE__ */ jsx("span", {
|
|
191
|
+
role: "status",
|
|
192
|
+
"aria-live": "polite",
|
|
193
|
+
className: "sr-only",
|
|
194
|
+
children: announcement
|
|
195
|
+
})
|
|
196
|
+
]
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function hasModifier(event) {
|
|
200
|
+
return event.metaKey || event.ctrlKey || event.altKey || event.shiftKey;
|
|
201
|
+
}
|
|
202
|
+
function safeParse(text) {
|
|
203
|
+
try {
|
|
204
|
+
return parseShortcut(text);
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
export { ShortcutRecorder };
|
|
211
|
+
|
|
212
|
+
//# sourceMappingURL=shortcut-recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcut-recorder.js","names":[],"sources":["../../../src/components/shortcut-recorder/shortcut-recorder.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useId,\n useState,\n useSyncExternalStore,\n type ComponentPropsWithRef,\n type KeyboardEvent,\n} from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n describeShortcut,\n detectPlatform,\n needsModifier,\n parseShortcut,\n serializeShortcut,\n shortcutFromKey,\n shortcutParts,\n shortcutsEqual,\n type Platform,\n type Shortcut,\n} from \"./shortcut-model\";\n\n/**\n * Press the keys you want.\n *\n * Linear, Slack, VS Code, Figma and Superhuman each wrote one, and no\n * component library ships the recorder — only the `<kbd>` that displays the\n * result. The recorder is the hard part: a button that, when pressed, stops\n * being a button and starts being a keyboard listener, and has to come back.\n *\n * Three things it does that a listener bolted onto an input does not. It\n * reads letters from `code`, not `key`, so Option-K records as Option K and\n * not as the ˚ the Mac produced. It stores `Mod`, not Command or Control,\n * so a binding saved on one machine is right on the other. And it refuses a\n * bare printable key by saying so, because a shortcut that fires while\n * someone types a sentence is the bug every app that allowed it later fixed.\n *\n * Tab and Escape are never recorded: Tab leaves, Escape cancels, and a\n * recorder that captures both is a keyboard trap with a nice label. A\n * chord that another command already uses is said, with that command's\n * name, and applied only if the person says to use it anyway.\n */\n\nexport interface TakenShortcut {\n /** Stored form: `Mod+K`. */\n shortcut: string;\n /** What it does: \"Search\". */\n label: string;\n}\n\nexport interface ShortcutRecorderProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"onChange\" | \"defaultValue\"\n> {\n /** What the shortcut triggers: \"Open search\". Names the control. */\n label: string;\n /** Stored form, `Mod+Shift+K`, or null for none. Controlled. */\n value?: string | null;\n defaultValue?: string | null;\n onChange?: (value: string | null) => void;\n /** Shortcuts other commands already use. A clash is said, and applied only on request. */\n taken?: TakenShortcut[];\n /** Mac shows ⌘⇧K; everything else shows Ctrl+Shift+K. Detected if omitted. */\n platform?: Platform;\n /** Printable keys need a modifier. On by default, for the reason in the source. */\n requireModifier?: boolean;\n}\n\n/* The platform, as an external store that never changes: a constant that the\n server cannot know, so the server snapshot is the neutral one and the real\n answer arrives after hydration without a mismatch. */\nconst subscribeToNothing = () => () => undefined;\nconst readPlatform = () => detectPlatform();\nconst readNeutralPlatform = (): Platform => \"other\";\n\nexport function ShortcutRecorder({\n className,\n label,\n value: valueProp,\n defaultValue = null,\n onChange,\n taken = [],\n platform: platformProp,\n requireModifier = true,\n ...props\n}: ShortcutRecorderProps) {\n const id = useId();\n const detected = useSyncExternalStore(subscribeToNothing, readPlatform, readNeutralPlatform);\n const platform = platformProp ?? detected;\n\n const [uncontrolled, setUncontrolled] = useState<string | null>(defaultValue);\n const value = valueProp === undefined ? uncontrolled : valueProp;\n const current = value ? safeParse(value) : null;\n\n const [recording, setRecording] = useState(false);\n /** Modifiers held mid-chord, shown so the person sees the recorder listening. */\n const [held, setHeld] = useState<Shortcut | null>(null);\n const [conflict, setConflict] = useState<{ shortcut: Shortcut; with: TakenShortcut } | null>(\n null,\n );\n const [announcement, setAnnouncement] = useState(\"\");\n\n const commit = (next: Shortcut | null) => {\n const serialized = next ? serializeShortcut(next) : null;\n setUncontrolled(serialized);\n onChange?.(serialized);\n };\n\n const stop = () => {\n setRecording(false);\n setHeld(null);\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (!recording) return;\n // Tab leaves. A recorder that keeps it is a keyboard trap.\n if (event.key === \"Tab\") {\n stop();\n setAnnouncement(\"Cancelled.\");\n return;\n }\n event.preventDefault();\n if (event.key === \"Escape\") {\n stop();\n setAnnouncement(\"Cancelled.\");\n return;\n }\n if ((event.key === \"Backspace\" || event.key === \"Delete\") && !hasModifier(event)) {\n stop();\n commit(null);\n setAnnouncement(\"Cleared.\");\n return;\n }\n\n const chord = shortcutFromKey(event.nativeEvent, platform);\n if (!chord) {\n // Only modifiers so far. Show them, keep listening.\n setHeld({\n key: \"\",\n mod: platform === \"mac\" ? event.metaKey : event.ctrlKey,\n ctrl: platform === \"mac\" ? event.ctrlKey : false,\n alt: event.altKey,\n shift: event.shiftKey,\n });\n return;\n }\n\n if (requireModifier && needsModifier(chord)) {\n setAnnouncement(\n `${describeShortcut(chord, platform)} needs a modifier — it would fire while typing. Try again.`,\n );\n setHeld(null);\n return;\n }\n\n const clash = taken.find((entry) =>\n shortcutsEqual(safeParse(entry.shortcut) ?? chord, chord),\n );\n stop();\n if (clash && !(current && shortcutsEqual(current, chord))) {\n setConflict({ shortcut: chord, with: clash });\n setAnnouncement(\n `${describeShortcut(chord, platform)} is already used by ${clash.label}.`,\n );\n return;\n }\n commit(chord);\n setAnnouncement(`Recorded ${describeShortcut(chord, platform)}.`);\n };\n\n const onKeyUp = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (!recording || !held) return;\n setHeld({\n key: \"\",\n mod: platform === \"mac\" ? event.metaKey : event.ctrlKey,\n ctrl: platform === \"mac\" ? event.ctrlKey : false,\n alt: event.altKey,\n shift: event.shiftKey,\n });\n };\n\n const shown = recording ? held : current;\n const description = recording\n ? \"Press the keys you want. Escape cancels, Backspace clears.\"\n : current\n ? `${describeShortcut(current, platform)}. Press to change.`\n : \"Not set. Press to record.\";\n\n return (\n <div\n data-slot=\"shortcut-recorder\"\n data-state={recording ? \"recording\" : conflict ? \"conflict\" : current ? \"set\" : \"empty\"}\n className={cn(\"flex flex-col gap-1.5\", className)}\n {...props}\n >\n <span id={`${id}-label`} className=\"text-xs font-medium\">\n {label}\n </span>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n aria-labelledby={`${id}-label`}\n aria-describedby={`${id}-description`}\n aria-pressed={recording}\n className={cn(\n \"flex h-9 min-w-32 items-center justify-center gap-1 rounded-md border px-3 font-mono text-sm\",\n recording\n ? \"border-primary bg-primary/5 text-muted-foreground\"\n : \"border-input bg-background hover:bg-accent\",\n focusRing,\n disabledStyles,\n )}\n onClick={() => {\n if (recording) return;\n setConflict(null);\n setRecording(true);\n setAnnouncement(\"\");\n }}\n onKeyDown={onKeyDown}\n onKeyUp={onKeyUp}\n onBlur={() => {\n if (recording) {\n stop();\n setAnnouncement(\"Cancelled.\");\n }\n }}\n >\n {/* Symbols and key caps are for the eye; the description says the\n same thing in words. */}\n <span aria-hidden=\"true\" className=\"flex items-center gap-1\">\n {shown && (shown.key || shown.mod || shown.ctrl || shown.alt || shown.shift) ? (\n shortcutParts(shown, platform)\n .filter(Boolean)\n .map((part, index) => (\n <kbd\n key={index}\n className=\"rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-xs\"\n >\n {part}\n </kbd>\n ))\n ) : recording ? (\n <span className=\"text-xs\">Press keys…</span>\n ) : (\n <span className=\"text-xs text-muted-foreground\">Not set</span>\n )}\n </span>\n </button>\n\n {current && !recording ? (\n <button\n type=\"button\"\n aria-label={`Clear — ${label}`}\n className={cn(\n \"rounded-md border border-input bg-background px-2.5 py-1 text-xs font-medium hover:bg-accent\",\n focusRing,\n )}\n onClick={() => {\n commit(null);\n setConflict(null);\n setAnnouncement(\"Cleared.\");\n }}\n >\n Clear\n </button>\n ) : null}\n </div>\n\n <p id={`${id}-description`} className=\"text-xs text-muted-foreground\">\n {description}\n </p>\n\n {conflict ? (\n <div\n data-slot=\"shortcut-conflict\"\n className=\"flex flex-wrap items-center gap-2 rounded-md border border-warning/50 bg-warning/5 px-2.5 py-1.5 text-xs\"\n >\n <span className=\"flex-1 text-warning\">\n {describeShortcut(conflict.shortcut, platform)} is already used by{\" \"}\n <strong>{conflict.with.label}</strong>.\n </span>\n <button\n type=\"button\"\n className={cn(\n \"rounded-md border border-input bg-background px-2 py-0.5 font-medium hover:bg-accent\",\n focusRing,\n )}\n onClick={() => {\n commit(conflict.shortcut);\n setConflict(null);\n setAnnouncement(\n `Recorded ${describeShortcut(conflict.shortcut, platform)}. ${conflict.with.label} no longer has a shortcut.`,\n );\n }}\n >\n Use anyway\n </button>\n <button\n type=\"button\"\n className={cn(\n \"rounded-md border border-input bg-background px-2 py-0.5 font-medium hover:bg-accent\",\n focusRing,\n )}\n onClick={() => {\n setConflict(null);\n }}\n >\n Keep the old one\n </button>\n </div>\n ) : null}\n\n {/* Present from the start, so the first outcome is heard. */}\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </span>\n </div>\n );\n}\n\nfunction hasModifier(event: KeyboardEvent): boolean {\n return event.metaKey || event.ctrlKey || event.altKey || event.shiftKey;\n}\n\nfunction safeParse(text: string): Shortcut | null {\n try {\n return parseShortcut(text);\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;AA2EA,MAAM,iCAAiC,KAAA;AACvC,MAAM,qBAAqB,eAAe;AAC1C,MAAM,4BAAsC;AAE5C,SAAgB,iBAAiB,EAC/B,WACA,OACA,OAAO,WACP,eAAe,MACf,UACA,QAAQ,CAAC,GACT,UAAU,cACV,kBAAkB,MAClB,GAAG,SACqB;CACxB,MAAM,KAAK,MAAM;CACjB,MAAM,WAAW,qBAAqB,oBAAoB,cAAc,mBAAmB;CAC3F,MAAM,WAAW,gBAAgB;CAEjC,MAAM,CAAC,cAAc,mBAAmB,SAAwB,YAAY;CAC5E,MAAM,QAAQ,cAAc,KAAA,IAAY,eAAe;CACvD,MAAM,UAAU,QAAQ,UAAU,KAAK,IAAI;CAE3C,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;;CAEhD,MAAM,CAAC,MAAM,WAAW,SAA0B,IAAI;CACtD,MAAM,CAAC,UAAU,eAAe,SAC9B,IACF;CACA,MAAM,CAAC,cAAc,mBAAmB,SAAS,EAAE;CAEnD,MAAM,UAAU,SAA0B;EACxC,MAAM,aAAa,OAAO,kBAAkB,IAAI,IAAI;EACpD,gBAAgB,UAAU;EAC1B,WAAW,UAAU;CACvB;CAEA,MAAM,aAAa;EACjB,aAAa,KAAK;EAClB,QAAQ,IAAI;CACd;CAEA,MAAM,aAAa,UAA4C;EAC7D,IAAI,CAAC,WAAW;EAEhB,IAAI,MAAM,QAAQ,OAAO;GACvB,KAAK;GACL,gBAAgB,YAAY;GAC5B;EACF;EACA,MAAM,eAAe;EACrB,IAAI,MAAM,QAAQ,UAAU;GAC1B,KAAK;GACL,gBAAgB,YAAY;GAC5B;EACF;EACA,KAAK,MAAM,QAAQ,eAAe,MAAM,QAAQ,aAAa,CAAC,YAAY,KAAK,GAAG;GAChF,KAAK;GACL,OAAO,IAAI;GACX,gBAAgB,UAAU;GAC1B;EACF;EAEA,MAAM,QAAQ,gBAAgB,MAAM,aAAa,QAAQ;EACzD,IAAI,CAAC,OAAO;GAEV,QAAQ;IACN,KAAK;IACL,KAAK,aAAa,QAAQ,MAAM,UAAU,MAAM;IAChD,MAAM,aAAa,QAAQ,MAAM,UAAU;IAC3C,KAAK,MAAM;IACX,OAAO,MAAM;GACf,CAAC;GACD;EACF;EAEA,IAAI,mBAAmB,cAAc,KAAK,GAAG;GAC3C,gBACE,GAAG,iBAAiB,OAAO,QAAQ,EAAE,2DACvC;GACA,QAAQ,IAAI;GACZ;EACF;EAEA,MAAM,QAAQ,MAAM,MAAM,UACxB,eAAe,UAAU,MAAM,QAAQ,KAAK,OAAO,KAAK,CAC1D;EACA,KAAK;EACL,IAAI,SAAS,EAAE,WAAW,eAAe,SAAS,KAAK,IAAI;GACzD,YAAY;IAAE,UAAU;IAAO,MAAM;GAAM,CAAC;GAC5C,gBACE,GAAG,iBAAiB,OAAO,QAAQ,EAAE,sBAAsB,MAAM,MAAM,EACzE;GACA;EACF;EACA,OAAO,KAAK;EACZ,gBAAgB,YAAY,iBAAiB,OAAO,QAAQ,EAAE,EAAE;CAClE;CAEA,MAAM,WAAW,UAA4C;EAC3D,IAAI,CAAC,aAAa,CAAC,MAAM;EACzB,QAAQ;GACN,KAAK;GACL,KAAK,aAAa,QAAQ,MAAM,UAAU,MAAM;GAChD,MAAM,aAAa,QAAQ,MAAM,UAAU;GAC3C,KAAK,MAAM;GACX,OAAO,MAAM;EACf,CAAC;CACH;CAEA,MAAM,QAAQ,YAAY,OAAO;CACjC,MAAM,cAAc,YAChB,+DACA,UACE,GAAG,iBAAiB,SAAS,QAAQ,EAAE,sBACvC;CAEN,OACE,qBAAC,OAAD;EACE,aAAU;EACV,cAAY,YAAY,cAAc,WAAW,aAAa,UAAU,QAAQ;EAChF,WAAW,GAAG,yBAAyB,SAAS;EAChD,GAAI;EAJN,UAAA;GAME,oBAAC,QAAD;IAAM,IAAI,GAAG,GAAG;IAAS,WAAU;IAChC,UAAA;GACG,CAAA;GAEN,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,UAAD;KACE,MAAK;KACL,mBAAiB,GAAG,GAAG;KACvB,oBAAkB,GAAG,GAAG;KACxB,gBAAc;KACd,WAAW,GACT,gGACA,YACI,sDACA,8CACJ,WACA,cACF;KACA,eAAe;MACb,IAAI,WAAW;MACf,YAAY,IAAI;MAChB,aAAa,IAAI;MACjB,gBAAgB,EAAE;KACpB;KACW;KACF;KACT,cAAc;MACZ,IAAI,WAAW;OACb,KAAK;OACL,gBAAgB,YAAY;MAC9B;KACF;KAIA,UAAA,oBAAC,QAAD;MAAM,eAAY;MAAO,WAAU;MAChC,UAAA,UAAU,MAAM,OAAO,MAAM,OAAO,MAAM,QAAQ,MAAM,OAAO,MAAM,SACpE,cAAc,OAAO,QAAQ,CAAC,CAC3B,OAAO,OAAO,CAAC,CACf,KAAK,MAAM,UACV,oBAAC,OAAD;OAEE,WAAU;OAET,UAAA;MACE,GAJE,KAIF,CACN,IACD,YACF,oBAAC,QAAD;OAAM,WAAU;OAAU,UAAA;MAAiB,CAAA,IAE3C,oBAAC,QAAD;OAAM,WAAU;OAAgC,UAAA;MAAa,CAAA;KAE3D,CAAA;IACA,CAAA,GAEP,WAAW,CAAC,YACX,oBAAC,UAAD;KACE,MAAK;KACL,cAAY,WAAW;KACvB,WAAW,GACT,gGACA,SACF;KACA,eAAe;MACb,OAAO,IAAI;MACX,YAAY,IAAI;MAChB,gBAAgB,UAAU;KAC5B;KACD,UAAA;IAEO,CAAA,IACN,IACD;;GAEL,oBAAC,KAAD;IAAG,IAAI,GAAG,GAAG;IAAe,WAAU;IACnC,UAAA;GACA,CAAA;GAEF,WACC,qBAAC,OAAD;IACE,aAAU;IACV,WAAU;IAFZ,UAAA;KAIE,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA;OACG,iBAAiB,SAAS,UAAU,QAAQ;OAAE;OAAoB;OACnE,oBAAC,UAAD,EAAA,UAAS,SAAS,KAAK,MAAc,CAAA;OAAC;MAClC;;KACN,oBAAC,UAAD;MACE,MAAK;MACL,WAAW,GACT,wFACA,SACF;MACA,eAAe;OACb,OAAO,SAAS,QAAQ;OACxB,YAAY,IAAI;OAChB,gBACE,YAAY,iBAAiB,SAAS,UAAU,QAAQ,EAAE,IAAI,SAAS,KAAK,MAAM,2BACpF;MACF;MACD,UAAA;KAEO,CAAA;KACR,oBAAC,UAAD;MACE,MAAK;MACL,WAAW,GACT,wFACA,SACF;MACA,eAAe;OACb,YAAY,IAAI;MAClB;MACD,UAAA;KAEO,CAAA;IACL;GACH,CAAA,IAAA;GAGJ,oBAAC,QAAD;IAAM,MAAK;IAAS,aAAU;IAAS,WAAU;IAC9C,UAAA;GACG,CAAA;EACH;;AAET;AAEA,SAAS,YAAY,OAA+B;CAClD,OAAO,MAAM,WAAW,MAAM,WAAW,MAAM,UAAU,MAAM;AACjE;AAEA,SAAS,UAAU,MAA+B;CAChD,IAAI;EACF,OAAO,cAAc,IAAI;CAC3B,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuButtonProps, SidebarMenuItem, SidebarMenuLabel, SidebarProps, SidebarProvider, SidebarProviderProps, SidebarTrigger, SidebarTriggerProps } from "./sidebar.js";
|
|
2
|
+
export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, type SidebarMenuButtonProps, SidebarMenuItem, SidebarMenuLabel, type SidebarProps, SidebarProvider, type SidebarProviderProps, SidebarTrigger, type SidebarTriggerProps };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuLabel, SidebarProvider, SidebarTrigger } from "./sidebar.js";
|
|
2
|
+
export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuLabel, SidebarProvider, SidebarTrigger };
|