@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
|
@@ -48,14 +48,14 @@ function TableRow({ className, ...props }) {
|
|
|
48
48
|
function TableHead({ className, ...props }) {
|
|
49
49
|
return /* @__PURE__ */ jsx("th", {
|
|
50
50
|
"data-slot": "table-head",
|
|
51
|
-
className: cn("h-10 px-3 text-
|
|
51
|
+
className: cn("h-10 px-3 text-start align-middle text-xs font-medium whitespace-nowrap text-muted-foreground", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pe-0", className),
|
|
52
52
|
...props
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
function TableCell({ className, ...props }) {
|
|
56
56
|
return /* @__PURE__ */ jsx("td", {
|
|
57
57
|
"data-slot": "table-cell",
|
|
58
|
-
className: cn("px-3 py-2.5 align-middle", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:
|
|
58
|
+
className: cn("px-3 py-2.5 align-middle", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pe-0", className),
|
|
59
59
|
...props
|
|
60
60
|
});
|
|
61
61
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","names":[],"sources":["../../../src/components/table/table.tsx"],"sourcesContent":["import type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Semantic table primitives.\n *\n * A real `<table>`, not a grid of divs: screen readers announce row and column\n * position, header association and table dimensions from the native elements,\n * and none of that can be recovered with ARIA after the fact.\n *\n * The wrapper scrolls horizontally and is focusable, because a table that\n * overflows its container is otherwise unreachable by keyboard.\n */\n/**\n * Arbitrary `data-*` attributes.\n *\n * TypeScript accepts `data-*` written directly on JSX, but not inside an object\n * literal typed as HTML attributes — so without this, `containerProps` could not\n * carry a test id or a styling hook.\n */\ntype DataAttributes = Record<`data-${string}`, string | number | boolean | undefined>;\n\nexport interface TableProps extends ComponentPropsWithRef<\"table\"> {\n /** Props for the scrolling wrapper around the table. */\n containerProps?: ComponentPropsWithRef<\"div\"> & DataAttributes;\n}\n\nexport function Table({ className, containerProps, ...props }: TableProps) {\n const { className: containerClassName, ...restContainer } = containerProps ?? {};\n\n return (\n <div\n data-slot=\"table-container\"\n // tabIndex 0 with a role and label makes an overflowing table scrollable\n // by keyboard. Without it, the columns past the edge are unreachable.\n tabIndex={0}\n role=\"region\"\n aria-label={props[\"aria-label\"] ?? \"Table\"}\n className={cn(\"relative w-full overflow-x-auto\", containerClassName)}\n {...restContainer}\n >\n <table\n data-slot=\"table\"\n className={cn(\"w-full caption-bottom border-collapse text-sm\", className)}\n {...props}\n />\n </div>\n );\n}\n\nexport function TableHeader({ className, ...props }: ComponentPropsWithRef<\"thead\">) {\n return (\n <thead\n data-slot=\"table-header\"\n className={cn(\"[&_tr]:border-b [&_tr]:border-border\", className)}\n {...props}\n />\n );\n}\n\nexport function TableBody({ className, ...props }: ComponentPropsWithRef<\"tbody\">) {\n return (\n <tbody\n data-slot=\"table-body\"\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n );\n}\n\nexport function TableFooter({ className, ...props }: ComponentPropsWithRef<\"tfoot\">) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn(\"border-t border-border bg-muted/40 font-medium\", className)}\n {...props}\n />\n );\n}\n\nexport function TableRow({ className, ...props }: ComponentPropsWithRef<\"tr\">) {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n \"border-b border-border transition-colors duration-[var(--duration-instant)]\",\n \"hover:bg-muted/50 data-[state=selected]:bg-accent\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function TableHead({ className, ...props }: ComponentPropsWithRef<\"th\">) {\n return (\n <th\n data-slot=\"table-head\"\n className={cn(\n \"h-10 px-3 text-
|
|
1
|
+
{"version":3,"file":"table.js","names":[],"sources":["../../../src/components/table/table.tsx"],"sourcesContent":["import type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Semantic table primitives.\n *\n * A real `<table>`, not a grid of divs: screen readers announce row and column\n * position, header association and table dimensions from the native elements,\n * and none of that can be recovered with ARIA after the fact.\n *\n * The wrapper scrolls horizontally and is focusable, because a table that\n * overflows its container is otherwise unreachable by keyboard.\n */\n/**\n * Arbitrary `data-*` attributes.\n *\n * TypeScript accepts `data-*` written directly on JSX, but not inside an object\n * literal typed as HTML attributes — so without this, `containerProps` could not\n * carry a test id or a styling hook.\n */\ntype DataAttributes = Record<`data-${string}`, string | number | boolean | undefined>;\n\nexport interface TableProps extends ComponentPropsWithRef<\"table\"> {\n /** Props for the scrolling wrapper around the table. */\n containerProps?: ComponentPropsWithRef<\"div\"> & DataAttributes;\n}\n\nexport function Table({ className, containerProps, ...props }: TableProps) {\n const { className: containerClassName, ...restContainer } = containerProps ?? {};\n\n return (\n <div\n data-slot=\"table-container\"\n // tabIndex 0 with a role and label makes an overflowing table scrollable\n // by keyboard. Without it, the columns past the edge are unreachable.\n tabIndex={0}\n role=\"region\"\n aria-label={props[\"aria-label\"] ?? \"Table\"}\n className={cn(\"relative w-full overflow-x-auto\", containerClassName)}\n {...restContainer}\n >\n <table\n data-slot=\"table\"\n className={cn(\"w-full caption-bottom border-collapse text-sm\", className)}\n {...props}\n />\n </div>\n );\n}\n\nexport function TableHeader({ className, ...props }: ComponentPropsWithRef<\"thead\">) {\n return (\n <thead\n data-slot=\"table-header\"\n className={cn(\"[&_tr]:border-b [&_tr]:border-border\", className)}\n {...props}\n />\n );\n}\n\nexport function TableBody({ className, ...props }: ComponentPropsWithRef<\"tbody\">) {\n return (\n <tbody\n data-slot=\"table-body\"\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n );\n}\n\nexport function TableFooter({ className, ...props }: ComponentPropsWithRef<\"tfoot\">) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn(\"border-t border-border bg-muted/40 font-medium\", className)}\n {...props}\n />\n );\n}\n\nexport function TableRow({ className, ...props }: ComponentPropsWithRef<\"tr\">) {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n \"border-b border-border transition-colors duration-[var(--duration-instant)]\",\n \"hover:bg-muted/50 data-[state=selected]:bg-accent\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function TableHead({ className, ...props }: ComponentPropsWithRef<\"th\">) {\n return (\n <th\n data-slot=\"table-head\"\n className={cn(\n \"h-10 px-3 text-start align-middle text-xs font-medium whitespace-nowrap text-muted-foreground\",\n \"[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pe-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function TableCell({ className, ...props }: ComponentPropsWithRef<\"td\">) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\n \"px-3 py-2.5 align-middle\",\n \"[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pe-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A caption describing the table.\n *\n * Rendered below the table but announced as its accessible name, which is why\n * it belongs here rather than as a heading above.\n */\nexport function TableCaption({ className, ...props }: ComponentPropsWithRef<\"caption\">) {\n return (\n <caption\n data-slot=\"table-caption\"\n className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n"],"mappings":";;;AA4BA,SAAgB,MAAM,EAAE,WAAW,gBAAgB,GAAG,SAAqB;CACzE,MAAM,EAAE,WAAW,oBAAoB,GAAG,kBAAkB,kBAAkB,CAAC;CAE/E,OACE,oBAAC,OAAD;EACE,aAAU;EAGV,UAAU;EACV,MAAK;EACL,cAAY,MAAM,iBAAiB;EACnC,WAAW,GAAG,mCAAmC,kBAAkB;EACnE,GAAI;EAEJ,UAAA,oBAAC,SAAD;GACE,aAAU;GACV,WAAW,GAAG,iDAAiD,SAAS;GACxE,GAAI;EACL,CAAA;CACE,CAAA;AAET;AAEA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAAyC;CACnF,OACE,oBAAC,SAAD;EACE,aAAU;EACV,WAAW,GAAG,wCAAwC,SAAS;EAC/D,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,UAAU,EAAE,WAAW,GAAG,SAAyC;CACjF,OACE,oBAAC,SAAD;EACE,aAAU;EACV,WAAW,GAAG,8BAA8B,SAAS;EACrD,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAAyC;CACnF,OACE,oBAAC,SAAD;EACE,aAAU;EACV,WAAW,GAAG,kDAAkD,SAAS;EACzE,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,SAAS,EAAE,WAAW,GAAG,SAAsC;CAC7E,OACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,+EACA,qDACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,UAAU,EAAE,WAAW,GAAG,SAAsC;CAC9E,OACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,iGACA,+DACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,UAAU,EAAE,WAAW,GAAG,SAAsC;CAC9E,OACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,4BACA,+DACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;;;;;;;AAQA,SAAgB,aAAa,EAAE,WAAW,GAAG,SAA2C;CACtF,OACE,oBAAC,WAAD;EACE,aAAU;EACV,WAAW,GAAG,sCAAsC,SAAS;EAC7D,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -123,7 +123,7 @@ function TagsInput({ className, value, onValueChange, label, delimiters = [",",
|
|
|
123
123
|
onClick: () => {
|
|
124
124
|
removeAt(index);
|
|
125
125
|
},
|
|
126
|
-
className: cn("-
|
|
126
|
+
className: cn("-me-0.5 grid size-4 place-items-center rounded-full", "transition-colors hover:bg-foreground/10", focusRing, disabledStyles),
|
|
127
127
|
children: /* @__PURE__ */ jsx("svg", {
|
|
128
128
|
viewBox: "0 0 24 24",
|
|
129
129
|
"aria-hidden": "true",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tags-input.js","names":[],"sources":["../../../src/components/tags-input/tags-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useId,\n useRef,\n useState,\n type ClipboardEvent,\n type ComponentPropsWithRef,\n type KeyboardEvent,\n} from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A field whose value is a list of short strings: invited emails, allowed\n * domains, labels, stop sequences.\n *\n * The behaviour that makes this worth shipping is what happens to input that\n * fails validation. Every implementation surveyed either refuses to create the\n * token, or creates it and silently throws it away — and both leave the reader\n * staring at a field that did not do what they asked, with nothing to correct.\n * Here an invalid entry becomes a token like any other, marked invalid and\n * carrying its reason, so it can be seen, read out, and fixed by removing it.\n * `value` holds every token including the invalid ones; the consumer runs the\n * same `validate` to decide whether the field is submittable.\n *\n * Deliberately no suggestion list. A token field with an anchored listbox is a\n * multi-select combobox, and this library already has 544 lines of hand-rolled\n * combobox ARIA. A second copy of that would be the expensive kind of\n * duplication — the kind that drifts. When multi-select is wanted, it belongs in\n * Combobox.\n */\n\n/** Returns `true` for a valid tag, or a reason it is not. */\nexport type TagValidator = (tag: string, existing: string[]) => true | string;\n\nexport interface TagsInputProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"onChange\" | \"children\"\n> {\n value: string[];\n onValueChange: (tags: string[]) => void;\n /** Names the field. Required — an unlabelled list of tokens is unreadable. */\n label: string;\n /** Characters that commit the token being typed, besides Enter. */\n delimiters?: string[];\n /**\n * Decides whether a tag is acceptable. An invalid tag is still added, marked,\n * and given its reason, rather than being refused or dropped.\n */\n validate?: TagValidator;\n /** Rejects a tag already present. Rejections are announced, never silent. */\n allowDuplicates?: boolean;\n /** Refuses further tags once reached, and says so. */\n max?: number;\n placeholder?: string;\n disabled?: boolean;\n inputProps?: Omit<ComponentPropsWithRef<\"input\">, \"value\" | \"onChange\" | \"disabled\">;\n}\n\n/** Splits pasted or typed text on any delimiter, dropping empty fragments. */\nfunction splitOn(text: string, delimiters: string[]): string[] {\n if (delimiters.length === 0) return [text];\n const pattern = new RegExp(`[${delimiters.map((d) => `\\\\${d}`).join(\"\")}]`);\n return text\n .split(pattern)\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n}\n\nexport function TagsInput({\n className,\n value,\n onValueChange,\n label,\n delimiters = [\",\", \";\"],\n validate,\n allowDuplicates = false,\n max,\n placeholder,\n disabled = false,\n inputProps,\n ...props\n}: TagsInputProps) {\n const [draft, setDraft] = useState(\"\");\n const [announcement, setAnnouncement] = useState(\"\");\n const inputRef = useRef<HTMLInputElement | null>(null);\n const labelId = useId();\n const inputId = useId();\n const hintId = useId();\n\n const atLimit = max !== undefined && value.length >= max;\n\n function reasonFor(tag: string): string | null {\n if (!validate) return null;\n const result = validate(tag, value);\n return result === true ? null : result;\n }\n\n /** Adds tags, reporting anything refused rather than dropping it in silence. */\n function commit(candidates: string[]) {\n const accepted: string[] = [];\n const refused: string[] = [];\n\n // Split defensively rather than trusting the caller. A draft can arrive\n // holding a delimiter from autofill, an IME commit, or a programmatic set,\n // and a token with a comma inside it is never what was meant.\n for (const candidate of candidates.flatMap((part) => splitOn(part, delimiters))) {\n const tag = candidate.trim();\n if (tag.length === 0) continue;\n\n if (!allowDuplicates && [...value, ...accepted].includes(tag)) {\n refused.push(`${tag} is already added`);\n continue;\n }\n if (max !== undefined && value.length + accepted.length >= max) {\n refused.push(`${tag} not added, limit of ${String(max)} reached`);\n continue;\n }\n // An invalid tag is still added. It is the reader's text and they need to\n // see it to fix it; refusing silently is how these fields lose input.\n accepted.push(tag);\n }\n\n if (accepted.length > 0) onValueChange([...value, ...accepted]);\n\n const parts: string[] = [];\n if (accepted.length > 0) {\n const invalid = accepted.filter((tag) => reasonFor(tag) !== null);\n parts.push(`Added ${accepted.join(\", \")}`);\n if (invalid.length > 0) parts.push(`${String(invalid.length)} need attention`);\n }\n parts.push(...refused);\n setAnnouncement(parts.join(\". \"));\n }\n\n function removeAt(index: number) {\n const removed = value[index];\n onValueChange(value.filter((_, position) => position !== index));\n setAnnouncement(removed === undefined ? \"\" : `Removed ${removed}`);\n inputRef.current?.focus();\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\") {\n // Never let a token commit submit the form as well.\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n if (delimiters.includes(event.key)) {\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n // Backspace on an empty field removes the last token — the expected\n // shortcut, and the only way to correct without reaching for the mouse.\n if (event.key === \"Backspace\" && draft.length === 0 && value.length > 0) {\n event.preventDefault();\n removeAt(value.length - 1);\n }\n }\n\n function handlePaste(event: ClipboardEvent<HTMLInputElement>) {\n const text = event.clipboardData.getData(\"text\");\n const parts = splitOn(text, delimiters);\n // Only intercept a paste that actually contains several tags; a single\n // value should behave like an ordinary paste and stay editable.\n if (parts.length <= 1) return;\n\n event.preventDefault();\n commit(parts);\n setDraft(\"\");\n }\n\n return (\n <div data-slot=\"tags-input\" className={cn(\"flex flex-col gap-1.5\", className)} {...props}>\n <label id={labelId} htmlFor={inputId} className=\"w-fit text-sm font-medium\">\n {label}\n </label>\n\n {/* A group rather than a listbox: these are committed values, not options\n being chosen from, and calling it a listbox would promise navigation\n semantics that do not exist here. */}\n <div\n role=\"group\"\n aria-labelledby={labelId}\n data-disabled={disabled || undefined}\n className={cn(\n \"flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-background px-2 py-1.5\",\n \"focus-within:ring-2 focus-within:ring-ring/55\",\n disabled && \"pointer-events-none opacity-55\",\n )}\n >\n {value.length > 0 ? (\n <ul className=\"contents\">\n {value.map((tag, index) => {\n const reason = reasonFor(tag);\n return (\n <li\n key={`${tag}-${String(index)}`}\n data-slot=\"tags-input-tag\"\n data-invalid={reason !== null || undefined}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium\",\n reason === null\n ? \"border-transparent bg-secondary text-secondary-foreground\"\n : \"border-destructive/40 bg-destructive/10 text-destructive\",\n )}\n >\n {tag}\n {/* The reason travels with the token, so it is announced when\n the token is, rather than as a detached error elsewhere. */}\n {reason === null ? null : (\n <span className=\"sr-only\">, invalid: {reason}</span>\n )}\n <button\n type=\"button\"\n data-slot=\"tags-input-remove\"\n disabled={disabled}\n aria-label={`Remove ${tag}`}\n onClick={() => {\n removeAt(index);\n }}\n className={cn(\n \"-mr-0.5 grid size-4 place-items-center rounded-full\",\n \"transition-colors hover:bg-foreground/10\",\n focusRing,\n disabledStyles,\n )}\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-2.5\">\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n\n <input\n ref={inputRef}\n type=\"text\"\n id={inputId}\n data-slot=\"tags-input-field\"\n value={draft}\n disabled={disabled}\n placeholder={value.length === 0 ? placeholder : undefined}\n aria-describedby={hintId}\n onChange={(event) => {\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onBlur={() => {\n // Committing on blur stops a typed-but-uncommitted value being lost\n // when the reader tabs away thinking they are done.\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n }}\n className={cn(\n \"min-w-24 flex-1 self-stretch bg-transparent text-sm outline-none\",\n \"placeholder:text-muted-foreground\",\n )}\n {...inputProps}\n />\n </div>\n\n <span id={hintId} className=\"text-xs text-muted-foreground\">\n {atLimit\n ? `Limit of ${String(max ?? 0)} reached`\n : `Press Enter${delimiters.length > 0 ? ` or ${delimiters.join(\" \")}` : \"\"} to add`}\n </span>\n\n {/* Additions, removals and refusals all land here. A refusal that is only\n visible as \"nothing happened\" is the failure this component exists to\n avoid. */}\n <span aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tags-input.js","names":[],"sources":["../../../src/components/tags-input/tags-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useId,\n useRef,\n useState,\n type ClipboardEvent,\n type ComponentPropsWithRef,\n type KeyboardEvent,\n} from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A field whose value is a list of short strings: invited emails, allowed\n * domains, labels, stop sequences.\n *\n * The behaviour that makes this worth shipping is what happens to input that\n * fails validation. Every implementation surveyed either refuses to create the\n * token, or creates it and silently throws it away — and both leave the reader\n * staring at a field that did not do what they asked, with nothing to correct.\n * Here an invalid entry becomes a token like any other, marked invalid and\n * carrying its reason, so it can be seen, read out, and fixed by removing it.\n * `value` holds every token including the invalid ones; the consumer runs the\n * same `validate` to decide whether the field is submittable.\n *\n * Deliberately no suggestion list. A token field with an anchored listbox is a\n * multi-select combobox, and this library already has 544 lines of hand-rolled\n * combobox ARIA. A second copy of that would be the expensive kind of\n * duplication — the kind that drifts. When multi-select is wanted, it belongs in\n * Combobox.\n */\n\n/** Returns `true` for a valid tag, or a reason it is not. */\nexport type TagValidator = (tag: string, existing: string[]) => true | string;\n\nexport interface TagsInputProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"onChange\" | \"children\"\n> {\n value: string[];\n onValueChange: (tags: string[]) => void;\n /** Names the field. Required — an unlabelled list of tokens is unreadable. */\n label: string;\n /** Characters that commit the token being typed, besides Enter. */\n delimiters?: string[];\n /**\n * Decides whether a tag is acceptable. An invalid tag is still added, marked,\n * and given its reason, rather than being refused or dropped.\n */\n validate?: TagValidator;\n /** Rejects a tag already present. Rejections are announced, never silent. */\n allowDuplicates?: boolean;\n /** Refuses further tags once reached, and says so. */\n max?: number;\n placeholder?: string;\n disabled?: boolean;\n inputProps?: Omit<ComponentPropsWithRef<\"input\">, \"value\" | \"onChange\" | \"disabled\">;\n}\n\n/** Splits pasted or typed text on any delimiter, dropping empty fragments. */\nfunction splitOn(text: string, delimiters: string[]): string[] {\n if (delimiters.length === 0) return [text];\n const pattern = new RegExp(`[${delimiters.map((d) => `\\\\${d}`).join(\"\")}]`);\n return text\n .split(pattern)\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n}\n\nexport function TagsInput({\n className,\n value,\n onValueChange,\n label,\n delimiters = [\",\", \";\"],\n validate,\n allowDuplicates = false,\n max,\n placeholder,\n disabled = false,\n inputProps,\n ...props\n}: TagsInputProps) {\n const [draft, setDraft] = useState(\"\");\n const [announcement, setAnnouncement] = useState(\"\");\n const inputRef = useRef<HTMLInputElement | null>(null);\n const labelId = useId();\n const inputId = useId();\n const hintId = useId();\n\n const atLimit = max !== undefined && value.length >= max;\n\n function reasonFor(tag: string): string | null {\n if (!validate) return null;\n const result = validate(tag, value);\n return result === true ? null : result;\n }\n\n /** Adds tags, reporting anything refused rather than dropping it in silence. */\n function commit(candidates: string[]) {\n const accepted: string[] = [];\n const refused: string[] = [];\n\n // Split defensively rather than trusting the caller. A draft can arrive\n // holding a delimiter from autofill, an IME commit, or a programmatic set,\n // and a token with a comma inside it is never what was meant.\n for (const candidate of candidates.flatMap((part) => splitOn(part, delimiters))) {\n const tag = candidate.trim();\n if (tag.length === 0) continue;\n\n if (!allowDuplicates && [...value, ...accepted].includes(tag)) {\n refused.push(`${tag} is already added`);\n continue;\n }\n if (max !== undefined && value.length + accepted.length >= max) {\n refused.push(`${tag} not added, limit of ${String(max)} reached`);\n continue;\n }\n // An invalid tag is still added. It is the reader's text and they need to\n // see it to fix it; refusing silently is how these fields lose input.\n accepted.push(tag);\n }\n\n if (accepted.length > 0) onValueChange([...value, ...accepted]);\n\n const parts: string[] = [];\n if (accepted.length > 0) {\n const invalid = accepted.filter((tag) => reasonFor(tag) !== null);\n parts.push(`Added ${accepted.join(\", \")}`);\n if (invalid.length > 0) parts.push(`${String(invalid.length)} need attention`);\n }\n parts.push(...refused);\n setAnnouncement(parts.join(\". \"));\n }\n\n function removeAt(index: number) {\n const removed = value[index];\n onValueChange(value.filter((_, position) => position !== index));\n setAnnouncement(removed === undefined ? \"\" : `Removed ${removed}`);\n inputRef.current?.focus();\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\") {\n // Never let a token commit submit the form as well.\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n if (delimiters.includes(event.key)) {\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n // Backspace on an empty field removes the last token — the expected\n // shortcut, and the only way to correct without reaching for the mouse.\n if (event.key === \"Backspace\" && draft.length === 0 && value.length > 0) {\n event.preventDefault();\n removeAt(value.length - 1);\n }\n }\n\n function handlePaste(event: ClipboardEvent<HTMLInputElement>) {\n const text = event.clipboardData.getData(\"text\");\n const parts = splitOn(text, delimiters);\n // Only intercept a paste that actually contains several tags; a single\n // value should behave like an ordinary paste and stay editable.\n if (parts.length <= 1) return;\n\n event.preventDefault();\n commit(parts);\n setDraft(\"\");\n }\n\n return (\n <div data-slot=\"tags-input\" className={cn(\"flex flex-col gap-1.5\", className)} {...props}>\n <label id={labelId} htmlFor={inputId} className=\"w-fit text-sm font-medium\">\n {label}\n </label>\n\n {/* A group rather than a listbox: these are committed values, not options\n being chosen from, and calling it a listbox would promise navigation\n semantics that do not exist here. */}\n <div\n role=\"group\"\n aria-labelledby={labelId}\n data-disabled={disabled || undefined}\n className={cn(\n \"flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-background px-2 py-1.5\",\n \"focus-within:ring-2 focus-within:ring-ring/55\",\n disabled && \"pointer-events-none opacity-55\",\n )}\n >\n {value.length > 0 ? (\n <ul className=\"contents\">\n {value.map((tag, index) => {\n const reason = reasonFor(tag);\n return (\n <li\n key={`${tag}-${String(index)}`}\n data-slot=\"tags-input-tag\"\n data-invalid={reason !== null || undefined}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium\",\n reason === null\n ? \"border-transparent bg-secondary text-secondary-foreground\"\n : \"border-destructive/40 bg-destructive/10 text-destructive\",\n )}\n >\n {tag}\n {/* The reason travels with the token, so it is announced when\n the token is, rather than as a detached error elsewhere. */}\n {reason === null ? null : (\n <span className=\"sr-only\">, invalid: {reason}</span>\n )}\n <button\n type=\"button\"\n data-slot=\"tags-input-remove\"\n disabled={disabled}\n aria-label={`Remove ${tag}`}\n onClick={() => {\n removeAt(index);\n }}\n className={cn(\n \"-me-0.5 grid size-4 place-items-center rounded-full\",\n \"transition-colors hover:bg-foreground/10\",\n focusRing,\n disabledStyles,\n )}\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-2.5\">\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n\n <input\n ref={inputRef}\n type=\"text\"\n id={inputId}\n data-slot=\"tags-input-field\"\n value={draft}\n disabled={disabled}\n placeholder={value.length === 0 ? placeholder : undefined}\n aria-describedby={hintId}\n onChange={(event) => {\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onBlur={() => {\n // Committing on blur stops a typed-but-uncommitted value being lost\n // when the reader tabs away thinking they are done.\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n }}\n className={cn(\n \"min-w-24 flex-1 self-stretch bg-transparent text-sm outline-none\",\n \"placeholder:text-muted-foreground\",\n )}\n {...inputProps}\n />\n </div>\n\n <span id={hintId} className=\"text-xs text-muted-foreground\">\n {atLimit\n ? `Limit of ${String(max ?? 0)} reached`\n : `Press Enter${delimiters.length > 0 ? ` or ${delimiters.join(\" \")}` : \"\"} to add`}\n </span>\n\n {/* Additions, removals and refusals all land here. A refusal that is only\n visible as \"nothing happened\" is the failure this component exists to\n avoid. */}\n <span aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/textarea/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "textarea",
|
|
5
|
+
title: "Textarea",
|
|
6
|
+
description: "A multi-line text field, with optional auto-resize and a character count that knows when to stay quiet.",
|
|
7
|
+
category: "form",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: ["class-variance-authority"],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["textarea.tsx"],
|
|
12
|
+
a11y: "Pair with a Label via htmlFor/id. The error state is driven by aria-invalid, so assistive technology and styling stay in sync. The character count is described by the field rather than announced on every keystroke — a permanently live counter reads the remaining number between every letter typed, which is unusable — so it goes live only once the limit is close, and states the remainder in words rather than as a bare ratio that is read aloud as two unlabelled numbers. Horizontal resizing is off by default, because a field dragged wider than its container is a broken layout."
|
|
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/textarea/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"textarea\",\n title: \"Textarea\",\n description:\n \"A multi-line text field, with optional auto-resize and a character count that knows when to stay quiet.\",\n category: \"form\",\n status: \"stable\",\n dependencies: [\"class-variance-authority\"],\n registryDependencies: [],\n files: [\"textarea.tsx\"],\n a11y:\n \"Pair with a Label via htmlFor/id. The error state is driven by aria-invalid, so assistive \" +\n \"technology and styling stay in sync. The character count is described by the field rather \" +\n \"than announced on every keystroke — a permanently live counter reads the remaining number \" +\n \"between every letter typed, which is unusable — so it goes live only once the limit is \" +\n \"close, and states the remainder in words rather than as a bare ratio that is read aloud as \" +\n \"two unlabelled numbers. Horizontal resizing is off by default, because a field dragged \" +\n \"wider than its container is a broken layout.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC,0BAA0B;CACzC,sBAAsB,CAAC;CACvB,OAAO,CAAC,cAAc;CACtB,MACE;AAOJ,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { VariantProps } from "class-variance-authority";
|
|
2
|
+
import { ComponentPropsWithRef } from "react";
|
|
3
|
+
//#region src/components/textarea/textarea.d.ts
|
|
4
|
+
declare const textareaVariants: (props?: ({
|
|
5
|
+
textareaSize?: "sm" | "md" | "lg" | null | undefined;
|
|
6
|
+
resize?: "none" | "vertical" | "both" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
interface TextareaProps extends Omit<ComponentPropsWithRef<"textarea">, "cols">, VariantProps<typeof textareaVariants> {
|
|
9
|
+
/**
|
|
10
|
+
* Grows with its content instead of scrolling, up to `maxRows`.
|
|
11
|
+
*
|
|
12
|
+
* Off by default. A field that changes height as you type moves everything
|
|
13
|
+
* below it, which is the right trade for a comment box and the wrong one for
|
|
14
|
+
* a form of twelve fields.
|
|
15
|
+
*/
|
|
16
|
+
autoResize?: boolean;
|
|
17
|
+
/** Ceiling for `autoResize`, after which it scrolls. */
|
|
18
|
+
maxRows?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Shows a live character count.
|
|
21
|
+
*
|
|
22
|
+
* Needs `maxLength` to have anything to count against.
|
|
23
|
+
*/
|
|
24
|
+
showCount?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Multi-line text field.
|
|
28
|
+
*
|
|
29
|
+
* The visual size prop is `textareaSize`, not `size`, for the same reason
|
|
30
|
+
* Input's is `inputSize`: `size` is a native attribute on form controls, and a
|
|
31
|
+
* prop of that name shadows it.
|
|
32
|
+
*
|
|
33
|
+
* The character count is where this differs from most. A counter wired as a
|
|
34
|
+
* live region announces on every keystroke, which is unusable — a screen
|
|
35
|
+
* reader reads "one hundred and forty-one characters remaining" between every
|
|
36
|
+
* letter. So the count is silent while there is room and only announces once
|
|
37
|
+
* it is nearly gone, which is the point at which it is information rather than
|
|
38
|
+
* chatter.
|
|
39
|
+
*/
|
|
40
|
+
declare function Textarea({ className, textareaSize, resize, autoResize, maxRows, showCount, rows, maxLength, value, defaultValue, onChange, id, "aria-describedby": describedBy, ref, ...props }: TextareaProps): import("react").JSX.Element;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { Textarea, TextareaProps, textareaVariants };
|
|
43
|
+
//# sourceMappingURL=textarea.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.d.ts","names":[],"sources":["../../../src/components/textarea/textarea.tsx"],"mappings":";;;cAeM,mBAAgB;;;6CAmCrB;UAEgB,sBAEb,KAAK,4CACL,oBAAoB;;;;;;;;EAQtB;;EAEA;;;;;;EAMA;;;;;;;;;;;;;;;;iBAiBc,WACd,WACA,cACA,QACA,YACA,SACA,WACA,MACA,WACA,OACA,cACA,UACA,wBACoB,aACpB,QACG,SACF,gCAAa,IAAA"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { focusRingInset, invalidStyles } from "../../lib/styles.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
import { useId, useLayoutEffect, useRef, useState } from "react";
|
|
7
|
+
//#region src/components/textarea/textarea.tsx
|
|
8
|
+
const textareaVariants = cva(cn("block w-full min-w-0 rounded-md border border-input bg-background text-foreground shadow-xs", "transition-[border-color,box-shadow] duration-[var(--duration-fast)] ease-[var(--ease-out-quint)]", "placeholder:text-muted-foreground", "focus-visible:border-ring", "disabled:cursor-not-allowed disabled:opacity-55", focusRingInset, invalidStyles), {
|
|
9
|
+
variants: {
|
|
10
|
+
textareaSize: {
|
|
11
|
+
sm: "px-2.5 py-1.5 text-sm",
|
|
12
|
+
md: "px-3 py-2 text-sm",
|
|
13
|
+
lg: "px-3.5 py-2.5 text-base"
|
|
14
|
+
},
|
|
15
|
+
/**
|
|
16
|
+
* Vertical only by default.
|
|
17
|
+
*
|
|
18
|
+
* The browser default is `both`, and a textarea dragged wider than its
|
|
19
|
+
* container is a layout broken by a control that was only meant to be
|
|
20
|
+
* made taller.
|
|
21
|
+
*/
|
|
22
|
+
resize: {
|
|
23
|
+
none: "resize-none",
|
|
24
|
+
vertical: "resize-y",
|
|
25
|
+
both: "resize"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
textareaSize: "md",
|
|
30
|
+
resize: "vertical"
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Multi-line text field.
|
|
35
|
+
*
|
|
36
|
+
* The visual size prop is `textareaSize`, not `size`, for the same reason
|
|
37
|
+
* Input's is `inputSize`: `size` is a native attribute on form controls, and a
|
|
38
|
+
* prop of that name shadows it.
|
|
39
|
+
*
|
|
40
|
+
* The character count is where this differs from most. A counter wired as a
|
|
41
|
+
* live region announces on every keystroke, which is unusable — a screen
|
|
42
|
+
* reader reads "one hundred and forty-one characters remaining" between every
|
|
43
|
+
* letter. So the count is silent while there is room and only announces once
|
|
44
|
+
* it is nearly gone, which is the point at which it is information rather than
|
|
45
|
+
* chatter.
|
|
46
|
+
*/
|
|
47
|
+
function Textarea({ className, textareaSize, resize, autoResize = false, maxRows = 12, showCount = false, rows = 3, maxLength, value, defaultValue, onChange, id, "aria-describedby": describedBy, ref, ...props }) {
|
|
48
|
+
const internalRef = useRef(null);
|
|
49
|
+
const generatedId = useId();
|
|
50
|
+
const countId = `${id ?? generatedId}-count`;
|
|
51
|
+
const counted = showCount && maxLength !== void 0;
|
|
52
|
+
/**
|
|
53
|
+
* Both refs, without a library.
|
|
54
|
+
*
|
|
55
|
+
* React 19 passes `ref` as an ordinary prop, so it arrives here and has to be
|
|
56
|
+
* forwarded on deliberately. Auto-resize needs the element too, and silently
|
|
57
|
+
* dropping the caller's ref would break every form library that reaches for
|
|
58
|
+
* the node.
|
|
59
|
+
*/
|
|
60
|
+
const setRef = (node) => {
|
|
61
|
+
internalRef.current = node;
|
|
62
|
+
if (typeof ref === "function") ref(node);
|
|
63
|
+
else if (ref) ref.current = node;
|
|
64
|
+
};
|
|
65
|
+
useLayoutEffect(() => {
|
|
66
|
+
const textarea = internalRef.current;
|
|
67
|
+
if (!textarea || !autoResize) return;
|
|
68
|
+
textarea.style.height = "auto";
|
|
69
|
+
const maxHeight = (Number.parseFloat(getComputedStyle(textarea).lineHeight) || 20) * maxRows;
|
|
70
|
+
textarea.style.height = `${String(Math.min(textarea.scrollHeight, maxHeight))}px`;
|
|
71
|
+
textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* The length, for the count.
|
|
75
|
+
*
|
|
76
|
+
* Held in state for the uncontrolled case rather than read off the element
|
|
77
|
+
* during render. Reading the ref looks simpler and does not work: nothing
|
|
78
|
+
* re-renders when an uncontrolled field is typed into, so the count would
|
|
79
|
+
* show whatever it happened to say on first paint and never move again.
|
|
80
|
+
*/
|
|
81
|
+
const [uncontrolledLength, setUncontrolledLength] = useState(typeof defaultValue === "string" ? defaultValue.length : 0);
|
|
82
|
+
const length = typeof value === "string" ? value.length : uncontrolledLength;
|
|
83
|
+
const remaining = maxLength === void 0 ? void 0 : maxLength - length;
|
|
84
|
+
const announceAt = maxLength === void 0 ? 0 : Math.max(20, Math.floor(maxLength / 10));
|
|
85
|
+
const nearLimit = remaining !== void 0 && remaining <= announceAt;
|
|
86
|
+
function handleChange(event) {
|
|
87
|
+
if (value === void 0) setUncontrolledLength(event.target.value.length);
|
|
88
|
+
onChange?.(event);
|
|
89
|
+
}
|
|
90
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
91
|
+
className: cn("grid gap-1.5", counted && "w-full"),
|
|
92
|
+
children: [/* @__PURE__ */ jsx("textarea", {
|
|
93
|
+
ref: setRef,
|
|
94
|
+
id: id ?? (counted ? generatedId : void 0),
|
|
95
|
+
"data-slot": "textarea",
|
|
96
|
+
rows,
|
|
97
|
+
maxLength,
|
|
98
|
+
value,
|
|
99
|
+
defaultValue,
|
|
100
|
+
onChange: handleChange,
|
|
101
|
+
"aria-describedby": cn(describedBy, counted && countId) || void 0,
|
|
102
|
+
className: cn(textareaVariants({
|
|
103
|
+
textareaSize,
|
|
104
|
+
resize: autoResize ? "none" : resize
|
|
105
|
+
}), className),
|
|
106
|
+
...props
|
|
107
|
+
}), counted ? /* @__PURE__ */ jsx("p", {
|
|
108
|
+
id: countId,
|
|
109
|
+
"data-slot": "textarea-count",
|
|
110
|
+
"aria-live": nearLimit ? "polite" : "off",
|
|
111
|
+
className: cn("text-xs tabular-nums", remaining !== void 0 && remaining < 0 ? "text-destructive" : nearLimit ? "text-warning" : "text-muted-foreground"),
|
|
112
|
+
children: remaining !== void 0 && remaining < 0 ? `${String(Math.abs(remaining))} characters over the limit` : `${String(remaining ?? 0)} characters left`
|
|
113
|
+
}) : null]
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { Textarea, textareaVariants };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=textarea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.js","names":[],"sources":["../../../src/components/textarea/textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type ChangeEvent,\n type ComponentPropsWithRef,\n} from \"react\";\n\nimport { focusRingInset, invalidStyles } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nconst textareaVariants = cva(\n cn(\n \"block w-full min-w-0 rounded-md border border-input bg-background text-foreground shadow-xs\",\n \"transition-[border-color,box-shadow] duration-[var(--duration-fast)] ease-[var(--ease-out-quint)]\",\n \"placeholder:text-muted-foreground\",\n \"focus-visible:border-ring\",\n \"disabled:cursor-not-allowed disabled:opacity-55\",\n focusRingInset,\n invalidStyles,\n ),\n {\n variants: {\n textareaSize: {\n sm: \"px-2.5 py-1.5 text-sm\",\n md: \"px-3 py-2 text-sm\",\n lg: \"px-3.5 py-2.5 text-base\",\n },\n /**\n * Vertical only by default.\n *\n * The browser default is `both`, and a textarea dragged wider than its\n * container is a layout broken by a control that was only meant to be\n * made taller.\n */\n resize: {\n none: \"resize-none\",\n vertical: \"resize-y\",\n both: \"resize\",\n },\n },\n defaultVariants: {\n textareaSize: \"md\",\n resize: \"vertical\",\n },\n },\n);\n\nexport interface TextareaProps\n extends\n Omit<ComponentPropsWithRef<\"textarea\">, \"cols\">,\n VariantProps<typeof textareaVariants> {\n /**\n * Grows with its content instead of scrolling, up to `maxRows`.\n *\n * Off by default. A field that changes height as you type moves everything\n * below it, which is the right trade for a comment box and the wrong one for\n * a form of twelve fields.\n */\n autoResize?: boolean;\n /** Ceiling for `autoResize`, after which it scrolls. */\n maxRows?: number;\n /**\n * Shows a live character count.\n *\n * Needs `maxLength` to have anything to count against.\n */\n showCount?: boolean;\n}\n\n/**\n * Multi-line text field.\n *\n * The visual size prop is `textareaSize`, not `size`, for the same reason\n * Input's is `inputSize`: `size` is a native attribute on form controls, and a\n * prop of that name shadows it.\n *\n * The character count is where this differs from most. A counter wired as a\n * live region announces on every keystroke, which is unusable — a screen\n * reader reads \"one hundred and forty-one characters remaining\" between every\n * letter. So the count is silent while there is room and only announces once\n * it is nearly gone, which is the point at which it is information rather than\n * chatter.\n */\nexport function Textarea({\n className,\n textareaSize,\n resize,\n autoResize = false,\n maxRows = 12,\n showCount = false,\n rows = 3,\n maxLength,\n value,\n defaultValue,\n onChange,\n id,\n \"aria-describedby\": describedBy,\n ref,\n ...props\n}: TextareaProps) {\n const internalRef = useRef<HTMLTextAreaElement | null>(null);\n const generatedId = useId();\n const countId = `${id ?? generatedId}-count`;\n\n const counted = showCount && maxLength !== undefined;\n\n /**\n * Both refs, without a library.\n *\n * React 19 passes `ref` as an ordinary prop, so it arrives here and has to be\n * forwarded on deliberately. Auto-resize needs the element too, and silently\n * dropping the caller's ref would break every form library that reaches for\n * the node.\n */\n const setRef = (node: HTMLTextAreaElement | null) => {\n internalRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as { current: HTMLTextAreaElement | null }).current = node;\n };\n\n // Measured after layout, so the height is right on the first paint rather\n // than one frame late and visibly settling.\n useLayoutEffect(() => {\n const textarea = internalRef.current;\n if (!textarea || !autoResize) return;\n\n textarea.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(textarea).lineHeight) || 20;\n const maxHeight = lineHeight * maxRows;\n textarea.style.height = `${String(Math.min(textarea.scrollHeight, maxHeight))}px`;\n textarea.style.overflowY = textarea.scrollHeight > maxHeight ? \"auto\" : \"hidden\";\n });\n\n /**\n * The length, for the count.\n *\n * Held in state for the uncontrolled case rather than read off the element\n * during render. Reading the ref looks simpler and does not work: nothing\n * re-renders when an uncontrolled field is typed into, so the count would\n * show whatever it happened to say on first paint and never move again.\n */\n const [uncontrolledLength, setUncontrolledLength] = useState(\n typeof defaultValue === \"string\" ? defaultValue.length : 0,\n );\n\n const length = typeof value === \"string\" ? value.length : uncontrolledLength;\n\n const remaining = maxLength === undefined ? undefined : maxLength - length;\n // Announce only when it starts to matter: a tenth of the budget, or twenty\n // characters, whichever is larger.\n const announceAt = maxLength === undefined ? 0 : Math.max(20, Math.floor(maxLength / 10));\n const nearLimit = remaining !== undefined && remaining <= announceAt;\n\n function handleChange(event: ChangeEvent<HTMLTextAreaElement>) {\n if (value === undefined) setUncontrolledLength(event.target.value.length);\n onChange?.(event);\n }\n\n return (\n <div className={cn(\"grid gap-1.5\", counted && \"w-full\")}>\n <textarea\n ref={setRef}\n id={id ?? (counted ? generatedId : undefined)}\n data-slot=\"textarea\"\n rows={rows}\n maxLength={maxLength}\n value={value}\n defaultValue={defaultValue}\n onChange={handleChange}\n aria-describedby={cn(describedBy, counted && countId) || undefined}\n className={cn(\n textareaVariants({ textareaSize, resize: autoResize ? \"none\" : resize }),\n className,\n )}\n {...props}\n />\n\n {counted ? (\n <p\n id={countId}\n data-slot=\"textarea-count\"\n // Polite only once it matters. Left permanently live it reads the\n // count between every keystroke, which is noise, not help.\n aria-live={nearLimit ? \"polite\" : \"off\"}\n className={cn(\n \"text-xs tabular-nums\",\n remaining !== undefined && remaining < 0\n ? \"text-destructive\"\n : nearLimit\n ? \"text-warning\"\n : \"text-muted-foreground\",\n )}\n >\n {/* Words, not \"141/200\": a bare ratio is read aloud as two numbers\n with no indication of which is which. */}\n {remaining !== undefined && remaining < 0\n ? `${String(Math.abs(remaining))} characters over the limit`\n : `${String(remaining ?? 0)} characters left`}\n </p>\n ) : null}\n </div>\n );\n}\n\nexport { textareaVariants };\n"],"mappings":";;;;;;;AAeA,MAAM,mBAAmB,IACvB,GACE,+FACA,qGACA,qCACA,6BACA,mDACA,gBACA,aACF,GACA;CACE,UAAU;EACR,cAAc;GACZ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;;;;;;;;EAQA,QAAQ;GACN,MAAM;GACN,UAAU;GACV,MAAM;EACR;CACF;CACA,iBAAiB;EACf,cAAc;EACd,QAAQ;CACV;AACF,CACF;;;;;;;;;;;;;;;AAsCA,SAAgB,SAAS,EACvB,WACA,cACA,QACA,aAAa,OACb,UAAU,IACV,YAAY,OACZ,OAAO,GACP,WACA,OACA,cACA,UACA,IACA,oBAAoB,aACpB,KACA,GAAG,SACa;CAChB,MAAM,cAAc,OAAmC,IAAI;CAC3D,MAAM,cAAc,MAAM;CAC1B,MAAM,UAAU,GAAG,MAAM,YAAY;CAErC,MAAM,UAAU,aAAa,cAAc,KAAA;;;;;;;;;CAU3C,MAAM,UAAU,SAAqC;EACnD,YAAY,UAAU;EACtB,IAAI,OAAO,QAAQ,YAAY,IAAI,IAAI;OAClC,IAAI,KAAK,IAAiD,UAAU;CAC3E;CAIA,sBAAsB;EACpB,MAAM,WAAW,YAAY;EAC7B,IAAI,CAAC,YAAY,CAAC,YAAY;EAE9B,SAAS,MAAM,SAAS;EAExB,MAAM,aADa,OAAO,WAAW,iBAAiB,QAAQ,CAAC,CAAC,UAAU,KAAK,MAChD;EAC/B,SAAS,MAAM,SAAS,GAAG,OAAO,KAAK,IAAI,SAAS,cAAc,SAAS,CAAC,EAAE;EAC9E,SAAS,MAAM,YAAY,SAAS,eAAe,YAAY,SAAS;CAC1E,CAAC;;;;;;;;;CAUD,MAAM,CAAC,oBAAoB,yBAAyB,SAClD,OAAO,iBAAiB,WAAW,aAAa,SAAS,CAC3D;CAEA,MAAM,SAAS,OAAO,UAAU,WAAW,MAAM,SAAS;CAE1D,MAAM,YAAY,cAAc,KAAA,IAAY,KAAA,IAAY,YAAY;CAGpE,MAAM,aAAa,cAAc,KAAA,IAAY,IAAI,KAAK,IAAI,IAAI,KAAK,MAAM,YAAY,EAAE,CAAC;CACxF,MAAM,YAAY,cAAc,KAAA,KAAa,aAAa;CAE1D,SAAS,aAAa,OAAyC;EAC7D,IAAI,UAAU,KAAA,GAAW,sBAAsB,MAAM,OAAO,MAAM,MAAM;EACxE,WAAW,KAAK;CAClB;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,gBAAgB,WAAW,QAAQ;EAAtD,UAAA,CACE,oBAAC,YAAD;GACE,KAAK;GACL,IAAI,OAAO,UAAU,cAAc,KAAA;GACnC,aAAU;GACJ;GACK;GACJ;GACO;GACd,UAAU;GACV,oBAAkB,GAAG,aAAa,WAAW,OAAO,KAAK,KAAA;GACzD,WAAW,GACT,iBAAiB;IAAE;IAAc,QAAQ,aAAa,SAAS;GAAO,CAAC,GACvE,SACF;GACA,GAAI;EACL,CAAA,GAEA,UACC,oBAAC,KAAD;GACE,IAAI;GACJ,aAAU;GAGV,aAAW,YAAY,WAAW;GAClC,WAAW,GACT,wBACA,cAAc,KAAA,KAAa,YAAY,IACnC,qBACA,YACE,iBACA,uBACR;GAIC,UAAA,cAAc,KAAA,KAAa,YAAY,IACpC,GAAG,OAAO,KAAK,IAAI,SAAS,CAAC,EAAE,8BAC/B,GAAG,OAAO,aAAa,CAAC,EAAE;EAC7B,CAAA,IACD,IACD;;AAET"}
|
|
@@ -208,7 +208,7 @@ function TimeRangePresets({ className, ...props }) {
|
|
|
208
208
|
}, [presets]);
|
|
209
209
|
return /* @__PURE__ */ jsx("div", {
|
|
210
210
|
"data-slot": "time-range-presets",
|
|
211
|
-
className: cn("max-h-80 min-w-52 overflow-y-auto border-border p-1.5 sm:border-
|
|
211
|
+
className: cn("max-h-80 min-w-52 overflow-y-auto border-border p-1.5 sm:border-e", className),
|
|
212
212
|
...props,
|
|
213
213
|
children: groups.map(([group, items]) => /* @__PURE__ */ jsxs("div", {
|
|
214
214
|
role: "group",
|
|
@@ -224,7 +224,7 @@ function TimeRangePresets({ className, ...props }) {
|
|
|
224
224
|
setValue(preset.expression);
|
|
225
225
|
close();
|
|
226
226
|
},
|
|
227
|
-
className: cn("flex w-full items-center rounded-md px-2 py-1.5 text-
|
|
227
|
+
className: cn("flex w-full items-center rounded-md px-2 py-1.5 text-start text-sm transition-colors", preset.expression === value ? "bg-accent font-medium text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground", focusRing),
|
|
228
228
|
children: preset.label
|
|
229
229
|
}, preset.expression))]
|
|
230
230
|
}, group))
|
|
@@ -339,7 +339,7 @@ function TimeRangeCalendar({ className, numberOfMonths = 1, ...props }) {
|
|
|
339
339
|
} : void 0);
|
|
340
340
|
return /* @__PURE__ */ jsx("div", {
|
|
341
341
|
"data-slot": "time-range-calendar",
|
|
342
|
-
className: cn("border-t border-border p-1 sm:border-
|
|
342
|
+
className: cn("border-t border-border p-1 sm:border-s sm:border-t-0", className),
|
|
343
343
|
...props,
|
|
344
344
|
children: /* @__PURE__ */ jsx(Calendar, {
|
|
345
345
|
mode: "range",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-range-picker.js","names":[],"sources":["../../../src/components/time-range-picker/time-range-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useId,\n useMemo,\n useState,\n useSyncExternalStore,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\nimport type { DateRange } from \"react-day-picker\";\n\nimport { Button } from \"@/components/button\";\nimport { Calendar } from \"@/components/calendar\";\nimport { Input } from \"@/components/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/popover\";\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n DEFAULT_PRESETS,\n TimeExpressionError,\n absoluteExpression,\n describeTimeRange,\n formatResolvedRange,\n resolveTimeRange,\n type ResolvedRange,\n type TimeRangePreset,\n} from \"./time-expression\";\n\n/**\n * The control every observability product builds for itself.\n *\n * Grafana, Datadog, Sentry, PostHog, Vercel, Honeycomb, Cloudflare and\n * Amplitude each maintain a bespoke one, and no React package ships it, because\n * it looks like a date picker and is not one — see `time-expression.ts` for the\n * value model that makes the difference.\n *\n * Composed rather than reimplemented: Popover supplies the dialog semantics,\n * Calendar the absolute path, Input the expression entry.\n *\n * Two omissions are deliberate. No timezone picker — that is a 400-entry\n * combobox and a decision an app makes once, so `timeZone` is a prop. And no\n * comparison range; a second window belongs to whatever renders the chart.\n */\n\ninterface TimeRangeContextValue {\n value: string;\n setValue: (expression: string) => void;\n now: Date;\n timeZone?: string;\n locale?: string;\n presets: TimeRangePreset[];\n resolved: ResolvedRange | null;\n error: string | null;\n /** False until the clock is known, which on a server render is never. */\n clockReady: boolean;\n close: () => void;\n labelId: string;\n}\n\n/** Stands in until the clock is known. Never shown; see `clockReady`. */\nconst EPOCH = new Date(0);\n\n/**\n * The clock, for callers who did not supply one.\n *\n * `useSyncExternalStore` rather than an effect, because this is the case it\n * exists for: a value the server cannot know. The server snapshot is null, so a\n * server render and the hydration render both commit to nothing clock-dependent\n * and cannot disagree; the real instant arrives in the re-render after.\n *\n * `getSnapshot` has to be referentially stable, so the instant is created once\n * for the page. That means every picker without a `now` prop shares it and none\n * of them advance — which is the whole reason the prop is there.\n */\nconst subscribeToNothing = () => () => undefined;\nlet sharedClock: Date | null = null;\nconst readSharedClock = () => (sharedClock ??= new Date());\nconst readNoClock = () => null;\n\nconst TimeRangeContext = createContext<TimeRangeContextValue | null>(null);\n\nfunction useTimeRange(component: string): TimeRangeContextValue {\n const context = useContext(TimeRangeContext);\n if (!context) {\n throw new Error(`${component} must be used inside <TimeRange>.`);\n }\n return context;\n}\n\nexport interface TimeRangeProps {\n /** The expression, e.g. `now-6h..now`. Controlled. */\n value?: string;\n defaultValue?: string;\n onValueChange?: (expression: string) => void;\n /**\n * The instant relative expressions are measured from. Supplied by the\n * consumer so nothing here reads the clock during render. Pass a value that\n * changes when you refresh; a stable one holds the window still between them.\n */\n now?: Date;\n /** IANA zone the snapping happens in. Defaults to the runtime zone. */\n timeZone?: string;\n locale?: string;\n presets?: TimeRangePreset[];\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n children: ReactNode;\n}\n\nexport function TimeRange({\n value: valueProp,\n defaultValue = \"now-6h..now\",\n onValueChange,\n now,\n timeZone,\n locale,\n presets = DEFAULT_PRESETS,\n open: openProp,\n onOpenChange,\n children,\n}: TimeRangeProps) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = valueProp ?? uncontrolledValue;\n\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n const open = openProp ?? uncontrolledOpen;\n\n const labelId = useId();\n\n // Reading the clock during render is what the `now` prop exists to avoid: the\n // server would render one instant and hydration another, and React would\n // throw out the tree. Until the fallback arrives, nothing clock-dependent is\n // rendered — the relative label, \"Last 6 hours\", does not need one, which is\n // why the trigger still says something useful in the meantime.\n const fallbackClock = useSyncExternalStore(subscribeToNothing, readSharedClock, readNoClock);\n\n const clock = now ?? fallbackClock;\n // A placeholder only for expressions that never mention `now`; anything that\n // does is gated on `clock` below rather than resolved against a fake instant.\n const effectiveNow = clock ?? EPOCH;\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (openProp === undefined) setUncontrolledOpen(next);\n onOpenChange?.(next);\n },\n [openProp, onOpenChange],\n );\n\n const setValue = useCallback(\n (expression: string) => {\n if (valueProp === undefined) setUncontrolledValue(expression);\n onValueChange?.(expression);\n },\n [valueProp, onValueChange],\n );\n\n const { resolved, error } = useMemo(() => {\n // Claiming neither a window nor an error before the clock is known: an\n // expression cannot be judged against an instant nobody has read yet.\n if (!clock) return { resolved: null, error: null };\n try {\n return { resolved: resolveTimeRange(value, { now: clock, timeZone }), error: null };\n } catch (thrown) {\n return {\n resolved: null,\n error: thrown instanceof TimeExpressionError ? thrown.message : \"Invalid time range.\",\n };\n }\n }, [value, clock, timeZone]);\n\n const context = useMemo<TimeRangeContextValue>(\n () => ({\n value,\n setValue,\n now: effectiveNow,\n timeZone,\n locale,\n presets,\n resolved,\n error,\n clockReady: clock !== null,\n close: () => {\n setOpen(false);\n },\n labelId,\n }),\n [\n value,\n setValue,\n effectiveNow,\n clock,\n timeZone,\n locale,\n presets,\n resolved,\n error,\n setOpen,\n labelId,\n ],\n );\n\n return (\n <TimeRangeContext.Provider value={context}>\n <Popover open={open} onOpenChange={setOpen}>\n {children}\n </Popover>\n </TimeRangeContext.Provider>\n );\n}\n\nfunction ClockIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" className=\"size-4 opacity-70\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <path d=\"M12 7v5l3 2\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n\nexport interface TimeRangeTriggerProps extends ComponentPropsWithRef<\"button\"> {\n /** Shows the resolved window under the label. Off by default; it is long. */\n showResolved?: boolean;\n}\n\n/**\n * The button, named by the range it holds: \"Last 6 hours\", not two timestamps.\n * The resolved window stays reachable as the title, and as `showResolved`.\n */\nexport function TimeRangeTrigger({\n className,\n showResolved = false,\n ...props\n}: TimeRangeTriggerProps) {\n const { value, now, timeZone, locale, presets, resolved, error } =\n useTimeRange(\"TimeRangeTrigger\");\n\n const label = describeTimeRange(value, { now, timeZone, locale, presets });\n const resolvedText = resolved ? formatResolvedRange(resolved, { locale, timeZone }) : null;\n\n return (\n <PopoverTrigger asChild>\n <Button\n data-slot=\"time-range-trigger\"\n variant=\"outline\"\n aria-invalid={error ? true : undefined}\n title={resolvedText ?? undefined}\n className={cn(\"justify-start gap-2 font-normal\", className)}\n {...props}\n >\n <ClockIcon />\n <span className=\"flex flex-col items-start leading-tight\">\n <span>{label}</span>\n {showResolved && resolvedText ? (\n <span className=\"text-2xs font-normal text-muted-foreground\">{resolvedText}</span>\n ) : null}\n </span>\n {/* The window the label stands for, for anyone who cannot see the\n title attribute. Read after the label, not instead of it. */}\n {resolvedText && !showResolved ? (\n <span className=\"sr-only\">{`, ${resolvedText}`}</span>\n ) : null}\n </Button>\n </PopoverTrigger>\n );\n}\n\nexport interface TimeRangeContentProps extends ComponentPropsWithRef<typeof PopoverContent> {\n /** Names the dialog. */\n label?: string;\n}\n\nexport function TimeRangeContent({\n className,\n label = \"Choose a time range\",\n children,\n ...props\n}: TimeRangeContentProps) {\n const { labelId } = useTimeRange(\"TimeRangeContent\");\n\n return (\n <PopoverContent\n data-slot=\"time-range-content\"\n aria-labelledby={labelId}\n align=\"start\"\n className={cn(\"w-auto max-w-[min(38rem,calc(100vw-2rem))] p-0\", className)}\n {...props}\n >\n <h2 id={labelId} className=\"sr-only\">\n {label}\n </h2>\n <div className=\"flex flex-col gap-0 sm:flex-row sm:items-stretch\">{children}</div>\n </PopoverContent>\n );\n}\n\n/**\n * The named ranges, which is how most of these are actually used. Buttons\n * rather than a radiogroup: choosing one both selects and dismisses, which is a\n * command, and `aria-pressed` still carries which one matches.\n */\nexport function TimeRangePresets({\n className,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"children\">) {\n const { presets, value, setValue, close } = useTimeRange(\"TimeRangePresets\");\n\n const groups = useMemo(() => {\n const collected = new Map<string, TimeRangePreset[]>();\n for (const preset of presets) {\n const existing = collected.get(preset.group);\n if (existing) existing.push(preset);\n else collected.set(preset.group, [preset]);\n }\n return [...collected];\n }, [presets]);\n\n return (\n <div\n data-slot=\"time-range-presets\"\n className={cn(\n \"max-h-80 min-w-52 overflow-y-auto border-border p-1.5 sm:border-r\",\n className,\n )}\n {...props}\n >\n {groups.map(([group, items]) => (\n <div key={group} role=\"group\" aria-label={group} className=\"pb-1 last:pb-0\">\n <p className=\"px-2 py-1 text-2xs font-medium tracking-wide text-muted-foreground uppercase\">\n {group}\n </p>\n {items.map((preset) => (\n <button\n key={preset.expression}\n type=\"button\"\n aria-pressed={preset.expression === value}\n onClick={() => {\n setValue(preset.expression);\n close();\n }}\n className={cn(\n \"flex w-full items-center rounded-md px-2 py-1.5 text-left text-sm transition-colors\",\n preset.expression === value\n ? \"bg-accent font-medium text-accent-foreground\"\n : \"hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n )}\n >\n {preset.label}\n </button>\n ))}\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * Raw expression entry, with the resolved window shown as you type — the escape\n * hatch that keeps the presets from being a ceiling.\n *\n * An expression that does not parse says why and is not applied: a chart\n * quietly re-scoping itself to a window nobody asked for is worse than one that\n * refuses.\n */\nexport function TimeRangeExpression({\n className,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"children\">) {\n const { value, setValue, now, timeZone, locale, clockReady, close } =\n useTimeRange(\"TimeRangeExpression\");\n\n const [draft, setDraft] = useState(value);\n // The expression can change under the input — a preset click, or the\n // consumer setting it. Adjusting during render rather than in an effect keeps\n // the field from showing a stale value for a frame.\n const [lastValue, setLastValue] = useState(value);\n if (value !== lastValue) {\n setLastValue(value);\n setDraft(value);\n }\n\n const feedbackId = useId();\n const preview = useMemo(() => {\n // Before the clock is known there is nothing to preview and nothing to\n // reject — saying \"invalid\" here would be a verdict reached without a fact.\n if (!clockReady) return { text: null, error: null as string | null };\n try {\n return {\n text: formatResolvedRange(resolveTimeRange(draft, { now, timeZone }), {\n locale,\n timeZone,\n }),\n error: null as string | null,\n };\n } catch (thrown) {\n return {\n text: null,\n error: thrown instanceof TimeExpressionError ? thrown.message : \"Invalid time range.\",\n };\n }\n }, [draft, now, timeZone, locale, clockReady]);\n\n const apply = () => {\n if (preview.error || !clockReady) return;\n setValue(draft.trim());\n close();\n };\n\n return (\n <div\n data-slot=\"time-range-expression\"\n className={cn(\n \"flex flex-col gap-1.5 border-t border-border p-3 sm:border-t-0\",\n className,\n )}\n {...props}\n >\n <label htmlFor={feedbackId + \"-input\"} className=\"text-xs font-medium\">\n Expression\n </label>\n <div className=\"flex gap-2\">\n <Input\n id={feedbackId + \"-input\"}\n inputSize=\"sm\"\n value={draft}\n spellCheck={false}\n autoComplete=\"off\"\n aria-invalid={preview.error ? true : undefined}\n aria-describedby={feedbackId}\n onChange={(event) => {\n setDraft(event.target.value);\n }}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n apply();\n }\n }}\n className=\"font-mono\"\n />\n <Button size=\"sm\" onClick={apply} disabled={Boolean(preview.error) || !clockReady}>\n Apply\n </Button>\n </div>\n {/* One element for both outcomes, so a screen reader hears the error\n replace the preview rather than accumulating two descriptions. */}\n <p\n id={feedbackId}\n className={cn(\n \"min-h-4 text-2xs\",\n preview.error ? \"text-destructive\" : \"text-muted-foreground\",\n )}\n >\n {preview.error ?? preview.text}\n </p>\n </div>\n );\n}\n\n/**\n * The absolute path, for a window with no relative meaning. Picking dates\n * writes a concrete expression: \"1 to 7 March\" does not become relative just\n * because it was chosen here.\n */\nexport function TimeRangeCalendar({\n className,\n numberOfMonths = 1,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"children\"> & { numberOfMonths?: number }) {\n const { setValue, resolved, close } = useTimeRange(\"TimeRangeCalendar\");\n\n const [selection, setSelection] = useState<DateRange | undefined>(() =>\n resolved ? { from: resolved.from, to: resolved.to } : undefined,\n );\n\n return (\n <div\n data-slot=\"time-range-calendar\"\n className={cn(\"border-t border-border p-1 sm:border-t-0 sm:border-l\", className)}\n {...props}\n >\n <Calendar\n mode=\"range\"\n selected={selection}\n defaultMonth={selection?.from}\n numberOfMonths={numberOfMonths}\n onSelect={(next) => {\n setSelection(next);\n // Only once both ends exist. One click is half an answer, and\n // applying it would show a zero-length range mid-gesture.\n if (next?.from && next.to) {\n setValue(absoluteExpression(startOfDay(next.from), endOfDay(next.to)));\n close();\n }\n }}\n />\n </div>\n );\n}\n\nfunction startOfDay(date: Date): Date {\n const start = new Date(date);\n start.setHours(0, 0, 0, 0);\n return start;\n}\n\n/**\n * A day picked as the end of a range means the whole of that day. Taking\n * midnight instead silently drops the last day's data, which is the same\n * off-by-one that `now/d..now/d` has to avoid.\n */\nfunction endOfDay(date: Date): Date {\n const end = new Date(date);\n end.setHours(23, 59, 59, 999);\n return end;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"time-range-picker.js","names":[],"sources":["../../../src/components/time-range-picker/time-range-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useId,\n useMemo,\n useState,\n useSyncExternalStore,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\nimport type { DateRange } from \"react-day-picker\";\n\nimport { Button } from \"@/components/button\";\nimport { Calendar } from \"@/components/calendar\";\nimport { Input } from \"@/components/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/popover\";\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n DEFAULT_PRESETS,\n TimeExpressionError,\n absoluteExpression,\n describeTimeRange,\n formatResolvedRange,\n resolveTimeRange,\n type ResolvedRange,\n type TimeRangePreset,\n} from \"./time-expression\";\n\n/**\n * The control every observability product builds for itself.\n *\n * Grafana, Datadog, Sentry, PostHog, Vercel, Honeycomb, Cloudflare and\n * Amplitude each maintain a bespoke one, and no React package ships it, because\n * it looks like a date picker and is not one — see `time-expression.ts` for the\n * value model that makes the difference.\n *\n * Composed rather than reimplemented: Popover supplies the dialog semantics,\n * Calendar the absolute path, Input the expression entry.\n *\n * Two omissions are deliberate. No timezone picker — that is a 400-entry\n * combobox and a decision an app makes once, so `timeZone` is a prop. And no\n * comparison range; a second window belongs to whatever renders the chart.\n */\n\ninterface TimeRangeContextValue {\n value: string;\n setValue: (expression: string) => void;\n now: Date;\n timeZone?: string;\n locale?: string;\n presets: TimeRangePreset[];\n resolved: ResolvedRange | null;\n error: string | null;\n /** False until the clock is known, which on a server render is never. */\n clockReady: boolean;\n close: () => void;\n labelId: string;\n}\n\n/** Stands in until the clock is known. Never shown; see `clockReady`. */\nconst EPOCH = new Date(0);\n\n/**\n * The clock, for callers who did not supply one.\n *\n * `useSyncExternalStore` rather than an effect, because this is the case it\n * exists for: a value the server cannot know. The server snapshot is null, so a\n * server render and the hydration render both commit to nothing clock-dependent\n * and cannot disagree; the real instant arrives in the re-render after.\n *\n * `getSnapshot` has to be referentially stable, so the instant is created once\n * for the page. That means every picker without a `now` prop shares it and none\n * of them advance — which is the whole reason the prop is there.\n */\nconst subscribeToNothing = () => () => undefined;\nlet sharedClock: Date | null = null;\nconst readSharedClock = () => (sharedClock ??= new Date());\nconst readNoClock = () => null;\n\nconst TimeRangeContext = createContext<TimeRangeContextValue | null>(null);\n\nfunction useTimeRange(component: string): TimeRangeContextValue {\n const context = useContext(TimeRangeContext);\n if (!context) {\n throw new Error(`${component} must be used inside <TimeRange>.`);\n }\n return context;\n}\n\nexport interface TimeRangeProps {\n /** The expression, e.g. `now-6h..now`. Controlled. */\n value?: string;\n defaultValue?: string;\n onValueChange?: (expression: string) => void;\n /**\n * The instant relative expressions are measured from. Supplied by the\n * consumer so nothing here reads the clock during render. Pass a value that\n * changes when you refresh; a stable one holds the window still between them.\n */\n now?: Date;\n /** IANA zone the snapping happens in. Defaults to the runtime zone. */\n timeZone?: string;\n locale?: string;\n presets?: TimeRangePreset[];\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n children: ReactNode;\n}\n\nexport function TimeRange({\n value: valueProp,\n defaultValue = \"now-6h..now\",\n onValueChange,\n now,\n timeZone,\n locale,\n presets = DEFAULT_PRESETS,\n open: openProp,\n onOpenChange,\n children,\n}: TimeRangeProps) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = valueProp ?? uncontrolledValue;\n\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n const open = openProp ?? uncontrolledOpen;\n\n const labelId = useId();\n\n // Reading the clock during render is what the `now` prop exists to avoid: the\n // server would render one instant and hydration another, and React would\n // throw out the tree. Until the fallback arrives, nothing clock-dependent is\n // rendered — the relative label, \"Last 6 hours\", does not need one, which is\n // why the trigger still says something useful in the meantime.\n const fallbackClock = useSyncExternalStore(subscribeToNothing, readSharedClock, readNoClock);\n\n const clock = now ?? fallbackClock;\n // A placeholder only for expressions that never mention `now`; anything that\n // does is gated on `clock` below rather than resolved against a fake instant.\n const effectiveNow = clock ?? EPOCH;\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (openProp === undefined) setUncontrolledOpen(next);\n onOpenChange?.(next);\n },\n [openProp, onOpenChange],\n );\n\n const setValue = useCallback(\n (expression: string) => {\n if (valueProp === undefined) setUncontrolledValue(expression);\n onValueChange?.(expression);\n },\n [valueProp, onValueChange],\n );\n\n const { resolved, error } = useMemo(() => {\n // Claiming neither a window nor an error before the clock is known: an\n // expression cannot be judged against an instant nobody has read yet.\n if (!clock) return { resolved: null, error: null };\n try {\n return { resolved: resolveTimeRange(value, { now: clock, timeZone }), error: null };\n } catch (thrown) {\n return {\n resolved: null,\n error: thrown instanceof TimeExpressionError ? thrown.message : \"Invalid time range.\",\n };\n }\n }, [value, clock, timeZone]);\n\n const context = useMemo<TimeRangeContextValue>(\n () => ({\n value,\n setValue,\n now: effectiveNow,\n timeZone,\n locale,\n presets,\n resolved,\n error,\n clockReady: clock !== null,\n close: () => {\n setOpen(false);\n },\n labelId,\n }),\n [\n value,\n setValue,\n effectiveNow,\n clock,\n timeZone,\n locale,\n presets,\n resolved,\n error,\n setOpen,\n labelId,\n ],\n );\n\n return (\n <TimeRangeContext.Provider value={context}>\n <Popover open={open} onOpenChange={setOpen}>\n {children}\n </Popover>\n </TimeRangeContext.Provider>\n );\n}\n\nfunction ClockIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" className=\"size-4 opacity-70\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <path d=\"M12 7v5l3 2\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n\nexport interface TimeRangeTriggerProps extends ComponentPropsWithRef<\"button\"> {\n /** Shows the resolved window under the label. Off by default; it is long. */\n showResolved?: boolean;\n}\n\n/**\n * The button, named by the range it holds: \"Last 6 hours\", not two timestamps.\n * The resolved window stays reachable as the title, and as `showResolved`.\n */\nexport function TimeRangeTrigger({\n className,\n showResolved = false,\n ...props\n}: TimeRangeTriggerProps) {\n const { value, now, timeZone, locale, presets, resolved, error } =\n useTimeRange(\"TimeRangeTrigger\");\n\n const label = describeTimeRange(value, { now, timeZone, locale, presets });\n const resolvedText = resolved ? formatResolvedRange(resolved, { locale, timeZone }) : null;\n\n return (\n <PopoverTrigger asChild>\n <Button\n data-slot=\"time-range-trigger\"\n variant=\"outline\"\n aria-invalid={error ? true : undefined}\n title={resolvedText ?? undefined}\n className={cn(\"justify-start gap-2 font-normal\", className)}\n {...props}\n >\n <ClockIcon />\n <span className=\"flex flex-col items-start leading-tight\">\n <span>{label}</span>\n {showResolved && resolvedText ? (\n <span className=\"text-2xs font-normal text-muted-foreground\">{resolvedText}</span>\n ) : null}\n </span>\n {/* The window the label stands for, for anyone who cannot see the\n title attribute. Read after the label, not instead of it. */}\n {resolvedText && !showResolved ? (\n <span className=\"sr-only\">{`, ${resolvedText}`}</span>\n ) : null}\n </Button>\n </PopoverTrigger>\n );\n}\n\nexport interface TimeRangeContentProps extends ComponentPropsWithRef<typeof PopoverContent> {\n /** Names the dialog. */\n label?: string;\n}\n\nexport function TimeRangeContent({\n className,\n label = \"Choose a time range\",\n children,\n ...props\n}: TimeRangeContentProps) {\n const { labelId } = useTimeRange(\"TimeRangeContent\");\n\n return (\n <PopoverContent\n data-slot=\"time-range-content\"\n aria-labelledby={labelId}\n align=\"start\"\n className={cn(\"w-auto max-w-[min(38rem,calc(100vw-2rem))] p-0\", className)}\n {...props}\n >\n <h2 id={labelId} className=\"sr-only\">\n {label}\n </h2>\n <div className=\"flex flex-col gap-0 sm:flex-row sm:items-stretch\">{children}</div>\n </PopoverContent>\n );\n}\n\n/**\n * The named ranges, which is how most of these are actually used. Buttons\n * rather than a radiogroup: choosing one both selects and dismisses, which is a\n * command, and `aria-pressed` still carries which one matches.\n */\nexport function TimeRangePresets({\n className,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"children\">) {\n const { presets, value, setValue, close } = useTimeRange(\"TimeRangePresets\");\n\n const groups = useMemo(() => {\n const collected = new Map<string, TimeRangePreset[]>();\n for (const preset of presets) {\n const existing = collected.get(preset.group);\n if (existing) existing.push(preset);\n else collected.set(preset.group, [preset]);\n }\n return [...collected];\n }, [presets]);\n\n return (\n <div\n data-slot=\"time-range-presets\"\n className={cn(\n \"max-h-80 min-w-52 overflow-y-auto border-border p-1.5 sm:border-e\",\n className,\n )}\n {...props}\n >\n {groups.map(([group, items]) => (\n <div key={group} role=\"group\" aria-label={group} className=\"pb-1 last:pb-0\">\n <p className=\"px-2 py-1 text-2xs font-medium tracking-wide text-muted-foreground uppercase\">\n {group}\n </p>\n {items.map((preset) => (\n <button\n key={preset.expression}\n type=\"button\"\n aria-pressed={preset.expression === value}\n onClick={() => {\n setValue(preset.expression);\n close();\n }}\n className={cn(\n \"flex w-full items-center rounded-md px-2 py-1.5 text-start text-sm transition-colors\",\n preset.expression === value\n ? \"bg-accent font-medium text-accent-foreground\"\n : \"hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n )}\n >\n {preset.label}\n </button>\n ))}\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * Raw expression entry, with the resolved window shown as you type — the escape\n * hatch that keeps the presets from being a ceiling.\n *\n * An expression that does not parse says why and is not applied: a chart\n * quietly re-scoping itself to a window nobody asked for is worse than one that\n * refuses.\n */\nexport function TimeRangeExpression({\n className,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"children\">) {\n const { value, setValue, now, timeZone, locale, clockReady, close } =\n useTimeRange(\"TimeRangeExpression\");\n\n const [draft, setDraft] = useState(value);\n // The expression can change under the input — a preset click, or the\n // consumer setting it. Adjusting during render rather than in an effect keeps\n // the field from showing a stale value for a frame.\n const [lastValue, setLastValue] = useState(value);\n if (value !== lastValue) {\n setLastValue(value);\n setDraft(value);\n }\n\n const feedbackId = useId();\n const preview = useMemo(() => {\n // Before the clock is known there is nothing to preview and nothing to\n // reject — saying \"invalid\" here would be a verdict reached without a fact.\n if (!clockReady) return { text: null, error: null as string | null };\n try {\n return {\n text: formatResolvedRange(resolveTimeRange(draft, { now, timeZone }), {\n locale,\n timeZone,\n }),\n error: null as string | null,\n };\n } catch (thrown) {\n return {\n text: null,\n error: thrown instanceof TimeExpressionError ? thrown.message : \"Invalid time range.\",\n };\n }\n }, [draft, now, timeZone, locale, clockReady]);\n\n const apply = () => {\n if (preview.error || !clockReady) return;\n setValue(draft.trim());\n close();\n };\n\n return (\n <div\n data-slot=\"time-range-expression\"\n className={cn(\n \"flex flex-col gap-1.5 border-t border-border p-3 sm:border-t-0\",\n className,\n )}\n {...props}\n >\n <label htmlFor={feedbackId + \"-input\"} className=\"text-xs font-medium\">\n Expression\n </label>\n <div className=\"flex gap-2\">\n <Input\n id={feedbackId + \"-input\"}\n inputSize=\"sm\"\n value={draft}\n spellCheck={false}\n autoComplete=\"off\"\n aria-invalid={preview.error ? true : undefined}\n aria-describedby={feedbackId}\n onChange={(event) => {\n setDraft(event.target.value);\n }}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n apply();\n }\n }}\n className=\"font-mono\"\n />\n <Button size=\"sm\" onClick={apply} disabled={Boolean(preview.error) || !clockReady}>\n Apply\n </Button>\n </div>\n {/* One element for both outcomes, so a screen reader hears the error\n replace the preview rather than accumulating two descriptions. */}\n <p\n id={feedbackId}\n className={cn(\n \"min-h-4 text-2xs\",\n preview.error ? \"text-destructive\" : \"text-muted-foreground\",\n )}\n >\n {preview.error ?? preview.text}\n </p>\n </div>\n );\n}\n\n/**\n * The absolute path, for a window with no relative meaning. Picking dates\n * writes a concrete expression: \"1 to 7 March\" does not become relative just\n * because it was chosen here.\n */\nexport function TimeRangeCalendar({\n className,\n numberOfMonths = 1,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"children\"> & { numberOfMonths?: number }) {\n const { setValue, resolved, close } = useTimeRange(\"TimeRangeCalendar\");\n\n const [selection, setSelection] = useState<DateRange | undefined>(() =>\n resolved ? { from: resolved.from, to: resolved.to } : undefined,\n );\n\n return (\n <div\n data-slot=\"time-range-calendar\"\n className={cn(\"border-t border-border p-1 sm:border-s sm:border-t-0\", className)}\n {...props}\n >\n <Calendar\n mode=\"range\"\n selected={selection}\n defaultMonth={selection?.from}\n numberOfMonths={numberOfMonths}\n onSelect={(next) => {\n setSelection(next);\n // Only once both ends exist. One click is half an answer, and\n // applying it would show a zero-length range mid-gesture.\n if (next?.from && next.to) {\n setValue(absoluteExpression(startOfDay(next.from), endOfDay(next.to)));\n close();\n }\n }}\n />\n </div>\n );\n}\n\nfunction startOfDay(date: Date): Date {\n const start = new Date(date);\n start.setHours(0, 0, 0, 0);\n return start;\n}\n\n/**\n * A day picked as the end of a range means the whole of that day. Taking\n * midnight instead silently drops the last day's data, which is the same\n * off-by-one that `now/d..now/d` has to avoid.\n */\nfunction endOfDay(date: Date): Date {\n const end = new Date(date);\n end.setHours(23, 59, 59, 999);\n return end;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.d.ts","names":[],"sources":["../../../src/components/toast/toast.tsx"],"mappings":";;;;;;;;;;;;;cA2BM,gBAAa;;6CAyBlB;cAEY,+BAAa,GAAA,MAAA;UAET,mBAEb,6BAA6B,MAAe,OAC5C,oBAAoB;iBAER,UAAQ,WAAW,YAAY,SAAS,6BAAU,IAAA;iBAUlD,aACd,cACG,SACF,6BAA6B,MAAe,yBAAM,IAAA;iBAUrC,mBACd,cACG,SACF,6BAA6B,MAAe,+BAAY,IAAA;iBAU3C,cACd,cACG,SACF,6BAA6B,MAAe,0BAAO,IAAA;iBAetC,aACd,cACG,SACF,6BAA6B,MAAe,yBAAM,IAAA;KAyBzC;
|
|
1
|
+
{"version":3,"file":"toast.d.ts","names":[],"sources":["../../../src/components/toast/toast.tsx"],"mappings":";;;;;;;;;;;;;cA2BM,gBAAa;;6CAyBlB;cAEY,+BAAa,GAAA,MAAA;UAET,mBAEb,6BAA6B,MAAe,OAC5C,oBAAoB;iBAER,UAAQ,WAAW,YAAY,SAAS,6BAAU,IAAA;iBAUlD,aACd,cACG,SACF,6BAA6B,MAAe,yBAAM,IAAA;iBAUrC,mBACd,cACG,SACF,6BAA6B,MAAe,+BAAY,IAAA;iBAU3C,cACd,cACG,SACF,6BAA6B,MAAe,0BAAO,IAAA;iBAetC,aACd,cACG,SACF,6BAA6B,MAAe,yBAAM,IAAA;KAyBzC;UAoBK;EACf,WAAW;;EAEX;EACA;;EAEA;;;;;iBAMc,UACd,UACA,UACA,WACA,SACC,+BAAY,IAAA"}
|
|
@@ -15,7 +15,7 @@ import { cva } from "class-variance-authority";
|
|
|
15
15
|
* hover, focus and window blur, swipe-to-dismiss, and F8 to jump focus into the
|
|
16
16
|
* toast list from anywhere on the page.
|
|
17
17
|
*/
|
|
18
|
-
const toastVariants = cva(cn("group pointer-events-auto relative flex w-full items-start gap-3 overflow-hidden", "rounded-lg border p-4
|
|
18
|
+
const toastVariants = cva(cn("group pointer-events-auto relative flex w-full items-start gap-3 overflow-hidden", "rounded-lg border p-4 pe-10 shadow-lg", "[--slide-x:calc(100%+1rem)]", "data-[state=closed]:animate-float-out data-[state=open]:animate-slide-in", "data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none", "data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-transform", "data-[swipe=end]:animate-toast-swipe-out"), {
|
|
19
19
|
variants: { variant: {
|
|
20
20
|
default: "border-border bg-popover text-popover-foreground",
|
|
21
21
|
success: "border-success/30 bg-popover text-popover-foreground [&_svg]:text-success",
|
|
@@ -58,7 +58,7 @@ function ToastClose({ className, ...props }) {
|
|
|
58
58
|
return /* @__PURE__ */ jsx(Toast.Close, {
|
|
59
59
|
"data-slot": "toast-close",
|
|
60
60
|
"aria-label": "Dismiss",
|
|
61
|
-
className: cn("absolute
|
|
61
|
+
className: cn("absolute end-3 top-3 grid size-6 place-items-center rounded-md text-muted-foreground", "transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground", focusRing, className),
|
|
62
62
|
...props,
|
|
63
63
|
children: /* @__PURE__ */ jsx("svg", {
|
|
64
64
|
viewBox: "0 0 24 24",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":["ToastPrimitive","Toast"],"sources":["../../../src/components/toast/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Toast as ToastPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n DEFAULT_TOAST_DURATION,\n toast,\n useToasts,\n type ToastRecord,\n type ToastVariant,\n} from \"./toast-store\";\n\n/**\n * Brief, non-blocking messages about something that just happened.\n *\n * Render one `<Toaster />` near the root of the app; raise messages from\n * anywhere with `toast()`. The primitive underneath supplies the parts that are\n * easy to get wrong: a screen-reader announcement region, timers that pause on\n * hover, focus and window blur, swipe-to-dismiss, and F8 to jump focus into the\n * toast list from anywhere on the page.\n */\n\nconst toastVariants = cva(\n cn(\n \"group pointer-events-auto relative flex w-full items-start gap-3 overflow-hidden\",\n \"rounded-lg border p-4 pr-10 shadow-lg\",\n \"[--slide-x:calc(100%+1rem)]\",\n \"data-[state=closed]:animate-float-out data-[state=open]:animate-slide-in\",\n \"data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none\",\n \"data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-transform\",\n \"data-[swipe=end]:animate-toast-swipe-out\",\n ),\n {\n variants: {\n variant: {\n default: \"border-border bg-popover text-popover-foreground\",\n success: \"border-success/30 bg-popover text-popover-foreground [&_svg]:text-success\",\n warning: \"border-warning/35 bg-popover text-popover-foreground [&_svg]:text-warning\",\n destructive:\n \"border-destructive/30 bg-popover text-popover-foreground [&_svg]:text-destructive\",\n info: \"border-info/30 bg-popover text-popover-foreground [&_svg]:text-info\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nexport const ToastProvider = ToastPrimitive.Provider;\n\nexport interface ToastProps\n extends\n ComponentPropsWithRef<typeof ToastPrimitive.Root>,\n VariantProps<typeof toastVariants> {}\n\nexport function Toast({ className, variant, ...props }: ToastProps) {\n return (\n <ToastPrimitive.Root\n data-slot=\"toast\"\n className={cn(toastVariants({ variant }), className)}\n {...props}\n />\n );\n}\n\nexport function ToastTitle({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Title>) {\n return (\n <ToastPrimitive.Title\n data-slot=\"toast-title\"\n className={cn(\"text-sm leading-snug font-medium\", className)}\n {...props}\n />\n );\n}\n\nexport function ToastDescription({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Description>) {\n return (\n <ToastPrimitive.Description\n data-slot=\"toast-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport function ToastAction({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Action>) {\n return (\n <ToastPrimitive.Action\n data-slot=\"toast-action\"\n className={cn(\n \"inline-flex h-8 shrink-0 items-center rounded-md border border-input bg-transparent px-3 text-xs font-medium\",\n \"transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function ToastClose({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Close>) {\n return (\n <ToastPrimitive.Close\n data-slot=\"toast-close\"\n aria-label=\"Dismiss\"\n className={cn(\n \"absolute top-3 right-3 grid size-6 place-items-center rounded-md text-muted-foreground\",\n \"transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground\",\n focusRing,\n className,\n )}\n {...props}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" className=\"size-3.5\">\n <path\n d=\"m6 6 12 12M18 6 6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </ToastPrimitive.Close>\n );\n}\n\nexport type ToastPosition =\n \"top-left\" | \"top-center\" | \"top-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\";\n\nconst viewportPositions: Record<ToastPosition, string> = {\n \"top-left\": \"top-0 left-0 sm:flex-col-reverse\",\n \"top-center\": \"top-0 left-1/2 -translate-x-1/2 sm:flex-col-reverse\",\n \"top-right\": \"top-0 right-0 sm:flex-col-reverse\",\n \"bottom-left\": \"bottom-0 left-0\",\n \"bottom-center\": \"bottom-0 left-1/2 -translate-x-1/2\",\n \"bottom-right\": \"bottom-0 right-0\",\n};\n\nexport interface ToasterProps {\n position?: ToastPosition;\n /** Default auto-dismiss in ms. Individual toasts may override it. */\n duration?: number;\n className?: string;\n /** Names the toast region for assistive technology. */\n label?: string;\n}\n\n/**\n * Renders the toast queue. Mount once, near the root of the application.\n */\nexport function Toaster({\n position = \"bottom-right\",\n duration = DEFAULT_TOAST_DURATION,\n className,\n label = \"Notifications\",\n}: ToasterProps) {\n const toasts = useToasts();\n // Swiping should follow the edge the toasts are docked against.\n const swipeDirection = position.endsWith(\"left\") ? \"left\" : \"right\";\n\n return (\n <ToastPrimitive.Provider swipeDirection={swipeDirection} duration={duration} label={label}>\n {toasts.map((record) => (\n <ToastItem key={record.id} record={record} />\n ))}\n <ToastPrimitive.Viewport\n data-slot=\"toast-viewport\"\n className={cn(\n \"pointer-events-none fixed z-[var(--z-toast)] flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm\",\n viewportPositions[position],\n className,\n )}\n />\n </ToastPrimitive.Provider>\n );\n}\n\n/** Variants that report a problem should interrupt rather than wait for a pause. */\nconst FOREGROUND_VARIANTS = new Set<ToastVariant>([\"destructive\", \"warning\"]);\n\n/**\n * Largest delay setTimeout accepts (2^31 - 1 ms, about 24 days).\n *\n * Anything above this silently overflows to a 32-bit integer and fires almost\n * immediately, so mapping `Infinity` to MAX_SAFE_INTEGER would dismiss a\n * persistent toast after roughly one millisecond — the exact opposite of what\n * was asked for.\n */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\nfunction ToastItem({ record }: { record: ToastRecord }) {\n const { id, title, description, variant, action, duration } = record;\n\n const type = FOREGROUND_VARIANTS.has(variant) ? \"foreground\" : \"background\";\n\n return (\n <Toast\n variant={variant}\n type={type}\n // Mirrored onto the element because the primitive expresses `type` only\n // through the transient live region it renders elsewhere. Having it here\n // gives styling and tests something stable to read.\n data-type={type}\n duration={duration === Infinity ? MAX_TIMEOUT_MS : duration}\n onOpenChange={(open) => {\n // Covers every route out: the timer, the close button and a swipe.\n if (!open) toast.dismiss(id);\n }}\n >\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {title === undefined ? null : <ToastTitle>{title}</ToastTitle>}\n {description === undefined ? null : <ToastDescription>{description}</ToastDescription>}\n </div>\n {action ? (\n <ToastAction altText={action.altText ?? action.label} onClick={action.onClick}>\n {action.label}\n </ToastAction>\n ) : null}\n <ToastClose />\n </Toast>\n );\n}\n\nexport { toastVariants };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toast.js","names":["ToastPrimitive","Toast"],"sources":["../../../src/components/toast/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Toast as ToastPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n DEFAULT_TOAST_DURATION,\n toast,\n useToasts,\n type ToastRecord,\n type ToastVariant,\n} from \"./toast-store\";\n\n/**\n * Brief, non-blocking messages about something that just happened.\n *\n * Render one `<Toaster />` near the root of the app; raise messages from\n * anywhere with `toast()`. The primitive underneath supplies the parts that are\n * easy to get wrong: a screen-reader announcement region, timers that pause on\n * hover, focus and window blur, swipe-to-dismiss, and F8 to jump focus into the\n * toast list from anywhere on the page.\n */\n\nconst toastVariants = cva(\n cn(\n \"group pointer-events-auto relative flex w-full items-start gap-3 overflow-hidden\",\n \"rounded-lg border p-4 pe-10 shadow-lg\",\n \"[--slide-x:calc(100%+1rem)]\",\n \"data-[state=closed]:animate-float-out data-[state=open]:animate-slide-in\",\n \"data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none\",\n \"data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-transform\",\n \"data-[swipe=end]:animate-toast-swipe-out\",\n ),\n {\n variants: {\n variant: {\n default: \"border-border bg-popover text-popover-foreground\",\n success: \"border-success/30 bg-popover text-popover-foreground [&_svg]:text-success\",\n warning: \"border-warning/35 bg-popover text-popover-foreground [&_svg]:text-warning\",\n destructive:\n \"border-destructive/30 bg-popover text-popover-foreground [&_svg]:text-destructive\",\n info: \"border-info/30 bg-popover text-popover-foreground [&_svg]:text-info\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nexport const ToastProvider = ToastPrimitive.Provider;\n\nexport interface ToastProps\n extends\n ComponentPropsWithRef<typeof ToastPrimitive.Root>,\n VariantProps<typeof toastVariants> {}\n\nexport function Toast({ className, variant, ...props }: ToastProps) {\n return (\n <ToastPrimitive.Root\n data-slot=\"toast\"\n className={cn(toastVariants({ variant }), className)}\n {...props}\n />\n );\n}\n\nexport function ToastTitle({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Title>) {\n return (\n <ToastPrimitive.Title\n data-slot=\"toast-title\"\n className={cn(\"text-sm leading-snug font-medium\", className)}\n {...props}\n />\n );\n}\n\nexport function ToastDescription({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Description>) {\n return (\n <ToastPrimitive.Description\n data-slot=\"toast-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport function ToastAction({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Action>) {\n return (\n <ToastPrimitive.Action\n data-slot=\"toast-action\"\n className={cn(\n \"inline-flex h-8 shrink-0 items-center rounded-md border border-input bg-transparent px-3 text-xs font-medium\",\n \"transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n className,\n )}\n {...props}\n />\n );\n}\n\nexport function ToastClose({\n className,\n ...props\n}: ComponentPropsWithRef<typeof ToastPrimitive.Close>) {\n return (\n <ToastPrimitive.Close\n data-slot=\"toast-close\"\n aria-label=\"Dismiss\"\n className={cn(\n \"absolute end-3 top-3 grid size-6 place-items-center rounded-md text-muted-foreground\",\n \"transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground\",\n focusRing,\n className,\n )}\n {...props}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" className=\"size-3.5\">\n <path\n d=\"m6 6 12 12M18 6 6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </ToastPrimitive.Close>\n );\n}\n\nexport type ToastPosition =\n \"top-left\" | \"top-center\" | \"top-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\";\n\n/*\n rtl-ok: the positions are *named* left and right, so they stay physical. A\n toast asked for at `bottom-right` that appears bottom-left in Arabic is the\n same lie as a Sheet asked for on the left that opens on the right — and it is\n worse here, because the slide-in comes from the right edge and the toast would\n fly across the screen to reach the other one. The logical version of this is a\n `bottom-end` position, which is a rename rather than a restyle.\n*/\nconst viewportPositions: Record<ToastPosition, string> = {\n \"top-left\": \"top-0 left-0 sm:flex-col-reverse\", // rtl-ok: named position\n \"top-center\": \"top-0 left-1/2 -translate-x-1/2 sm:flex-col-reverse\",\n \"top-right\": \"top-0 right-0 sm:flex-col-reverse\", // rtl-ok: named position\n \"bottom-left\": \"bottom-0 left-0\", // rtl-ok: named position\n \"bottom-center\": \"bottom-0 left-1/2 -translate-x-1/2\",\n \"bottom-right\": \"bottom-0 right-0\", // rtl-ok: named position\n};\n\nexport interface ToasterProps {\n position?: ToastPosition;\n /** Default auto-dismiss in ms. Individual toasts may override it. */\n duration?: number;\n className?: string;\n /** Names the toast region for assistive technology. */\n label?: string;\n}\n\n/**\n * Renders the toast queue. Mount once, near the root of the application.\n */\nexport function Toaster({\n position = \"bottom-right\",\n duration = DEFAULT_TOAST_DURATION,\n className,\n label = \"Notifications\",\n}: ToasterProps) {\n const toasts = useToasts();\n // Swiping should follow the edge the toasts are docked against.\n const swipeDirection = position.endsWith(\"left\") ? \"left\" : \"right\";\n\n return (\n <ToastPrimitive.Provider swipeDirection={swipeDirection} duration={duration} label={label}>\n {toasts.map((record) => (\n <ToastItem key={record.id} record={record} />\n ))}\n <ToastPrimitive.Viewport\n data-slot=\"toast-viewport\"\n className={cn(\n \"pointer-events-none fixed z-[var(--z-toast)] flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm\",\n viewportPositions[position],\n className,\n )}\n />\n </ToastPrimitive.Provider>\n );\n}\n\n/** Variants that report a problem should interrupt rather than wait for a pause. */\nconst FOREGROUND_VARIANTS = new Set<ToastVariant>([\"destructive\", \"warning\"]);\n\n/**\n * Largest delay setTimeout accepts (2^31 - 1 ms, about 24 days).\n *\n * Anything above this silently overflows to a 32-bit integer and fires almost\n * immediately, so mapping `Infinity` to MAX_SAFE_INTEGER would dismiss a\n * persistent toast after roughly one millisecond — the exact opposite of what\n * was asked for.\n */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\nfunction ToastItem({ record }: { record: ToastRecord }) {\n const { id, title, description, variant, action, duration } = record;\n\n const type = FOREGROUND_VARIANTS.has(variant) ? \"foreground\" : \"background\";\n\n return (\n <Toast\n variant={variant}\n type={type}\n // Mirrored onto the element because the primitive expresses `type` only\n // through the transient live region it renders elsewhere. Having it here\n // gives styling and tests something stable to read.\n data-type={type}\n duration={duration === Infinity ? MAX_TIMEOUT_MS : duration}\n onOpenChange={(open) => {\n // Covers every route out: the timer, the close button and a swipe.\n if (!open) toast.dismiss(id);\n }}\n >\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {title === undefined ? null : <ToastTitle>{title}</ToastTitle>}\n {description === undefined ? null : <ToastDescription>{description}</ToastDescription>}\n </div>\n {action ? (\n <ToastAction altText={action.altText ?? action.label} onClick={action.onClick}>\n {action.label}\n </ToastAction>\n ) : null}\n <ToastClose />\n </Toast>\n );\n}\n\nexport { toastVariants };\n"],"mappings":"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"}
|