@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Direction as DirectionPrimitive } from "radix-ui";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Tells the component set which way the writing runs.
|
|
8
|
+
*
|
|
9
|
+
* Every direction-dependent style in this library is written logically —
|
|
10
|
+
* `ps-`, `me-`, `text-end` — so the CSS follows `dir` on its own. The
|
|
11
|
+
* primitives underneath do not: several of them read direction from React
|
|
12
|
+
* context rather than from the document, and with no provider they assume
|
|
13
|
+
* left-to-right and set `dir="ltr"` on their own elements. The result is a page
|
|
14
|
+
* that mirrors correctly except for its menus, selects and sliders, which is
|
|
15
|
+
* worse than one that does not mirror at all, because it looks deliberate.
|
|
16
|
+
*
|
|
17
|
+
* So an RTL application needs both: `dir` on the document for the CSS, and this
|
|
18
|
+
* for the primitives.
|
|
19
|
+
*
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <html dir="rtl" lang="ar">
|
|
22
|
+
* <body>
|
|
23
|
+
* <DirectionProvider dir="rtl">{children}</DirectionProvider>
|
|
24
|
+
* </body>
|
|
25
|
+
* </html>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* A left-to-right application needs neither. Nothing here is required to render
|
|
29
|
+
* English correctly, which is why it is easy to ship without it and never know.
|
|
30
|
+
*/
|
|
31
|
+
export interface DirectionProviderProps {
|
|
32
|
+
dir: "ltr" | "rtl";
|
|
33
|
+
children?: ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function DirectionProvider({ dir, children }: DirectionProviderProps) {
|
|
37
|
+
return <DirectionPrimitive.Provider dir={dir}>{children}</DirectionPrimitive.Provider>;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DirectionProvider, type DirectionProviderProps } from "./direction";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "direction",
|
|
5
|
+
title: "Direction Provider",
|
|
6
|
+
description:
|
|
7
|
+
"Tells the component primitives which way the writing runs, so menus and sliders mirror with the rest of the page.",
|
|
8
|
+
category: "foundation",
|
|
9
|
+
status: "stable",
|
|
10
|
+
dependencies: ["radix-ui"],
|
|
11
|
+
registryDependencies: [],
|
|
12
|
+
files: ["direction.tsx"],
|
|
13
|
+
a11y:
|
|
14
|
+
"Needed only for right-to-left languages, and needed in addition to `dir` on the document " +
|
|
15
|
+
"rather than instead of it. The styling here is written with logical properties and follows " +
|
|
16
|
+
"`dir` by itself; the primitives underneath read direction from React context and assume " +
|
|
17
|
+
"left-to-right without a provider, which mirrors a page correctly except for its menus, " +
|
|
18
|
+
"selects and sliders — worse than not mirroring at all, because it looks deliberate.",
|
|
19
|
+
});
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useEffect,
|
|
5
|
+
useId,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
type ComponentPropsWithRef,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
import { Button } from "@/components/button";
|
|
13
|
+
import { Spinner } from "@/components/spinner";
|
|
14
|
+
import { focusRing } from "@/lib/styles";
|
|
15
|
+
import { cn } from "@/lib/utils";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* "Add this record at your DNS provider."
|
|
19
|
+
*
|
|
20
|
+
* Vercel, Resend, Cloudflare, Postmark, Google Workspace and every product
|
|
21
|
+
* that verifies a domain or routes its mail draws this card by hand, and
|
|
22
|
+
* they all learn the same three things the hard way.
|
|
23
|
+
*
|
|
24
|
+
* The parts are copied separately. A provider's form has a Name field, a
|
|
25
|
+
* Type field and a Value field, so one button that copies the whole line
|
|
26
|
+
* copies something nobody can paste anywhere.
|
|
27
|
+
*
|
|
28
|
+
* The Name field is a trap. Some providers want the host relative to the
|
|
29
|
+
* zone, `_dmarc`; some want the full name, `_dmarc.acme.com`; and some take
|
|
30
|
+
* the relative form and append the zone themselves, so a full name pasted
|
|
31
|
+
* in becomes `_dmarc.acme.com.acme.com` and the check fails for a reason
|
|
32
|
+
* nobody can see. So the host is shown both ways, with the sentence that
|
|
33
|
+
* explains which to use.
|
|
34
|
+
*
|
|
35
|
+
* A failed check says what was found. "Not verified" sends people back to
|
|
36
|
+
* their provider to stare at a record that is correct and has not
|
|
37
|
+
* propagated yet; "found v=spf1 -all, expected v=spf1 include:…" sends them
|
|
38
|
+
* to the typo. And nothing found at all is said as nothing found, with how
|
|
39
|
+
* long that can take, because it is not the same as wrong.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export type DnsRecordStatus = "pending" | "checking" | "verified" | "failed";
|
|
43
|
+
|
|
44
|
+
export interface DnsHostForms {
|
|
45
|
+
/** Relative to the zone: `_dmarc`, or `@` for the zone itself. */
|
|
46
|
+
relative: string;
|
|
47
|
+
/** Fully qualified: `_dmarc.acme.com`. */
|
|
48
|
+
full: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The host both ways a provider might want it. Accepts either form and a
|
|
53
|
+
* trailing dot, so the record can be declared however the backend has it.
|
|
54
|
+
*/
|
|
55
|
+
export function dnsHostForms(name: string, zone: string): DnsHostForms {
|
|
56
|
+
const cleanZone = zone.replace(/\.$/, "");
|
|
57
|
+
const cleanName = name.replace(/\.$/, "");
|
|
58
|
+
if (cleanName === "@" || cleanName === "" || cleanName === cleanZone) {
|
|
59
|
+
return { relative: "@", full: cleanZone };
|
|
60
|
+
}
|
|
61
|
+
if (cleanName.endsWith(`.${cleanZone}`)) {
|
|
62
|
+
return { relative: cleanName.slice(0, -cleanZone.length - 1), full: cleanName };
|
|
63
|
+
}
|
|
64
|
+
return { relative: cleanName, full: `${cleanName}.${cleanZone}` };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface DnsRecordProps extends Omit<ComponentPropsWithRef<"section">, "children"> {
|
|
68
|
+
type: string;
|
|
69
|
+
/** The host, relative or fully qualified. */
|
|
70
|
+
name: string;
|
|
71
|
+
value: string;
|
|
72
|
+
/** The zone the record belongs to. Enables the host to be shown both ways. */
|
|
73
|
+
zone?: string;
|
|
74
|
+
ttl?: number | string;
|
|
75
|
+
priority?: number;
|
|
76
|
+
/** What the record is for, in the reader's language: "Proves you own acme.com". */
|
|
77
|
+
purpose?: string;
|
|
78
|
+
status?: DnsRecordStatus;
|
|
79
|
+
/** What the check found instead, when it failed. The useful part. */
|
|
80
|
+
found?: string[];
|
|
81
|
+
checkedAt?: Date;
|
|
82
|
+
locale?: string;
|
|
83
|
+
onCheck?: () => void;
|
|
84
|
+
children?: ReactNode;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type CopyState = "idle" | "copied" | "error";
|
|
88
|
+
|
|
89
|
+
export function DnsRecord({
|
|
90
|
+
className,
|
|
91
|
+
type,
|
|
92
|
+
name,
|
|
93
|
+
value,
|
|
94
|
+
zone,
|
|
95
|
+
ttl,
|
|
96
|
+
priority,
|
|
97
|
+
purpose,
|
|
98
|
+
status = "pending",
|
|
99
|
+
found,
|
|
100
|
+
checkedAt,
|
|
101
|
+
locale,
|
|
102
|
+
onCheck,
|
|
103
|
+
children,
|
|
104
|
+
...props
|
|
105
|
+
}: DnsRecordProps) {
|
|
106
|
+
const id = useId();
|
|
107
|
+
const forms = zone ? dnsHostForms(name, zone) : null;
|
|
108
|
+
const host = forms ? forms.relative : name;
|
|
109
|
+
|
|
110
|
+
// Which part was copied, and whether it worked, so the button that was
|
|
111
|
+
// pressed is the one that says so.
|
|
112
|
+
const [copy, setCopy] = useState<{ part: string; state: CopyState } | null>(null);
|
|
113
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
114
|
+
useEffect(
|
|
115
|
+
() => () => {
|
|
116
|
+
if (timer.current) clearTimeout(timer.current);
|
|
117
|
+
},
|
|
118
|
+
[],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const copyPart = (part: string, text: string) => {
|
|
122
|
+
const settle = (state: CopyState) => {
|
|
123
|
+
setCopy({ part, state });
|
|
124
|
+
if (timer.current) clearTimeout(timer.current);
|
|
125
|
+
timer.current = setTimeout(() => {
|
|
126
|
+
setCopy(null);
|
|
127
|
+
}, 2000);
|
|
128
|
+
};
|
|
129
|
+
Promise.resolve()
|
|
130
|
+
.then(() => navigator.clipboard.writeText(text))
|
|
131
|
+
.then(
|
|
132
|
+
() => {
|
|
133
|
+
settle("copied");
|
|
134
|
+
},
|
|
135
|
+
() => {
|
|
136
|
+
settle("error");
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const statusText =
|
|
142
|
+
status === "verified"
|
|
143
|
+
? "Verified"
|
|
144
|
+
: status === "checking"
|
|
145
|
+
? "Checking…"
|
|
146
|
+
: status === "failed"
|
|
147
|
+
? found && found.length > 0
|
|
148
|
+
? "Not verified. Something else was found."
|
|
149
|
+
: "Not verified. Nothing found yet — DNS changes can take up to 48 hours to appear."
|
|
150
|
+
: "Not checked yet";
|
|
151
|
+
|
|
152
|
+
const checked =
|
|
153
|
+
checkedAt !== undefined
|
|
154
|
+
? new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(
|
|
155
|
+
checkedAt,
|
|
156
|
+
)
|
|
157
|
+
: null;
|
|
158
|
+
|
|
159
|
+
const parts: { key: string; label: string; text: string; note?: ReactNode }[] = [
|
|
160
|
+
{ key: "type", label: "Type", text: type },
|
|
161
|
+
{
|
|
162
|
+
key: "name",
|
|
163
|
+
label: "Name",
|
|
164
|
+
text: host,
|
|
165
|
+
note: forms ? (
|
|
166
|
+
forms.relative === "@" ? (
|
|
167
|
+
<>
|
|
168
|
+
Some providers want <code className="font-mono">{forms.full}</code> or leave it
|
|
169
|
+
blank instead of <code className="font-mono">@</code>.
|
|
170
|
+
</>
|
|
171
|
+
) : (
|
|
172
|
+
<>
|
|
173
|
+
Some providers want the full name, <code className="font-mono">{forms.full}</code>.
|
|
174
|
+
If the field already ends in <code className="font-mono">{zone}</code>, enter just
|
|
175
|
+
the part shown.
|
|
176
|
+
</>
|
|
177
|
+
)
|
|
178
|
+
) : undefined,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
key: "value",
|
|
182
|
+
label: "Value",
|
|
183
|
+
text: value,
|
|
184
|
+
note:
|
|
185
|
+
type.toUpperCase() === "TXT"
|
|
186
|
+
? "Enter without quotes; your provider adds them if it needs to."
|
|
187
|
+
: undefined,
|
|
188
|
+
},
|
|
189
|
+
...(priority !== undefined
|
|
190
|
+
? [{ key: "priority", label: "Priority", text: String(priority) }]
|
|
191
|
+
: []),
|
|
192
|
+
...(ttl !== undefined ? [{ key: "ttl", label: "TTL", text: String(ttl) }] : []),
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<section
|
|
197
|
+
data-slot="dns-record"
|
|
198
|
+
data-status={status}
|
|
199
|
+
aria-labelledby={`${id}-heading`}
|
|
200
|
+
aria-busy={status === "checking" || undefined}
|
|
201
|
+
className={cn(
|
|
202
|
+
"flex flex-col gap-3 rounded-lg border border-border bg-card p-4",
|
|
203
|
+
className,
|
|
204
|
+
)}
|
|
205
|
+
{...props}
|
|
206
|
+
>
|
|
207
|
+
<div className="flex flex-wrap items-baseline justify-between gap-2">
|
|
208
|
+
<h3 id={`${id}-heading`} className="text-sm font-medium">
|
|
209
|
+
{type} record
|
|
210
|
+
{purpose ? (
|
|
211
|
+
<span className="font-normal text-muted-foreground"> · {purpose}</span>
|
|
212
|
+
) : null}
|
|
213
|
+
</h3>
|
|
214
|
+
{/* Status as a word, and announced when it changes: a check was asked
|
|
215
|
+
for, and the answer should arrive where the asker is. */}
|
|
216
|
+
<p
|
|
217
|
+
data-slot="dns-record-status"
|
|
218
|
+
role="status"
|
|
219
|
+
aria-live="polite"
|
|
220
|
+
className={cn(
|
|
221
|
+
"flex items-center gap-1.5 text-xs",
|
|
222
|
+
status === "verified" && "text-success",
|
|
223
|
+
status === "failed" && "text-destructive",
|
|
224
|
+
(status === "pending" || status === "checking") && "text-muted-foreground",
|
|
225
|
+
)}
|
|
226
|
+
>
|
|
227
|
+
{status === "checking" ? <Spinner size="sm" /> : null}
|
|
228
|
+
{statusText}
|
|
229
|
+
{checked && status !== "checking" ? (
|
|
230
|
+
<span className="text-muted-foreground"> · checked {checked}</span>
|
|
231
|
+
) : null}
|
|
232
|
+
</p>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{children}
|
|
236
|
+
|
|
237
|
+
<dl className="m-0 grid gap-x-4 gap-y-2 sm:grid-cols-[auto_1fr]">
|
|
238
|
+
{parts.map((part) => (
|
|
239
|
+
<div key={part.key} className="contents">
|
|
240
|
+
<dt className="text-xs font-medium text-muted-foreground sm:pt-1.5">
|
|
241
|
+
{part.label}
|
|
242
|
+
</dt>
|
|
243
|
+
<dd className="m-0 flex flex-col gap-1">
|
|
244
|
+
<div className="flex items-start gap-2">
|
|
245
|
+
<code
|
|
246
|
+
data-slot="dns-record-part"
|
|
247
|
+
data-part={part.key}
|
|
248
|
+
className="min-w-0 flex-1 rounded-md bg-muted px-2 py-1 font-mono text-xs break-all"
|
|
249
|
+
>
|
|
250
|
+
{part.text}
|
|
251
|
+
</code>
|
|
252
|
+
<button
|
|
253
|
+
type="button"
|
|
254
|
+
aria-label={`Copy ${part.label.toLowerCase()}`}
|
|
255
|
+
data-state={copy?.part === part.key ? copy.state : "idle"}
|
|
256
|
+
className={cn(
|
|
257
|
+
"shrink-0 rounded-md border border-input bg-background px-2 py-1 text-xs font-medium",
|
|
258
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
259
|
+
focusRing,
|
|
260
|
+
)}
|
|
261
|
+
onClick={() => {
|
|
262
|
+
copyPart(part.key, part.text);
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
{copy?.part === part.key && copy.state === "copied" ? "Copied" : "Copy"}
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
{copy?.part === part.key && copy.state === "error" ? (
|
|
269
|
+
<p data-slot="dns-record-copy-error" className="text-xs text-destructive">
|
|
270
|
+
Could not copy. Select it and copy by hand.
|
|
271
|
+
</p>
|
|
272
|
+
) : null}
|
|
273
|
+
{part.note ? <p className="text-xs text-muted-foreground">{part.note}</p> : null}
|
|
274
|
+
</dd>
|
|
275
|
+
</div>
|
|
276
|
+
))}
|
|
277
|
+
</dl>
|
|
278
|
+
|
|
279
|
+
{status === "failed" && found && found.length > 0 ? (
|
|
280
|
+
<div
|
|
281
|
+
data-slot="dns-record-found"
|
|
282
|
+
className="rounded-md border border-destructive/40 bg-destructive/5 p-3 text-xs"
|
|
283
|
+
>
|
|
284
|
+
<p className="font-medium text-destructive">Found instead:</p>
|
|
285
|
+
<ul className="m-0 mt-1 list-none p-0">
|
|
286
|
+
{found.map((entry) => (
|
|
287
|
+
<li key={entry}>
|
|
288
|
+
<code className="font-mono break-all">{entry}</code>
|
|
289
|
+
</li>
|
|
290
|
+
))}
|
|
291
|
+
</ul>
|
|
292
|
+
</div>
|
|
293
|
+
) : null}
|
|
294
|
+
|
|
295
|
+
{/* A separate region from the verification status, so "Copied name"
|
|
296
|
+
never replaces or interrupts a check result that is being read.
|
|
297
|
+
Present from the start, so the first copy is heard. */}
|
|
298
|
+
<span role="status" aria-live="polite" className="sr-only">
|
|
299
|
+
{copy?.state === "copied"
|
|
300
|
+
? `Copied ${copy.part}`
|
|
301
|
+
: copy?.state === "error"
|
|
302
|
+
? `Could not copy ${copy.part}. Select it and copy by hand.`
|
|
303
|
+
: ""}
|
|
304
|
+
</span>
|
|
305
|
+
|
|
306
|
+
{onCheck ? (
|
|
307
|
+
<div className="flex items-center gap-2">
|
|
308
|
+
<Button variant="outline" size="sm" loading={status === "checking"} onClick={onCheck}>
|
|
309
|
+
{status === "verified" ? "Check again" : "Check now"}
|
|
310
|
+
</Button>
|
|
311
|
+
</div>
|
|
312
|
+
) : null}
|
|
313
|
+
</section>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "dns-record",
|
|
5
|
+
title: "DNS Record",
|
|
6
|
+
description:
|
|
7
|
+
"Add this record at your provider: parts copied separately, checked with what was found.",
|
|
8
|
+
category: "data",
|
|
9
|
+
status: "stable",
|
|
10
|
+
dependencies: [],
|
|
11
|
+
registryDependencies: ["button", "spinner"],
|
|
12
|
+
files: ["dns-record.tsx"],
|
|
13
|
+
a11y:
|
|
14
|
+
"The card is a region named by the record's type and purpose. Each part is a definition " +
|
|
15
|
+
"with its own copy button, named by the part it copies, so five Copy buttons are five " +
|
|
16
|
+
"different buttons. Verification status is a word in a polite live region, so the answer to " +
|
|
17
|
+
"a check arrives where it was asked for, and the card is aria-busy while checking. Copy " +
|
|
18
|
+
"results are announced in a separate region, present from the start, so a copy never " +
|
|
19
|
+
"replaces a check result mid-sentence; failure is announced with what to do instead. What " +
|
|
20
|
+
"the check found is a list, not a colour.",
|
|
21
|
+
});
|
|
@@ -221,7 +221,7 @@ export function DrawerHeader({ className, ...props }: ComponentPropsWithRef<"div
|
|
|
221
221
|
return (
|
|
222
222
|
<div
|
|
223
223
|
data-slot="drawer-header"
|
|
224
|
-
className={cn("flex shrink-0 flex-col gap-1.5 px-6 pt-4 pb-2 text-
|
|
224
|
+
className={cn("flex shrink-0 flex-col gap-1.5 px-6 pt-4 pb-2 text-start", className)}
|
|
225
225
|
{...props}
|
|
226
226
|
/>
|
|
227
227
|
);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
4
4
|
import type { ComponentPropsWithRef } from "react";
|
|
5
5
|
|
|
6
|
+
import { mirrorForDirection } from "@/lib/styles";
|
|
6
7
|
import { cn } from "@/lib/utils";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -80,7 +81,7 @@ export function DropdownMenuItem({
|
|
|
80
81
|
data-variant={variant}
|
|
81
82
|
className={cn(
|
|
82
83
|
menuItem,
|
|
83
|
-
inset && "
|
|
84
|
+
inset && "ps-8",
|
|
84
85
|
variant === "destructive" &&
|
|
85
86
|
"text-destructive data-[highlighted]:bg-destructive/10 data-[highlighted]:text-destructive",
|
|
86
87
|
className,
|
|
@@ -94,7 +95,7 @@ export function DropdownMenuItem({
|
|
|
94
95
|
* aria-checked, so announcing it twice would be noise. */
|
|
95
96
|
function ItemIndicator({ radio = false }: { radio?: boolean }) {
|
|
96
97
|
return (
|
|
97
|
-
<span className="pointer-events-none absolute
|
|
98
|
+
<span className="pointer-events-none absolute start-2 grid size-4 place-items-center">
|
|
98
99
|
<DropdownMenuPrimitive.ItemIndicator>
|
|
99
100
|
{radio ? (
|
|
100
101
|
<span className="size-2 rounded-full bg-current" />
|
|
@@ -122,7 +123,7 @@ export function DropdownMenuCheckboxItem({
|
|
|
122
123
|
return (
|
|
123
124
|
<DropdownMenuPrimitive.CheckboxItem
|
|
124
125
|
data-slot="dropdown-menu-checkbox-item"
|
|
125
|
-
className={cn(menuItem, "
|
|
126
|
+
className={cn(menuItem, "ps-8", className)}
|
|
126
127
|
{...props}
|
|
127
128
|
>
|
|
128
129
|
<ItemIndicator />
|
|
@@ -139,7 +140,7 @@ export function DropdownMenuRadioItem({
|
|
|
139
140
|
return (
|
|
140
141
|
<DropdownMenuPrimitive.RadioItem
|
|
141
142
|
data-slot="dropdown-menu-radio-item"
|
|
142
|
-
className={cn(menuItem, "
|
|
143
|
+
className={cn(menuItem, "ps-8", className)}
|
|
143
144
|
{...props}
|
|
144
145
|
>
|
|
145
146
|
<ItemIndicator radio />
|
|
@@ -160,7 +161,7 @@ export function DropdownMenuLabel({ className, inset, ...props }: DropdownMenuLa
|
|
|
160
161
|
data-slot="dropdown-menu-label"
|
|
161
162
|
className={cn(
|
|
162
163
|
"px-2 py-1.5 text-xs font-medium text-muted-foreground",
|
|
163
|
-
inset && "
|
|
164
|
+
inset && "ps-8",
|
|
164
165
|
className,
|
|
165
166
|
)}
|
|
166
167
|
{...props}
|
|
@@ -188,7 +189,7 @@ export function DropdownMenuShortcut({ className, ...props }: ComponentPropsWith
|
|
|
188
189
|
<span
|
|
189
190
|
data-slot="dropdown-menu-shortcut"
|
|
190
191
|
aria-hidden="true"
|
|
191
|
-
className={cn("
|
|
192
|
+
className={cn("ms-auto text-2xs tracking-wide text-muted-foreground", className)}
|
|
192
193
|
{...props}
|
|
193
194
|
/>
|
|
194
195
|
);
|
|
@@ -209,11 +210,16 @@ export function DropdownMenuSubTrigger({
|
|
|
209
210
|
return (
|
|
210
211
|
<DropdownMenuPrimitive.SubTrigger
|
|
211
212
|
data-slot="dropdown-menu-sub-trigger"
|
|
212
|
-
className={cn(menuItem, "data-[state=open]:bg-accent", inset && "
|
|
213
|
+
className={cn(menuItem, "data-[state=open]:bg-accent", inset && "ps-8", className)}
|
|
213
214
|
{...props}
|
|
214
215
|
>
|
|
215
216
|
{children}
|
|
216
|
-
<svg
|
|
217
|
+
<svg
|
|
218
|
+
viewBox="0 0 24 24"
|
|
219
|
+
fill="none"
|
|
220
|
+
aria-hidden="true"
|
|
221
|
+
className={cn("ms-auto size-3.5", mirrorForDirection)}
|
|
222
|
+
>
|
|
217
223
|
<path
|
|
218
224
|
d="m9 18 6-6-6-6"
|
|
219
225
|
stroke="currentColor"
|
|
@@ -9,7 +9,7 @@ const inputVariants = cva(
|
|
|
9
9
|
"flex w-full min-w-0 rounded-md border border-input bg-background text-foreground shadow-xs",
|
|
10
10
|
"transition-[border-color,box-shadow] duration-[var(--duration-fast)] ease-[var(--ease-out-quint)]",
|
|
11
11
|
"placeholder:text-muted-foreground",
|
|
12
|
-
"file:
|
|
12
|
+
"file:me-3 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground",
|
|
13
13
|
"focus-visible:border-ring",
|
|
14
14
|
"disabled:cursor-not-allowed disabled:opacity-55",
|
|
15
15
|
focusRingInset,
|
|
@@ -203,7 +203,7 @@ export function LogViewer({
|
|
|
203
203
|
data-index={item.index}
|
|
204
204
|
data-slot="log-viewer-row"
|
|
205
205
|
data-level={line.level}
|
|
206
|
-
className="absolute
|
|
206
|
+
className="absolute start-0 top-0 w-full"
|
|
207
207
|
style={{ transform: `translateY(${String(item.start)}px)` }}
|
|
208
208
|
>
|
|
209
209
|
<LogViewerRow
|
|
@@ -296,7 +296,7 @@ export function LogViewerRow({ line, expanded = false, onToggle }: LogViewerRowP
|
|
|
296
296
|
{expanded && line.fields ? (
|
|
297
297
|
<dl
|
|
298
298
|
data-slot="log-viewer-fields"
|
|
299
|
-
className="mt-1 mb-1
|
|
299
|
+
className="ms-4 mt-1 mb-1 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 border-s border-border ps-3"
|
|
300
300
|
>
|
|
301
301
|
{Object.entries(line.fields).map(([key, value]) => (
|
|
302
302
|
<div key={key} className="contents">
|
|
@@ -141,7 +141,7 @@ export function Meter({
|
|
|
141
141
|
data-slot="meter-segment"
|
|
142
142
|
data-tone={segment.tone ?? "primary"}
|
|
143
143
|
className={cn(
|
|
144
|
-
"h-full first:rounded-
|
|
144
|
+
"h-full first:rounded-s-full last:rounded-e-full",
|
|
145
145
|
"transition-[width] duration-[var(--duration-slow)] ease-[var(--ease-out-quint)]",
|
|
146
146
|
TONES[segment.tone ?? "primary"],
|
|
147
147
|
)}
|
|
@@ -2,6 +2,7 @@ import { Slot } from "radix-ui";
|
|
|
2
2
|
import type { ComponentPropsWithRef } from "react";
|
|
3
3
|
|
|
4
4
|
import { buttonVariants } from "@/components/button";
|
|
5
|
+
import { mirrorForDirection } from "@/lib/styles";
|
|
5
6
|
import { cn } from "@/lib/utils";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -80,7 +81,12 @@ export function PaginationPrevious({ className, ...props }: PaginationLinkProps)
|
|
|
80
81
|
className={cn("gap-1 px-2.5", className)}
|
|
81
82
|
{...props}
|
|
82
83
|
>
|
|
83
|
-
<svg
|
|
84
|
+
<svg
|
|
85
|
+
viewBox="0 0 24 24"
|
|
86
|
+
fill="none"
|
|
87
|
+
aria-hidden="true"
|
|
88
|
+
className={cn("size-4", mirrorForDirection)}
|
|
89
|
+
>
|
|
84
90
|
<path
|
|
85
91
|
d="m15 18-6-6 6-6"
|
|
86
92
|
stroke="currentColor"
|
|
@@ -103,7 +109,12 @@ export function PaginationNext({ className, ...props }: PaginationLinkProps) {
|
|
|
103
109
|
{...props}
|
|
104
110
|
>
|
|
105
111
|
<span className="hidden sm:inline">Next</span>
|
|
106
|
-
<svg
|
|
112
|
+
<svg
|
|
113
|
+
viewBox="0 0 24 24"
|
|
114
|
+
fill="none"
|
|
115
|
+
aria-hidden="true"
|
|
116
|
+
className={cn("size-4", mirrorForDirection)}
|
|
117
|
+
>
|
|
107
118
|
<path
|
|
108
119
|
d="m9 18 6-6-6-6"
|
|
109
120
|
stroke="currentColor"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { PermissionMatrix, type PermissionMatrixProps } from "./permission-matrix";
|
|
2
|
+
export {
|
|
3
|
+
cellState,
|
|
4
|
+
countGranted,
|
|
5
|
+
groupPermissions,
|
|
6
|
+
groupState,
|
|
7
|
+
inheritedFrom,
|
|
8
|
+
type CellState,
|
|
9
|
+
type Grants,
|
|
10
|
+
type GroupState,
|
|
11
|
+
type Permission,
|
|
12
|
+
type PermissionGroup,
|
|
13
|
+
type Role,
|
|
14
|
+
} from "./permission-model";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "permission-matrix",
|
|
5
|
+
title: "Permission Matrix",
|
|
6
|
+
description:
|
|
7
|
+
"Roles across, permissions down, with inheritance, locked roles and one tab stop.",
|
|
8
|
+
category: "data",
|
|
9
|
+
status: "stable",
|
|
10
|
+
dependencies: [],
|
|
11
|
+
registryDependencies: ["checkbox"],
|
|
12
|
+
files: ["permission-model.ts", "permission-matrix.tsx"],
|
|
13
|
+
a11y:
|
|
14
|
+
'A grid in the WAI-ARIA sense: role="grid" with one tab stop, arrow keys between cells, Home ' +
|
|
15
|
+
"and End along a row and Ctrl with them across the whole matrix — the right call for a " +
|
|
16
|
+
"matrix that is operated, where it was the wrong one for a diff that is read. Every checkbox " +
|
|
17
|
+
'is named by both coordinates, "Delete projects for Editor", so a reader arriving by arrow ' +
|
|
18
|
+
"key knows where they are without re-reading the headers. An inherited grant is a checked " +
|
|
19
|
+
"box that cannot be changed here, marked aria-disabled rather than disabled so it stays in " +
|
|
20
|
+
"the keyboard path, with the role it came from as its description. A locked role says in " +
|
|
21
|
+
"each cell's description that nothing can be changed. Group toggles are tri-state, and the " +
|
|
22
|
+
"column header counts each role's grants in text.",
|
|
23
|
+
});
|