@dowel-ui/react 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/dist/blocks/admin-dashboard/meta.js +31 -0
- package/dist/blocks/admin-dashboard/meta.js.map +1 -0
- package/dist/blocks/agent-console/meta.js +27 -0
- package/dist/blocks/agent-console/meta.js.map +1 -0
- package/dist/blocks/ai-dashboard/meta.js +25 -0
- package/dist/blocks/ai-dashboard/meta.js.map +1 -0
- package/dist/blocks/ai-workspace/meta.js +33 -0
- package/dist/blocks/ai-workspace/meta.js.map +1 -0
- package/dist/blocks/analytics/meta.js +24 -0
- package/dist/blocks/analytics/meta.js.map +1 -0
- package/dist/blocks/billing/meta.js +25 -0
- package/dist/blocks/billing/meta.js.map +1 -0
- package/dist/blocks/command-center/meta.js +28 -0
- package/dist/blocks/command-center/meta.js.map +1 -0
- package/dist/blocks/crm/meta.js +32 -0
- package/dist/blocks/crm/meta.js.map +1 -0
- package/dist/blocks/onboarding/meta.js +23 -0
- package/dist/blocks/onboarding/meta.js.map +1 -0
- package/dist/components/accordion/accordion.js +1 -1
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/activity-feed/activity-feed.js +1 -1
- package/dist/components/activity-feed/activity-feed.js.map +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -1
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts +52 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js +249 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts +94 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.js +91 -0
- package/dist/components/ai-extraction-review/extraction-model.js.map +1 -0
- package/dist/components/ai-extraction-review/index.d.ts +3 -0
- package/dist/components/ai-extraction-review/index.js +3 -0
- package/dist/components/ai-extraction-review/meta.js +17 -0
- package/dist/components/ai-extraction-review/meta.js.map +1 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.js +1 -1
- package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -1
- package/dist/components/ai-response/ai-response.js +2 -2
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts +72 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts.map +1 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js +126 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js.map +1 -0
- package/dist/components/ai-suggested-value/index.d.ts +2 -0
- package/dist/components/ai-suggested-value/index.js +2 -0
- package/dist/components/ai-suggested-value/meta.js +17 -0
- package/dist/components/ai-suggested-value/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.js +2 -2
- package/dist/components/ai-tool/ai-tool.js.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.d.ts +52 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb/breadcrumb.js +141 -0
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +2 -0
- package/dist/components/breadcrumb/index.js +2 -0
- package/dist/components/breadcrumb/meta.js +17 -0
- package/dist/components/breadcrumb/meta.js.map +1 -0
- package/dist/components/calendar/calendar.js +2 -2
- package/dist/components/calendar/calendar.js.map +1 -1
- package/dist/components/code-block/code-block.js +1 -1
- package/dist/components/code-block/code-block.js.map +1 -1
- package/dist/components/collapsible/collapsible.d.ts +30 -0
- package/dist/components/collapsible/collapsible.d.ts.map +1 -0
- package/dist/components/collapsible/collapsible.js +38 -0
- package/dist/components/collapsible/collapsible.js.map +1 -0
- package/dist/components/collapsible/index.d.ts +2 -0
- package/dist/components/collapsible/index.js +2 -0
- package/dist/components/collapsible/meta.js +17 -0
- package/dist/components/collapsible/meta.js.map +1 -0
- package/dist/components/combobox/combobox.js +2 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/command/command.js +1 -1
- package/dist/components/command/command.js.map +1 -1
- package/dist/components/confirm-typed/confirm-typed.d.ts +47 -0
- package/dist/components/confirm-typed/confirm-typed.d.ts.map +1 -0
- package/dist/components/confirm-typed/confirm-typed.js +106 -0
- package/dist/components/confirm-typed/confirm-typed.js.map +1 -0
- package/dist/components/confirm-typed/index.d.ts +2 -0
- package/dist/components/confirm-typed/index.js +2 -0
- package/dist/components/confirm-typed/meta.js +17 -0
- package/dist/components/confirm-typed/meta.js.map +1 -0
- package/dist/components/cron-editor/cron-editor.d.ts +36 -0
- package/dist/components/cron-editor/cron-editor.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-editor.js +493 -0
- package/dist/components/cron-editor/cron-editor.js.map +1 -0
- package/dist/components/cron-editor/cron-expression.d.ts +82 -0
- package/dist/components/cron-editor/cron-expression.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-expression.js +352 -0
- package/dist/components/cron-editor/cron-expression.js.map +1 -0
- package/dist/components/cron-editor/index.d.ts +3 -0
- package/dist/components/cron-editor/index.js +3 -0
- package/dist/components/cron-editor/meta.js +17 -0
- package/dist/components/cron-editor/meta.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts.map +1 -1
- package/dist/components/data-table/data-table.js +4 -1
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/dialog/dialog.js +2 -2
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/diff-viewer/diff-viewer.js +4 -4
- package/dist/components/diff-viewer/diff-viewer.js.map +1 -1
- package/dist/components/direction/direction.d.ts +35 -0
- package/dist/components/direction/direction.d.ts.map +1 -0
- package/dist/components/direction/direction.js +14 -0
- package/dist/components/direction/direction.js.map +1 -0
- package/dist/components/direction/index.d.ts +2 -0
- package/dist/components/direction/index.js +2 -0
- package/dist/components/direction/meta.js +17 -0
- package/dist/components/direction/meta.js.map +1 -0
- package/dist/components/dns-record/dns-record.d.ts +61 -0
- package/dist/components/dns-record/dns-record.d.ts.map +1 -0
- package/dist/components/dns-record/dns-record.js +227 -0
- package/dist/components/dns-record/dns-record.js.map +1 -0
- package/dist/components/dns-record/index.d.ts +2 -0
- package/dist/components/dns-record/index.js +2 -0
- package/dist/components/dns-record/meta.js +17 -0
- package/dist/components/dns-record/meta.js.map +1 -0
- package/dist/components/drawer/drawer.js +1 -1
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.js +9 -8
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/input/input.js +1 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/log-viewer/log-viewer.js +2 -2
- package/dist/components/log-viewer/log-viewer.js.map +1 -1
- package/dist/components/meter/meter.js +1 -1
- package/dist/components/meter/meter.js.map +1 -1
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +3 -2
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/permission-matrix/index.d.ts +3 -0
- package/dist/components/permission-matrix/index.js +3 -0
- package/dist/components/permission-matrix/meta.js +17 -0
- package/dist/components/permission-matrix/meta.js.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts +46 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.js +199 -0
- package/dist/components/permission-matrix/permission-matrix.js.map +1 -0
- package/dist/components/permission-matrix/permission-model.d.ts +53 -0
- package/dist/components/permission-matrix/permission-model.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-model.js +64 -0
- package/dist/components/permission-matrix/permission-model.js.map +1 -0
- package/dist/components/record-diff/record-diff.js +4 -4
- package/dist/components/record-diff/record-diff.js.map +1 -1
- package/dist/components/secret-field/index.d.ts +2 -0
- package/dist/components/secret-field/index.js +2 -0
- package/dist/components/secret-field/meta.js +17 -0
- package/dist/components/secret-field/meta.js.map +1 -0
- package/dist/components/secret-field/secret-field.d.ts +58 -0
- package/dist/components/secret-field/secret-field.d.ts.map +1 -0
- package/dist/components/secret-field/secret-field.js +168 -0
- package/dist/components/secret-field/secret-field.js.map +1 -0
- package/dist/components/select/select.js +2 -2
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/session-expiry/index.d.ts +2 -0
- package/dist/components/session-expiry/index.js +2 -0
- package/dist/components/session-expiry/meta.js +17 -0
- package/dist/components/session-expiry/meta.js.map +1 -0
- package/dist/components/session-expiry/session-expiry.d.ts +64 -0
- package/dist/components/session-expiry/session-expiry.d.ts.map +1 -0
- package/dist/components/session-expiry/session-expiry.js +185 -0
- package/dist/components/session-expiry/session-expiry.js.map +1 -0
- package/dist/components/sheet/sheet.d.ts.map +1 -1
- package/dist/components/sheet/sheet.js +2 -2
- package/dist/components/sheet/sheet.js.map +1 -1
- package/dist/components/shortcut-recorder/index.d.ts +3 -0
- package/dist/components/shortcut-recorder/index.js +3 -0
- package/dist/components/shortcut-recorder/meta.js +17 -0
- package/dist/components/shortcut-recorder/meta.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts +67 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.js +168 -0
- package/dist/components/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts +47 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js +212 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/meta.js +17 -0
- package/dist/components/sidebar/meta.js.map +1 -0
- package/dist/components/sidebar/sidebar.d.ts +69 -0
- package/dist/components/sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.js +256 -0
- package/dist/components/sidebar/sidebar.js.map +1 -0
- package/dist/components/sync-status/index.d.ts +2 -0
- package/dist/components/sync-status/index.js +2 -0
- package/dist/components/sync-status/meta.js +17 -0
- package/dist/components/sync-status/meta.js.map +1 -0
- package/dist/components/sync-status/sync-status.d.ts +65 -0
- package/dist/components/sync-status/sync-status.d.ts.map +1 -0
- package/dist/components/sync-status/sync-status.js +103 -0
- package/dist/components/sync-status/sync-status.js.map +1 -0
- package/dist/components/table/table.js +2 -2
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/tags-input/tags-input.js +1 -1
- package/dist/components/tags-input/tags-input.js.map +1 -1
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +2 -0
- package/dist/components/textarea/meta.js +17 -0
- package/dist/components/textarea/meta.js.map +1 -0
- package/dist/components/textarea/textarea.d.ts +43 -0
- package/dist/components/textarea/textarea.d.ts.map +1 -0
- package/dist/components/textarea/textarea.js +119 -0
- package/dist/components/textarea/textarea.js.map +1 -0
- package/dist/components/time-range-picker/time-range-picker.js +3 -3
- package/dist/components/time-range-picker/time-range-picker.js.map +1 -1
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +2 -2
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/index.d.ts +36 -2
- package/dist/index.js +22 -3
- package/dist/lib/styles.d.ts +16 -1
- package/dist/lib/styles.d.ts.map +1 -1
- package/dist/lib/styles.js +16 -1
- package/dist/lib/styles.js.map +1 -1
- package/dist/registry/blocks.d.ts.map +1 -1
- package/dist/registry/blocks.js +27 -9
- package/dist/registry/blocks.js.map +1 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +84 -54
- package/dist/registry/components.js.map +1 -1
- package/dist/registry/schema.d.ts +8 -0
- package/dist/registry/schema.d.ts.map +1 -1
- package/dist/registry/schema.js.map +1 -1
- package/package.json +5 -4
- package/src/components/accordion/accordion.tsx +1 -1
- package/src/components/activity-feed/activity-feed.tsx +1 -1
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-extraction-review/ai-extraction-review.tsx +492 -0
- package/src/components/ai-extraction-review/extraction-model.ts +159 -0
- package/src/components/ai-extraction-review/index.ts +13 -0
- package/src/components/ai-extraction-review/meta.ts +24 -0
- package/src/components/ai-prompt-input/ai-prompt-input.tsx +1 -1
- package/src/components/ai-reasoning/ai-reasoning.tsx +1 -1
- package/src/components/ai-response/ai-response.tsx +2 -2
- package/src/components/ai-suggested-value/ai-suggested-value.tsx +295 -0
- package/src/components/ai-suggested-value/index.ts +6 -0
- package/src/components/ai-suggested-value/meta.ts +24 -0
- package/src/components/ai-tool/ai-tool.tsx +2 -2
- package/src/components/breadcrumb/breadcrumb.tsx +157 -0
- package/src/components/breadcrumb/index.ts +10 -0
- package/src/components/breadcrumb/meta.ts +19 -0
- package/src/components/calendar/calendar.tsx +2 -2
- package/src/components/code-block/code-block.tsx +1 -1
- package/src/components/collapsible/collapsible.tsx +63 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/collapsible/meta.ts +16 -0
- package/src/components/combobox/combobox.tsx +2 -2
- package/src/components/command/command.tsx +1 -1
- package/src/components/confirm-typed/confirm-typed.tsx +191 -0
- package/src/components/confirm-typed/index.ts +1 -0
- package/src/components/confirm-typed/meta.ts +22 -0
- package/src/components/cron-editor/cron-editor.tsx +676 -0
- package/src/components/cron-editor/cron-expression.ts +552 -0
- package/src/components/cron-editor/index.ts +23 -0
- package/src/components/cron-editor/meta.ts +23 -0
- package/src/components/data-table/data-table.tsx +14 -3
- package/src/components/dialog/dialog.tsx +2 -2
- package/src/components/diff-viewer/diff-viewer.tsx +4 -4
- package/src/components/direction/direction.tsx +38 -0
- package/src/components/direction/index.ts +1 -0
- package/src/components/direction/meta.ts +19 -0
- package/src/components/dns-record/dns-record.tsx +315 -0
- package/src/components/dns-record/index.ts +7 -0
- package/src/components/dns-record/meta.ts +21 -0
- package/src/components/drawer/drawer.tsx +1 -1
- package/src/components/dropdown-menu/dropdown-menu.tsx +14 -8
- package/src/components/input/input.tsx +1 -1
- package/src/components/log-viewer/log-viewer.tsx +2 -2
- package/src/components/meter/meter.tsx +1 -1
- package/src/components/pagination/pagination.tsx +13 -2
- package/src/components/permission-matrix/index.ts +14 -0
- package/src/components/permission-matrix/meta.ts +23 -0
- package/src/components/permission-matrix/permission-matrix.tsx +368 -0
- package/src/components/permission-matrix/permission-model.ts +120 -0
- package/src/components/record-diff/record-diff.tsx +4 -4
- package/src/components/secret-field/index.ts +6 -0
- package/src/components/secret-field/meta.ts +21 -0
- package/src/components/secret-field/secret-field.tsx +284 -0
- package/src/components/select/select.tsx +2 -2
- package/src/components/session-expiry/index.ts +9 -0
- package/src/components/session-expiry/meta.ts +23 -0
- package/src/components/session-expiry/session-expiry.tsx +321 -0
- package/src/components/sheet/sheet.tsx +8 -4
- package/src/components/shortcut-recorder/index.ts +19 -0
- package/src/components/shortcut-recorder/meta.ts +22 -0
- package/src/components/shortcut-recorder/shortcut-model.ts +204 -0
- package/src/components/shortcut-recorder/shortcut-recorder.tsx +336 -0
- package/src/components/sidebar/index.ts +19 -0
- package/src/components/sidebar/meta.ts +25 -0
- package/src/components/sidebar/sidebar.tsx +374 -0
- package/src/components/sync-status/index.ts +9 -0
- package/src/components/sync-status/meta.ts +21 -0
- package/src/components/sync-status/sync-status.tsx +217 -0
- package/src/components/table/table.tsx +3 -3
- package/src/components/tags-input/tags-input.tsx +1 -1
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/meta.ts +21 -0
- package/src/components/textarea/textarea.tsx +210 -0
- package/src/components/time-range-picker/time-range-picker.tsx +3 -3
- package/src/components/toast/toast.tsx +14 -6
- package/src/index.ts +15 -0
- package/src/lib/styles.ts +16 -0
- package/src/registry/blocks.ts +18 -0
- package/src/registry/components.ts +30 -0
- package/src/registry/schema.ts +8 -0
- package/src/blocks/admin-users/admin-users.tsx +0 -251
- package/src/blocks/admin-users/index.ts +0 -1
- package/src/blocks/admin-users/meta.ts +0 -27
- package/src/blocks/ai-chat/ai-chat.tsx +0 -280
- package/src/blocks/ai-chat/index.ts +0 -7
- package/src/blocks/ai-chat/meta.ts +0 -32
- package/src/blocks/dashboard/dashboard.tsx +0 -192
- package/src/blocks/dashboard/index.ts +0 -6
- package/src/blocks/dashboard/meta.ts +0 -18
- package/src/blocks/forgot-password/forgot-password.tsx +0 -158
- package/src/blocks/forgot-password/index.ts +0 -1
- package/src/blocks/forgot-password/meta.ts +0 -18
- package/src/blocks/login/index.ts +0 -1
- package/src/blocks/login/login.tsx +0 -178
- package/src/blocks/login/meta.ts +0 -18
- package/src/blocks/pricing/index.ts +0 -1
- package/src/blocks/pricing/meta.ts +0 -18
- package/src/blocks/pricing/pricing.tsx +0 -176
- package/src/blocks/settings/index.ts +0 -6
- package/src/blocks/settings/meta.ts +0 -29
- package/src/blocks/settings/settings.tsx +0 -317
- package/src/blocks/signup/index.ts +0 -1
- package/src/blocks/signup/meta.ts +0 -18
- package/src/blocks/signup/signup.tsx +0 -240
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","names":["Dialog","DialogPrimitive"],"sources":["../../../src/components/dialog/dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A modal window that interrupts the user to gather a response.\n *\n * Focus is trapped while open, restored to the trigger on close, and the rest\n * of the page is hidden from assistive technology — all handled by the\n * underlying primitive rather than reimplemented here.\n */\nexport const Dialog = DialogPrimitive.Root;\nexport const DialogTrigger = DialogPrimitive.Trigger;\nexport const DialogPortal = DialogPrimitive.Portal;\nexport const DialogClose = DialogPrimitive.Close;\n\nexport type DialogOverlayProps = ComponentPropsWithRef<typeof DialogPrimitive.Overlay>;\n\nexport function DialogOverlay({ className, ...props }: DialogOverlayProps) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n \"fixed inset-0 z-[var(--z-overlay)] bg-overlay backdrop-blur-[2px]\",\n \"data-[state=closed]:animate-overlay-out data-[state=open]:animate-overlay-in\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface DialogContentProps extends ComponentPropsWithRef<\n typeof DialogPrimitive.Content\n> {\n /** Renders the built-in close button. Turn off to supply your own. */\n showCloseButton?: boolean;\n}\n\nexport function DialogContent({\n className,\n children,\n showCloseButton = true,\n ...props\n}: DialogContentProps) {\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot=\"dialog-content\"\n className={cn(\n \"fixed top-1/2 left-1/2 z-[var(--z-modal)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2\",\n \"grid gap-4 rounded-xl border border-border bg-card p-6 text-card-foreground shadow-xl\",\n \"data-[state=closed]:animate-dialog-out data-[state=open]:animate-dialog-in\",\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton ? (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n aria-label=\"Close\"\n className={cn(\n \"absolute
|
|
1
|
+
{"version":3,"file":"dialog.js","names":["Dialog","DialogPrimitive"],"sources":["../../../src/components/dialog/dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A modal window that interrupts the user to gather a response.\n *\n * Focus is trapped while open, restored to the trigger on close, and the rest\n * of the page is hidden from assistive technology — all handled by the\n * underlying primitive rather than reimplemented here.\n */\nexport const Dialog = DialogPrimitive.Root;\nexport const DialogTrigger = DialogPrimitive.Trigger;\nexport const DialogPortal = DialogPrimitive.Portal;\nexport const DialogClose = DialogPrimitive.Close;\n\nexport type DialogOverlayProps = ComponentPropsWithRef<typeof DialogPrimitive.Overlay>;\n\nexport function DialogOverlay({ className, ...props }: DialogOverlayProps) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n \"fixed inset-0 z-[var(--z-overlay)] bg-overlay backdrop-blur-[2px]\",\n \"data-[state=closed]:animate-overlay-out data-[state=open]:animate-overlay-in\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface DialogContentProps extends ComponentPropsWithRef<\n typeof DialogPrimitive.Content\n> {\n /** Renders the built-in close button. Turn off to supply your own. */\n showCloseButton?: boolean;\n}\n\nexport function DialogContent({\n className,\n children,\n showCloseButton = true,\n ...props\n}: DialogContentProps) {\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot=\"dialog-content\"\n className={cn(\n \"fixed top-1/2 left-1/2 z-[var(--z-modal)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2\",\n \"grid gap-4 rounded-xl border border-border bg-card p-6 text-card-foreground shadow-xl\",\n \"data-[state=closed]:animate-dialog-out data-[state=open]:animate-dialog-in\",\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton ? (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n aria-label=\"Close\"\n className={cn(\n \"absolute end-4 top-4 grid size-7 place-items-center rounded-md text-muted-foreground\",\n \"transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground\",\n \"[&_svg]:size-4\",\n focusRing,\n )}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"m6 6 12 12M18 6 6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </DialogPrimitive.Close>\n ) : null}\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nexport function DialogHeader({ className, ...props }: ComponentPropsWithRef<\"div\">) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn(\"flex flex-col gap-1.5 pe-8 text-start\", className)}\n {...props}\n />\n );\n}\n\nexport function DialogFooter({ className, ...props }: ComponentPropsWithRef<\"div\">) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n {...props}\n />\n );\n}\n\nexport type DialogTitleProps = ComponentPropsWithRef<typeof DialogPrimitive.Title>;\n\nexport function DialogTitle({ className, ...props }: DialogTitleProps) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn(\"text-lg leading-tight font-semibold tracking-tight\", className)}\n {...props}\n />\n );\n}\n\nexport type DialogDescriptionProps = ComponentPropsWithRef<typeof DialogPrimitive.Description>;\n\nexport function DialogDescription({ className, ...props }: DialogDescriptionProps) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAaA,WAASC,OAAgB;AACtC,MAAa,gBAAgBA,OAAgB;AAC7C,MAAa,eAAeA,OAAgB;AAC5C,MAAa,cAAcA,OAAgB;AAI3C,SAAgB,cAAc,EAAE,WAAW,GAAG,SAA6B;CACzE,OACE,oBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,WAAW,GACT,qEACA,gFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AASA,SAAgB,cAAc,EAC5B,WACA,UACA,kBAAkB,MAClB,GAAG,SACkB;CACrB,OACE,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAChB,qBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,WAAW,GACT,4GACA,yFACA,8EACA,SACF;EACA,GAAI;EARN,UAAA,CAUG,UACA,kBACC,oBAACA,OAAgB,OAAjB;GACE,aAAU;GACV,cAAW;GACX,WAAW,GACT,wFACA,2FACA,kBACA,SACF;GAEA,UAAA,oBAAC,OAAD;IAAK,SAAQ;IAAY,MAAK;IAAO,eAAY;IAC/C,UAAA,oBAAC,QAAD;KACE,GAAE;KACF,QAAO;KACP,aAAY;KACZ,eAAc;IACf,CAAA;GACE,CAAA;EACgB,CAAA,IACrB,IACmB;CACb,CAAA,CAAA,EAAA,CAAA;AAElB;AAEA,SAAgB,aAAa,EAAE,WAAW,GAAG,SAAuC;CAClF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,yCAAyC,SAAS;EAChE,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,aAAa,EAAE,WAAW,GAAG,SAAuC;CAClF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,0DAA0D,SAAS;EACjF,GAAI;CACL,CAAA;AAEL;AAIA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAA2B;CACrE,OACE,oBAACA,OAAgB,OAAjB;EACE,aAAU;EACV,WAAW,GAAG,sDAAsD,SAAS;EAC7E,GAAI;CACL,CAAA;AAEL;AAIA,SAAgB,kBAAkB,EAAE,WAAW,GAAG,SAAiC;CACjF,OACE,oBAACA,OAAgB,aAAjB;EACE,aAAU;EACV,WAAW,GAAG,iCAAiC,SAAS;EACxD,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -135,12 +135,12 @@ function UnifiedRow({ row }) {
|
|
|
135
135
|
/* @__PURE__ */ jsx(LineNumber, { value: row.before }),
|
|
136
136
|
/* @__PURE__ */ jsx(LineNumber, { value: row.after }),
|
|
137
137
|
/* @__PURE__ */ jsx("td", {
|
|
138
|
-
className: "w-4
|
|
138
|
+
className: "w-4 pe-1 text-center text-muted-foreground select-none",
|
|
139
139
|
"aria-hidden": "true",
|
|
140
140
|
children: row.kind === "added" ? "+" : row.kind === "removed" ? "−" : ""
|
|
141
141
|
}),
|
|
142
142
|
/* @__PURE__ */ jsxs("td", {
|
|
143
|
-
className: "w-full py-0.5
|
|
143
|
+
className: "w-full py-0.5 pe-3 break-all whitespace-pre-wrap",
|
|
144
144
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
145
145
|
className: "sr-only",
|
|
146
146
|
children: [KIND_LABEL[row.kind], ": "]
|
|
@@ -158,7 +158,7 @@ function SplitCell({ row, side }) {
|
|
|
158
158
|
className: "w-1/2 bg-muted/20"
|
|
159
159
|
})] });
|
|
160
160
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LineNumber, { value: side === "before" ? row.before : row.after }), /* @__PURE__ */ jsxs("td", {
|
|
161
|
-
className: cn("w-1/2 py-0.5
|
|
161
|
+
className: cn("w-1/2 py-0.5 pe-3 break-all whitespace-pre-wrap", ROW_STYLES[row.kind]),
|
|
162
162
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
163
163
|
className: "sr-only",
|
|
164
164
|
children: [KIND_LABEL[row.kind], ": "]
|
|
@@ -175,7 +175,7 @@ function RowContent({ row }) {
|
|
|
175
175
|
function LineNumber({ value }) {
|
|
176
176
|
return /* @__PURE__ */ jsx("td", {
|
|
177
177
|
"aria-hidden": "true",
|
|
178
|
-
className: "w-10 border-
|
|
178
|
+
className: "w-10 border-e border-border bg-muted/40 px-2 py-0.5 text-end text-muted-foreground tabular-nums select-none",
|
|
179
179
|
children: value ?? ""
|
|
180
180
|
});
|
|
181
181
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-viewer.js","names":[],"sources":["../../../src/components/diff-viewer/diff-viewer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type ComponentPropsWithRef, type ReactNode } from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport { countChanges, toSplitRows, type DiffHunk, type DiffRow } from \"./diff-model\";\n\n/**\n * A diff, and a decision about it.\n *\n * The rendering half exists because every packaged viewer brings its own\n * styling system — emotion, or HTML strings plus a stylesheet — which design\n * tokens cannot reach and which is awkward under RSC. The deciding half exists\n * because Dowel is for AI products, and an agent proposing a change to a file is\n * the case that needs it: what every coding agent ships, and no component\n * library does.\n *\n * Decisions are controlled. The component never mutates the diff or applies\n * anything; it reports which hunks were accepted and rejected and leaves the\n * consequences to the application, the same way ai-action-ledger does.\n *\n * Not a grid. A code listing is a table of text, and role=\"grid\" would promise\n * cell-by-cell arrow navigation that neither exists here nor makes sense for\n * reading code. Line kind is carried in text, because a plus sign and a green\n * background are not information.\n */\n\nexport type HunkDecision = \"accepted\" | \"rejected\";\n\nconst KIND_LABEL: Record<DiffRow[\"kind\"], string> = {\n added: \"Added\",\n removed: \"Removed\",\n context: \"Unchanged\",\n};\n\nexport interface DiffViewerProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n hunks: DiffHunk[];\n /** Names the diff — usually the path of the file being changed. */\n label: string;\n view?: \"unified\" | \"split\";\n /** Per-hunk decisions, keyed by hunk id. Controlled. */\n decisions?: Record<string, HunkDecision>;\n onDecision?: (hunkId: string, decision: HunkDecision) => void;\n children?: ReactNode;\n}\n\nexport function DiffViewer({\n className,\n hunks,\n label,\n view = \"unified\",\n decisions,\n onDecision,\n children,\n ...props\n}: DiffViewerProps) {\n const counts = useMemo(() => countChanges(hunks), [hunks]);\n\n return (\n <div\n data-slot=\"diff-viewer\"\n data-view={view}\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <span className=\"font-mono text-sm font-medium\">{label}</span>\n {/* Said in words as well as coloured, and before the diff rather than\n after it: a reader deserves to know the size of the change before\n they start reading it. */}\n <span data-slot=\"diff-viewer-summary\" className=\"text-xs text-muted-foreground\">\n <span className=\"text-success\">+{counts.added}</span>{\" \"}\n <span className=\"text-destructive\">−{counts.removed}</span>\n <span className=\"sr-only\">\n {` — ${String(counts.added)} lines added, ${String(counts.removed)} removed`}\n </span>\n </span>\n </div>\n\n {children}\n\n {hunks.length === 0 ? (\n <p className=\"rounded-lg border border-border bg-muted/30 p-3 text-sm text-muted-foreground\">\n No changes.\n </p>\n ) : (\n <div className=\"overflow-hidden rounded-lg border border-border\">\n {hunks.map((hunk) => (\n <DiffHunkView\n key={hunk.id}\n hunk={hunk}\n label={label}\n view={view}\n decision={decisions?.[hunk.id]}\n onDecision={onDecision}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction DiffHunkView({\n hunk,\n label,\n view,\n decision,\n onDecision,\n}: {\n hunk: DiffHunk;\n label: string;\n view: \"unified\" | \"split\";\n decision?: HunkDecision;\n onDecision?: (hunkId: string, decision: HunkDecision) => void;\n}) {\n const splitRows = useMemo(\n () => (view === \"split\" ? toSplitRows(hunk.rows) : []),\n [view, hunk.rows],\n );\n\n return (\n <section\n data-slot=\"diff-hunk\"\n data-decision={decision}\n aria-label={`${label}, hunk ${hunk.id}`}\n className={cn(\n \"border-b border-border last:border-b-0\",\n decision === \"rejected\" && \"opacity-55\",\n )}\n >\n {hunk.skippedBefore > 0 ? (\n <p\n data-slot=\"diff-hunk-skipped\"\n className=\"border-b border-border bg-muted/40 px-3 py-1 font-mono text-2xs text-muted-foreground\"\n >\n {hunk.skippedBefore} unchanged {hunk.skippedBefore === 1 ? \"line\" : \"lines\"} hidden\n </p>\n ) : null}\n\n {onDecision ? (\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5\">\n <span className=\"flex-1 text-xs text-muted-foreground\">\n {decision === \"accepted\"\n ? \"Accepted\"\n : decision === \"rejected\"\n ? \"Rejected\"\n : \"Not decided\"}\n </span>\n <HunkButton\n pressed={decision === \"accepted\"}\n onClick={() => {\n onDecision(hunk.id, \"accepted\");\n }}\n >\n Accept\n </HunkButton>\n <HunkButton\n pressed={decision === \"rejected\"}\n onClick={() => {\n onDecision(hunk.id, \"rejected\");\n }}\n >\n Reject\n </HunkButton>\n </div>\n ) : null}\n\n <div className=\"overflow-x-auto\">\n <table data-slot=\"diff-table\" className=\"w-full border-collapse font-mono text-xs\">\n <tbody>\n {view === \"split\"\n ? splitRows.map((pair, index) => (\n <tr key={index} data-slot=\"diff-row\">\n <SplitCell row={pair.left} side=\"before\" />\n <SplitCell row={pair.right} side=\"after\" />\n </tr>\n ))\n : hunk.rows.map((row, index) => <UnifiedRow key={index} row={row} />)}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n\nconst ROW_STYLES: Record<DiffRow[\"kind\"], string> = {\n added: \"bg-success/10\",\n removed: \"bg-destructive/10\",\n context: \"\",\n};\n\nfunction UnifiedRow({ row }: { row: DiffRow }) {\n return (\n <tr data-slot=\"diff-row\" data-kind={row.kind} className={ROW_STYLES[row.kind]}>\n <LineNumber value={row.before} />\n <LineNumber value={row.after} />\n <td className=\"w-4 pr-1 text-center text-muted-foreground select-none\" aria-hidden=\"true\">\n {row.kind === \"added\" ? \"+\" : row.kind === \"removed\" ? \"−\" : \"\"}\n </td>\n <td className=\"w-full py-0.5 pr-3 break-all whitespace-pre-wrap\">\n {/* The kind, for anyone who cannot see the sign or the tint. Reading a\n diff aloud without it is reading the same file twice. */}\n <span className=\"sr-only\">{KIND_LABEL[row.kind]}: </span>\n <RowContent row={row} />\n </td>\n </tr>\n );\n}\n\nfunction SplitCell({ row, side }: { row: DiffRow | null; side: \"before\" | \"after\" }) {\n if (!row) {\n // An empty half of a pair, not a blank line of code. Hidden from assistive\n // technology so a reader is not read padding.\n return (\n <>\n <td aria-hidden=\"true\" className=\"w-10 bg-muted/40\" />\n <td aria-hidden=\"true\" className=\"w-1/2 bg-muted/20\" />\n </>\n );\n }\n\n return (\n <>\n <LineNumber value={side === \"before\" ? row.before : row.after} />\n <td\n className={cn(\"w-1/2 py-0.5 pr-3 break-all whitespace-pre-wrap\", ROW_STYLES[row.kind])}\n >\n <span className=\"sr-only\">{KIND_LABEL[row.kind]}: </span>\n <RowContent row={row} />\n </td>\n </>\n );\n}\n\nfunction RowContent({ row }: { row: DiffRow }) {\n if (!row.segments) return <>{row.content}</>;\n\n return (\n <>\n {row.segments.map((segment, index) =>\n segment.changed ? (\n // A mark element, so the changed words survive as structure rather\n // than existing only as a slightly stronger background.\n <mark\n key={index}\n className={cn(\n \"rounded-[2px] text-inherit\",\n row.kind === \"added\" ? \"bg-success/30\" : \"bg-destructive/30\",\n )}\n >\n {segment.text}\n </mark>\n ) : (\n <span key={index}>{segment.text}</span>\n ),\n )}\n </>\n );\n}\n\nfunction LineNumber({ value }: { value?: number }) {\n return (\n <td\n // Decorative: the line number is orientation for a sighted reader, and\n // announcing two numbers before every line makes the diff unlistenable.\n aria-hidden=\"true\"\n className=\"w-10 border-r border-border bg-muted/40 px-2 py-0.5 text-right text-muted-foreground tabular-nums select-none\"\n >\n {value ?? \"\"}\n </td>\n );\n}\n\nfunction HunkButton({\n className,\n pressed,\n ...props\n}: ComponentPropsWithRef<\"button\"> & { pressed: boolean }) {\n return (\n <button\n type=\"button\"\n aria-pressed={pressed}\n className={cn(\n \"rounded-md border px-2 py-0.5 text-xs font-medium transition-colors\",\n pressed\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n disabledStyles,\n className,\n )}\n {...props}\n />\n );\n}\n\n/** Switches between unified and side-by-side. */\nexport function DiffViewerToolbar({\n className,\n view,\n onViewChange,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"onChange\"> & {\n view: \"unified\" | \"split\";\n onViewChange: (view: \"unified\" | \"split\") => void;\n}) {\n return (\n <div\n data-slot=\"diff-viewer-toolbar\"\n role=\"group\"\n aria-label=\"Diff layout\"\n className={cn(\"flex items-center gap-1\", className)}\n {...props}\n >\n {([\"unified\", \"split\"] as const).map((option) => (\n <button\n key={option}\n type=\"button\"\n aria-pressed={view === option}\n onClick={() => {\n onViewChange(option);\n }}\n className={cn(\n \"rounded-md border px-2 py-0.5 text-xs font-medium capitalize transition-colors\",\n view === option\n ? \"border-transparent bg-secondary text-secondary-foreground\"\n : \"border-input bg-background text-muted-foreground hover:text-foreground\",\n focusRing,\n )}\n >\n {option}\n </button>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;;AA+BA,MAAM,aAA8C;CAClD,OAAO;CACP,SAAS;CACT,SAAS;AACX;AAaA,SAAgB,WAAW,EACzB,WACA,OACA,OACA,OAAO,WACP,WACA,YACA,UACA,GAAG,SACe;CAClB,MAAM,SAAS,cAAc,aAAa,KAAK,GAAG,CAAC,KAAK,CAAC;CAEzD,OACE,qBAAC,OAAD;EACE,aAAU;EACV,aAAW;EACX,WAAW,GAAG,uBAAuB,SAAS;EAC9C,GAAI;EAJN,UAAA;GAME,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,QAAD;KAAM,WAAU;KAAiC,UAAA;IAAY,CAAA,GAI7D,qBAAC,QAAD;KAAM,aAAU;KAAsB,WAAU;KAAhD,UAAA;MACE,qBAAC,QAAD;OAAM,WAAU;OAAhB,UAAA,CAA+B,KAAE,OAAO,KAAY;;MAAE;MACtD,qBAAC,QAAD;OAAM,WAAU;OAAhB,UAAA,CAAmC,KAAE,OAAO,OAAc;;MAC1D,oBAAC,QAAD;OAAM,WAAU;OACb,UAAA,MAAM,OAAO,OAAO,KAAK,EAAE,gBAAgB,OAAO,OAAO,OAAO,EAAE;MAC/D,CAAA;KACF;IACH,CAAA,CAAA;;GAEJ;GAEA,MAAM,WAAW,IAChB,oBAAC,KAAD;IAAG,WAAU;IAAgF,UAAA;GAE1F,CAAA,IAEH,oBAAC,OAAD;IAAK,WAAU;IACZ,UAAA,MAAM,KAAK,SACV,oBAAC,cAAD;KAEQ;KACC;KACD;KACN,UAAU,YAAY,KAAK;KACf;IACb,GANM,KAAK,EAMX,CACF;GACE,CAAA;EAEJ;;AAET;AAEA,SAAS,aAAa,EACpB,MACA,OACA,MACA,UACA,cAOC;CACD,MAAM,YAAY,cACT,SAAS,UAAU,YAAY,KAAK,IAAI,IAAI,CAAC,GACpD,CAAC,MAAM,KAAK,IAAI,CAClB;CAEA,OACE,qBAAC,WAAD;EACE,aAAU;EACV,iBAAe;EACf,cAAY,GAAG,MAAM,SAAS,KAAK;EACnC,WAAW,GACT,0CACA,aAAa,cAAc,YAC7B;EAPF,UAAA;GASG,KAAK,gBAAgB,IACpB,qBAAC,KAAD;IACE,aAAU;IACV,WAAU;IAFZ,UAAA;KAIG,KAAK;KAAc;KAAY,KAAK,kBAAkB,IAAI,SAAS;KAAQ;IAC3E;GACD,CAAA,IAAA;GAEH,aACC,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,QAAD;MAAM,WAAU;MACb,UAAA,aAAa,aACV,aACA,aAAa,aACX,aACA;KACF,CAAA;KACN,oBAAC,YAAD;MACE,SAAS,aAAa;MACtB,eAAe;OACb,WAAW,KAAK,IAAI,UAAU;MAChC;MACD,UAAA;KAEW,CAAA;KACZ,oBAAC,YAAD;MACE,SAAS,aAAa;MACtB,eAAe;OACb,WAAW,KAAK,IAAI,UAAU;MAChC;MACD,UAAA;KAEW,CAAA;IACT;GACH,CAAA,IAAA;GAEJ,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,SAAD;KAAO,aAAU;KAAa,WAAU;KACtC,UAAA,oBAAC,SAAD,EAAA,UACG,SAAS,UACN,UAAU,KAAK,MAAM,UACnB,qBAAC,MAAD;MAAgB,aAAU;MAA1B,UAAA,CACE,oBAAC,WAAD;OAAW,KAAK,KAAK;OAAM,MAAK;MAAU,CAAA,GAC1C,oBAAC,WAAD;OAAW,KAAK,KAAK;OAAO,MAAK;MAAS,CAAA,CACxC;KAHK,GAAA,KAGL,CACL,IACD,KAAK,KAAK,KAAK,KAAK,UAAU,oBAAC,YAAD,EAA6B,IAAM,GAAlB,KAAkB,CAAC,EACjE,CAAA;IACF,CAAA;GACJ,CAAA;EACE;;AAEb;AAEA,MAAM,aAA8C;CAClD,OAAO;CACP,SAAS;CACT,SAAS;AACX;AAEA,SAAS,WAAW,EAAE,OAAyB;CAC7C,OACE,qBAAC,MAAD;EAAI,aAAU;EAAW,aAAW,IAAI;EAAM,WAAW,WAAW,IAAI;EAAxE,UAAA;GACE,oBAAC,YAAD,EAAY,OAAO,IAAI,OAAS,CAAA;GAChC,oBAAC,YAAD,EAAY,OAAO,IAAI,MAAQ,CAAA;GAC/B,oBAAC,MAAD;IAAI,WAAU;IAAyD,eAAY;IAChF,UAAA,IAAI,SAAS,UAAU,MAAM,IAAI,SAAS,YAAY,MAAM;GAC3D,CAAA;GACJ,qBAAC,MAAD;IAAI,WAAU;IAAd,UAAA,CAGE,qBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA,CAA2B,WAAW,IAAI,OAAM,IAAQ;IACxD,CAAA,GAAA,oBAAC,YAAD,EAAiB,IAAM,CAAA,CACrB;;EACF;;AAER;AAEA,SAAS,UAAU,EAAE,KAAK,QAA2D;CACnF,IAAI,CAAC,KAGH,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,MAAD;EAAI,eAAY;EAAO,WAAU;CAAoB,CAAA,GACrD,oBAAC,MAAD;EAAI,eAAY;EAAO,WAAU;CAAqB,CAAA,CACtD,EAAA,CAAA;CAIN,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,YAAD,EAAY,OAAO,SAAS,WAAW,IAAI,SAAS,IAAI,MAAQ,CAAA,GAChE,qBAAC,MAAD;EACE,WAAW,GAAG,mDAAmD,WAAW,IAAI,KAAK;EADvF,UAAA,CAGE,qBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CAA2B,WAAW,IAAI,OAAM,IAAQ;EACxD,CAAA,GAAA,oBAAC,YAAD,EAAiB,IAAM,CAAA,CACrB;CACJ,CAAA,CAAA,EAAA,CAAA;AAEN;AAEA,SAAS,WAAW,EAAE,OAAyB;CAC7C,IAAI,CAAC,IAAI,UAAU,OAAO,oBAAA,UAAA,EAAA,UAAG,IAAI,QAAU,CAAA;CAE3C,OACE,oBAAA,UAAA,EAAA,UACG,IAAI,SAAS,KAAK,SAAS,UAC1B,QAAQ,UAGN,oBAAC,QAAD;EAEE,WAAW,GACT,8BACA,IAAI,SAAS,UAAU,kBAAkB,mBAC3C;EAEC,UAAA,QAAQ;CACL,GAPC,KAOD,IAEN,oBAAC,QAAD,EAAA,UAAmB,QAAQ,KAAW,GAA3B,KAA2B,CAE1C,EACA,CAAA;AAEN;AAEA,SAAS,WAAW,EAAE,SAA6B;CACjD,OACE,oBAAC,MAAD;EAGE,eAAY;EACZ,WAAU;EAET,UAAA,SAAS;CACR,CAAA;AAER;AAEA,SAAS,WAAW,EAClB,WACA,SACA,GAAG,SACsD;CACzD,OACE,oBAAC,UAAD;EACE,MAAK;EACL,gBAAc;EACd,WAAW,GACT,uEACA,UACI,sDACA,2EACJ,WACA,gBACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;;AAGA,SAAgB,kBAAkB,EAChC,WACA,MACA,cACA,GAAG,SAIF;CACD,OACE,oBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,cAAW;EACX,WAAW,GAAG,2BAA2B,SAAS;EAClD,GAAI;EAEF,UAAA,CAAC,WAAW,OAAO,CAAC,CAAW,KAAK,WACpC,oBAAC,UAAD;GAEE,MAAK;GACL,gBAAc,SAAS;GACvB,eAAe;IACb,aAAa,MAAM;GACrB;GACA,WAAW,GACT,kFACA,SAAS,SACL,8DACA,0EACJ,SACF;GAEC,UAAA;EACK,GAfD,MAeC,CACT;CACE,CAAA;AAET"}
|
|
1
|
+
{"version":3,"file":"diff-viewer.js","names":[],"sources":["../../../src/components/diff-viewer/diff-viewer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type ComponentPropsWithRef, type ReactNode } from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport { countChanges, toSplitRows, type DiffHunk, type DiffRow } from \"./diff-model\";\n\n/**\n * A diff, and a decision about it.\n *\n * The rendering half exists because every packaged viewer brings its own\n * styling system — emotion, or HTML strings plus a stylesheet — which design\n * tokens cannot reach and which is awkward under RSC. The deciding half exists\n * because Dowel is for AI products, and an agent proposing a change to a file is\n * the case that needs it: what every coding agent ships, and no component\n * library does.\n *\n * Decisions are controlled. The component never mutates the diff or applies\n * anything; it reports which hunks were accepted and rejected and leaves the\n * consequences to the application, the same way ai-action-ledger does.\n *\n * Not a grid. A code listing is a table of text, and role=\"grid\" would promise\n * cell-by-cell arrow navigation that neither exists here nor makes sense for\n * reading code. Line kind is carried in text, because a plus sign and a green\n * background are not information.\n */\n\nexport type HunkDecision = \"accepted\" | \"rejected\";\n\nconst KIND_LABEL: Record<DiffRow[\"kind\"], string> = {\n added: \"Added\",\n removed: \"Removed\",\n context: \"Unchanged\",\n};\n\nexport interface DiffViewerProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n hunks: DiffHunk[];\n /** Names the diff — usually the path of the file being changed. */\n label: string;\n view?: \"unified\" | \"split\";\n /** Per-hunk decisions, keyed by hunk id. Controlled. */\n decisions?: Record<string, HunkDecision>;\n onDecision?: (hunkId: string, decision: HunkDecision) => void;\n children?: ReactNode;\n}\n\nexport function DiffViewer({\n className,\n hunks,\n label,\n view = \"unified\",\n decisions,\n onDecision,\n children,\n ...props\n}: DiffViewerProps) {\n const counts = useMemo(() => countChanges(hunks), [hunks]);\n\n return (\n <div\n data-slot=\"diff-viewer\"\n data-view={view}\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <span className=\"font-mono text-sm font-medium\">{label}</span>\n {/* Said in words as well as coloured, and before the diff rather than\n after it: a reader deserves to know the size of the change before\n they start reading it. */}\n <span data-slot=\"diff-viewer-summary\" className=\"text-xs text-muted-foreground\">\n <span className=\"text-success\">+{counts.added}</span>{\" \"}\n <span className=\"text-destructive\">−{counts.removed}</span>\n <span className=\"sr-only\">\n {` — ${String(counts.added)} lines added, ${String(counts.removed)} removed`}\n </span>\n </span>\n </div>\n\n {children}\n\n {hunks.length === 0 ? (\n <p className=\"rounded-lg border border-border bg-muted/30 p-3 text-sm text-muted-foreground\">\n No changes.\n </p>\n ) : (\n <div className=\"overflow-hidden rounded-lg border border-border\">\n {hunks.map((hunk) => (\n <DiffHunkView\n key={hunk.id}\n hunk={hunk}\n label={label}\n view={view}\n decision={decisions?.[hunk.id]}\n onDecision={onDecision}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction DiffHunkView({\n hunk,\n label,\n view,\n decision,\n onDecision,\n}: {\n hunk: DiffHunk;\n label: string;\n view: \"unified\" | \"split\";\n decision?: HunkDecision;\n onDecision?: (hunkId: string, decision: HunkDecision) => void;\n}) {\n const splitRows = useMemo(\n () => (view === \"split\" ? toSplitRows(hunk.rows) : []),\n [view, hunk.rows],\n );\n\n return (\n <section\n data-slot=\"diff-hunk\"\n data-decision={decision}\n aria-label={`${label}, hunk ${hunk.id}`}\n className={cn(\n \"border-b border-border last:border-b-0\",\n decision === \"rejected\" && \"opacity-55\",\n )}\n >\n {hunk.skippedBefore > 0 ? (\n <p\n data-slot=\"diff-hunk-skipped\"\n className=\"border-b border-border bg-muted/40 px-3 py-1 font-mono text-2xs text-muted-foreground\"\n >\n {hunk.skippedBefore} unchanged {hunk.skippedBefore === 1 ? \"line\" : \"lines\"} hidden\n </p>\n ) : null}\n\n {onDecision ? (\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5\">\n <span className=\"flex-1 text-xs text-muted-foreground\">\n {decision === \"accepted\"\n ? \"Accepted\"\n : decision === \"rejected\"\n ? \"Rejected\"\n : \"Not decided\"}\n </span>\n <HunkButton\n pressed={decision === \"accepted\"}\n onClick={() => {\n onDecision(hunk.id, \"accepted\");\n }}\n >\n Accept\n </HunkButton>\n <HunkButton\n pressed={decision === \"rejected\"}\n onClick={() => {\n onDecision(hunk.id, \"rejected\");\n }}\n >\n Reject\n </HunkButton>\n </div>\n ) : null}\n\n <div className=\"overflow-x-auto\">\n <table data-slot=\"diff-table\" className=\"w-full border-collapse font-mono text-xs\">\n <tbody>\n {view === \"split\"\n ? splitRows.map((pair, index) => (\n <tr key={index} data-slot=\"diff-row\">\n <SplitCell row={pair.left} side=\"before\" />\n <SplitCell row={pair.right} side=\"after\" />\n </tr>\n ))\n : hunk.rows.map((row, index) => <UnifiedRow key={index} row={row} />)}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n\nconst ROW_STYLES: Record<DiffRow[\"kind\"], string> = {\n added: \"bg-success/10\",\n removed: \"bg-destructive/10\",\n context: \"\",\n};\n\nfunction UnifiedRow({ row }: { row: DiffRow }) {\n return (\n <tr data-slot=\"diff-row\" data-kind={row.kind} className={ROW_STYLES[row.kind]}>\n <LineNumber value={row.before} />\n <LineNumber value={row.after} />\n <td className=\"w-4 pe-1 text-center text-muted-foreground select-none\" aria-hidden=\"true\">\n {row.kind === \"added\" ? \"+\" : row.kind === \"removed\" ? \"−\" : \"\"}\n </td>\n <td className=\"w-full py-0.5 pe-3 break-all whitespace-pre-wrap\">\n {/* The kind, for anyone who cannot see the sign or the tint. Reading a\n diff aloud without it is reading the same file twice. */}\n <span className=\"sr-only\">{KIND_LABEL[row.kind]}: </span>\n <RowContent row={row} />\n </td>\n </tr>\n );\n}\n\nfunction SplitCell({ row, side }: { row: DiffRow | null; side: \"before\" | \"after\" }) {\n if (!row) {\n // An empty half of a pair, not a blank line of code. Hidden from assistive\n // technology so a reader is not read padding.\n return (\n <>\n <td aria-hidden=\"true\" className=\"w-10 bg-muted/40\" />\n <td aria-hidden=\"true\" className=\"w-1/2 bg-muted/20\" />\n </>\n );\n }\n\n return (\n <>\n <LineNumber value={side === \"before\" ? row.before : row.after} />\n <td\n className={cn(\"w-1/2 py-0.5 pe-3 break-all whitespace-pre-wrap\", ROW_STYLES[row.kind])}\n >\n <span className=\"sr-only\">{KIND_LABEL[row.kind]}: </span>\n <RowContent row={row} />\n </td>\n </>\n );\n}\n\nfunction RowContent({ row }: { row: DiffRow }) {\n if (!row.segments) return <>{row.content}</>;\n\n return (\n <>\n {row.segments.map((segment, index) =>\n segment.changed ? (\n // A mark element, so the changed words survive as structure rather\n // than existing only as a slightly stronger background.\n <mark\n key={index}\n className={cn(\n \"rounded-[2px] text-inherit\",\n row.kind === \"added\" ? \"bg-success/30\" : \"bg-destructive/30\",\n )}\n >\n {segment.text}\n </mark>\n ) : (\n <span key={index}>{segment.text}</span>\n ),\n )}\n </>\n );\n}\n\nfunction LineNumber({ value }: { value?: number }) {\n return (\n <td\n // Decorative: the line number is orientation for a sighted reader, and\n // announcing two numbers before every line makes the diff unlistenable.\n aria-hidden=\"true\"\n className=\"w-10 border-e border-border bg-muted/40 px-2 py-0.5 text-end text-muted-foreground tabular-nums select-none\"\n >\n {value ?? \"\"}\n </td>\n );\n}\n\nfunction HunkButton({\n className,\n pressed,\n ...props\n}: ComponentPropsWithRef<\"button\"> & { pressed: boolean }) {\n return (\n <button\n type=\"button\"\n aria-pressed={pressed}\n className={cn(\n \"rounded-md border px-2 py-0.5 text-xs font-medium transition-colors\",\n pressed\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n disabledStyles,\n className,\n )}\n {...props}\n />\n );\n}\n\n/** Switches between unified and side-by-side. */\nexport function DiffViewerToolbar({\n className,\n view,\n onViewChange,\n ...props\n}: Omit<ComponentPropsWithRef<\"div\">, \"onChange\"> & {\n view: \"unified\" | \"split\";\n onViewChange: (view: \"unified\" | \"split\") => void;\n}) {\n return (\n <div\n data-slot=\"diff-viewer-toolbar\"\n role=\"group\"\n aria-label=\"Diff layout\"\n className={cn(\"flex items-center gap-1\", className)}\n {...props}\n >\n {([\"unified\", \"split\"] as const).map((option) => (\n <button\n key={option}\n type=\"button\"\n aria-pressed={view === option}\n onClick={() => {\n onViewChange(option);\n }}\n className={cn(\n \"rounded-md border px-2 py-0.5 text-xs font-medium capitalize transition-colors\",\n view === option\n ? \"border-transparent bg-secondary text-secondary-foreground\"\n : \"border-input bg-background text-muted-foreground hover:text-foreground\",\n focusRing,\n )}\n >\n {option}\n </button>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/components/direction/direction.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Tells the component set which way the writing runs.
|
|
5
|
+
*
|
|
6
|
+
* Every direction-dependent style in this library is written logically —
|
|
7
|
+
* `ps-`, `me-`, `text-end` — so the CSS follows `dir` on its own. The
|
|
8
|
+
* primitives underneath do not: several of them read direction from React
|
|
9
|
+
* context rather than from the document, and with no provider they assume
|
|
10
|
+
* left-to-right and set `dir="ltr"` on their own elements. The result is a page
|
|
11
|
+
* that mirrors correctly except for its menus, selects and sliders, which is
|
|
12
|
+
* worse than one that does not mirror at all, because it looks deliberate.
|
|
13
|
+
*
|
|
14
|
+
* So an RTL application needs both: `dir` on the document for the CSS, and this
|
|
15
|
+
* for the primitives.
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <html dir="rtl" lang="ar">
|
|
19
|
+
* <body>
|
|
20
|
+
* <DirectionProvider dir="rtl">{children}</DirectionProvider>
|
|
21
|
+
* </body>
|
|
22
|
+
* </html>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* A left-to-right application needs neither. Nothing here is required to render
|
|
26
|
+
* English correctly, which is why it is easy to ship without it and never know.
|
|
27
|
+
*/
|
|
28
|
+
interface DirectionProviderProps {
|
|
29
|
+
dir: "ltr" | "rtl";
|
|
30
|
+
children?: ReactNode;
|
|
31
|
+
}
|
|
32
|
+
declare function DirectionProvider({ dir, children }: DirectionProviderProps): import("react").JSX.Element;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { DirectionProvider, DirectionProviderProps };
|
|
35
|
+
//# sourceMappingURL=direction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"direction.d.ts","names":[],"sources":["../../../src/components/direction/direction.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BiB;EACf;EACA,WAAW;;iBAGG,oBAAoB,KAAK,YAAY,yCAAsB,IAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Direction } from "radix-ui";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/components/direction/direction.tsx
|
|
5
|
+
function DirectionProvider({ dir, children }) {
|
|
6
|
+
return /* @__PURE__ */ jsx(Direction.Provider, {
|
|
7
|
+
dir,
|
|
8
|
+
children
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { DirectionProvider };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=direction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"direction.js","names":["DirectionPrimitive"],"sources":["../../../src/components/direction/direction.tsx"],"sourcesContent":["\"use client\";\n\nimport { Direction as DirectionPrimitive } from \"radix-ui\";\nimport type { ReactNode } from \"react\";\n\n/**\n * Tells the component set which way the writing runs.\n *\n * Every direction-dependent style in this library is written logically —\n * `ps-`, `me-`, `text-end` — so the CSS follows `dir` on its own. The\n * primitives underneath do not: several of them read direction from React\n * context rather than from the document, and with no provider they assume\n * left-to-right and set `dir=\"ltr\"` on their own elements. The result is a page\n * that mirrors correctly except for its menus, selects and sliders, which is\n * worse than one that does not mirror at all, because it looks deliberate.\n *\n * So an RTL application needs both: `dir` on the document for the CSS, and this\n * for the primitives.\n *\n * ```tsx\n * <html dir=\"rtl\" lang=\"ar\">\n * <body>\n * <DirectionProvider dir=\"rtl\">{children}</DirectionProvider>\n * </body>\n * </html>\n * ```\n *\n * A left-to-right application needs neither. Nothing here is required to render\n * English correctly, which is why it is easy to ship without it and never know.\n */\nexport interface DirectionProviderProps {\n dir: \"ltr\" | \"rtl\";\n children?: ReactNode;\n}\n\nexport function DirectionProvider({ dir, children }: DirectionProviderProps) {\n return <DirectionPrimitive.Provider dir={dir}>{children}</DirectionPrimitive.Provider>;\n}\n"],"mappings":";;;;AAmCA,SAAgB,kBAAkB,EAAE,KAAK,YAAoC;CAC3E,OAAO,oBAACA,UAAmB,UAApB;EAAkC;EAAM;CAAsC,CAAA;AACvF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/direction/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "direction",
|
|
5
|
+
title: "Direction Provider",
|
|
6
|
+
description: "Tells the component primitives which way the writing runs, so menus and sliders mirror with the rest of the page.",
|
|
7
|
+
category: "foundation",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: ["radix-ui"],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["direction.tsx"],
|
|
12
|
+
a11y: "Needed only for right-to-left languages, and needed in addition to `dir` on the document rather than instead of it. The styling here is written with logical properties and follows `dir` by itself; the primitives underneath read direction from React context and assume left-to-right without a provider, which mirrors a page correctly except for its menus, selects and sliders — worse than not mirroring at all, because it looks deliberate."
|
|
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/direction/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"direction\",\n title: \"Direction Provider\",\n description:\n \"Tells the component primitives which way the writing runs, so menus and sliders mirror with the rest of the page.\",\n category: \"foundation\",\n status: \"stable\",\n dependencies: [\"radix-ui\"],\n registryDependencies: [],\n files: [\"direction.tsx\"],\n a11y:\n \"Needed only for right-to-left languages, and needed in addition to `dir` on the document \" +\n \"rather than instead of it. The styling here is written with logical properties and follows \" +\n \"`dir` by itself; the primitives underneath read direction from React context and assume \" +\n \"left-to-right without a provider, which mirrors a page correctly except for its menus, \" +\n \"selects and sliders — worse than not mirroring at all, because it looks deliberate.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC,UAAU;CACzB,sBAAsB,CAAC;CACvB,OAAO,CAAC,eAAe;CACvB,MACE;AAKJ,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
//#region src/components/dns-record/dns-record.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* "Add this record at your DNS provider."
|
|
5
|
+
*
|
|
6
|
+
* Vercel, Resend, Cloudflare, Postmark, Google Workspace and every product
|
|
7
|
+
* that verifies a domain or routes its mail draws this card by hand, and
|
|
8
|
+
* they all learn the same three things the hard way.
|
|
9
|
+
*
|
|
10
|
+
* The parts are copied separately. A provider's form has a Name field, a
|
|
11
|
+
* Type field and a Value field, so one button that copies the whole line
|
|
12
|
+
* copies something nobody can paste anywhere.
|
|
13
|
+
*
|
|
14
|
+
* The Name field is a trap. Some providers want the host relative to the
|
|
15
|
+
* zone, `_dmarc`; some want the full name, `_dmarc.acme.com`; and some take
|
|
16
|
+
* the relative form and append the zone themselves, so a full name pasted
|
|
17
|
+
* in becomes `_dmarc.acme.com.acme.com` and the check fails for a reason
|
|
18
|
+
* nobody can see. So the host is shown both ways, with the sentence that
|
|
19
|
+
* explains which to use.
|
|
20
|
+
*
|
|
21
|
+
* A failed check says what was found. "Not verified" sends people back to
|
|
22
|
+
* their provider to stare at a record that is correct and has not
|
|
23
|
+
* propagated yet; "found v=spf1 -all, expected v=spf1 include:…" sends them
|
|
24
|
+
* to the typo. And nothing found at all is said as nothing found, with how
|
|
25
|
+
* long that can take, because it is not the same as wrong.
|
|
26
|
+
*/
|
|
27
|
+
type DnsRecordStatus = "pending" | "checking" | "verified" | "failed";
|
|
28
|
+
interface DnsHostForms {
|
|
29
|
+
/** Relative to the zone: `_dmarc`, or `@` for the zone itself. */
|
|
30
|
+
relative: string;
|
|
31
|
+
/** Fully qualified: `_dmarc.acme.com`. */
|
|
32
|
+
full: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The host both ways a provider might want it. Accepts either form and a
|
|
36
|
+
* trailing dot, so the record can be declared however the backend has it.
|
|
37
|
+
*/
|
|
38
|
+
declare function dnsHostForms(name: string, zone: string): DnsHostForms;
|
|
39
|
+
interface DnsRecordProps extends Omit<ComponentPropsWithRef<"section">, "children"> {
|
|
40
|
+
type: string;
|
|
41
|
+
/** The host, relative or fully qualified. */
|
|
42
|
+
name: string;
|
|
43
|
+
value: string;
|
|
44
|
+
/** The zone the record belongs to. Enables the host to be shown both ways. */
|
|
45
|
+
zone?: string;
|
|
46
|
+
ttl?: number | string;
|
|
47
|
+
priority?: number;
|
|
48
|
+
/** What the record is for, in the reader's language: "Proves you own acme.com". */
|
|
49
|
+
purpose?: string;
|
|
50
|
+
status?: DnsRecordStatus;
|
|
51
|
+
/** What the check found instead, when it failed. The useful part. */
|
|
52
|
+
found?: string[];
|
|
53
|
+
checkedAt?: Date;
|
|
54
|
+
locale?: string;
|
|
55
|
+
onCheck?: () => void;
|
|
56
|
+
children?: ReactNode;
|
|
57
|
+
}
|
|
58
|
+
declare function DnsRecord({ className, type, name, value, zone, ttl, priority, purpose, status, found, checkedAt, locale, onCheck, children, ...props }: DnsRecordProps): import("react").JSX.Element;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { DnsHostForms, DnsRecord, DnsRecordProps, DnsRecordStatus, dnsHostForms };
|
|
61
|
+
//# sourceMappingURL=dns-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dns-record.d.ts","names":[],"sources":["../../../src/components/dns-record/dns-record.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;KAyCY;UAEK;;EAEf;;EAEA;;;;;;iBAOc,aAAa,cAAc,eAAe;UAYzC,uBAAuB,KAAK;EAC3C;;EAEA;EACA;;EAEA;EACA;EACA;;EAEA;EACA,SAAS;;EAET;EACA,YAAY;EACZ;EACA;EACA,WAAW;;iBAKG,YACd,WACA,MACA,MACA,OACA,MACA,KACA,UACA,SACA,QACA,OACA,WACA,QACA,SACA,aACG,SACF,iCAAc,IAAA"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { focusRing } from "../../lib/styles.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { Spinner } from "../spinner/spinner.js";
|
|
5
|
+
import { Button } from "../button/button.js";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
8
|
+
//#region src/components/dns-record/dns-record.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The host both ways a provider might want it. Accepts either form and a
|
|
11
|
+
* trailing dot, so the record can be declared however the backend has it.
|
|
12
|
+
*/
|
|
13
|
+
function dnsHostForms(name, zone) {
|
|
14
|
+
const cleanZone = zone.replace(/\.$/, "");
|
|
15
|
+
const cleanName = name.replace(/\.$/, "");
|
|
16
|
+
if (cleanName === "@" || cleanName === "" || cleanName === cleanZone) return {
|
|
17
|
+
relative: "@",
|
|
18
|
+
full: cleanZone
|
|
19
|
+
};
|
|
20
|
+
if (cleanName.endsWith(`.${cleanZone}`)) return {
|
|
21
|
+
relative: cleanName.slice(0, -cleanZone.length - 1),
|
|
22
|
+
full: cleanName
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
relative: cleanName,
|
|
26
|
+
full: `${cleanName}.${cleanZone}`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function DnsRecord({ className, type, name, value, zone, ttl, priority, purpose, status = "pending", found, checkedAt, locale, onCheck, children, ...props }) {
|
|
30
|
+
const id = useId();
|
|
31
|
+
const forms = zone ? dnsHostForms(name, zone) : null;
|
|
32
|
+
const host = forms ? forms.relative : name;
|
|
33
|
+
const [copy, setCopy] = useState(null);
|
|
34
|
+
const timer = useRef(null);
|
|
35
|
+
useEffect(() => () => {
|
|
36
|
+
if (timer.current) clearTimeout(timer.current);
|
|
37
|
+
}, []);
|
|
38
|
+
const copyPart = (part, text) => {
|
|
39
|
+
const settle = (state) => {
|
|
40
|
+
setCopy({
|
|
41
|
+
part,
|
|
42
|
+
state
|
|
43
|
+
});
|
|
44
|
+
if (timer.current) clearTimeout(timer.current);
|
|
45
|
+
timer.current = setTimeout(() => {
|
|
46
|
+
setCopy(null);
|
|
47
|
+
}, 2e3);
|
|
48
|
+
};
|
|
49
|
+
Promise.resolve().then(() => navigator.clipboard.writeText(text)).then(() => {
|
|
50
|
+
settle("copied");
|
|
51
|
+
}, () => {
|
|
52
|
+
settle("error");
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const statusText = status === "verified" ? "Verified" : status === "checking" ? "Checking…" : status === "failed" ? found && found.length > 0 ? "Not verified. Something else was found." : "Not verified. Nothing found yet — DNS changes can take up to 48 hours to appear." : "Not checked yet";
|
|
56
|
+
const checked = checkedAt !== void 0 ? new Intl.DateTimeFormat(locale, {
|
|
57
|
+
dateStyle: "medium",
|
|
58
|
+
timeStyle: "short"
|
|
59
|
+
}).format(checkedAt) : null;
|
|
60
|
+
const parts = [
|
|
61
|
+
{
|
|
62
|
+
key: "type",
|
|
63
|
+
label: "Type",
|
|
64
|
+
text: type
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "name",
|
|
68
|
+
label: "Name",
|
|
69
|
+
text: host,
|
|
70
|
+
note: forms ? forms.relative === "@" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
71
|
+
"Some providers want ",
|
|
72
|
+
/* @__PURE__ */ jsx("code", {
|
|
73
|
+
className: "font-mono",
|
|
74
|
+
children: forms.full
|
|
75
|
+
}),
|
|
76
|
+
" or leave it blank instead of ",
|
|
77
|
+
/* @__PURE__ */ jsx("code", {
|
|
78
|
+
className: "font-mono",
|
|
79
|
+
children: "@"
|
|
80
|
+
}),
|
|
81
|
+
"."
|
|
82
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
83
|
+
"Some providers want the full name, ",
|
|
84
|
+
/* @__PURE__ */ jsx("code", {
|
|
85
|
+
className: "font-mono",
|
|
86
|
+
children: forms.full
|
|
87
|
+
}),
|
|
88
|
+
". If the field already ends in ",
|
|
89
|
+
/* @__PURE__ */ jsx("code", {
|
|
90
|
+
className: "font-mono",
|
|
91
|
+
children: zone
|
|
92
|
+
}),
|
|
93
|
+
", enter just the part shown."
|
|
94
|
+
] }) : void 0
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: "value",
|
|
98
|
+
label: "Value",
|
|
99
|
+
text: value,
|
|
100
|
+
note: type.toUpperCase() === "TXT" ? "Enter without quotes; your provider adds them if it needs to." : void 0
|
|
101
|
+
},
|
|
102
|
+
...priority !== void 0 ? [{
|
|
103
|
+
key: "priority",
|
|
104
|
+
label: "Priority",
|
|
105
|
+
text: String(priority)
|
|
106
|
+
}] : [],
|
|
107
|
+
...ttl !== void 0 ? [{
|
|
108
|
+
key: "ttl",
|
|
109
|
+
label: "TTL",
|
|
110
|
+
text: String(ttl)
|
|
111
|
+
}] : []
|
|
112
|
+
];
|
|
113
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
114
|
+
"data-slot": "dns-record",
|
|
115
|
+
"data-status": status,
|
|
116
|
+
"aria-labelledby": `${id}-heading`,
|
|
117
|
+
"aria-busy": status === "checking" || void 0,
|
|
118
|
+
className: cn("flex flex-col gap-3 rounded-lg border border-border bg-card p-4", className),
|
|
119
|
+
...props,
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsxs("div", {
|
|
122
|
+
className: "flex flex-wrap items-baseline justify-between gap-2",
|
|
123
|
+
children: [/* @__PURE__ */ jsxs("h3", {
|
|
124
|
+
id: `${id}-heading`,
|
|
125
|
+
className: "text-sm font-medium",
|
|
126
|
+
children: [
|
|
127
|
+
type,
|
|
128
|
+
" record",
|
|
129
|
+
purpose ? /* @__PURE__ */ jsxs("span", {
|
|
130
|
+
className: "font-normal text-muted-foreground",
|
|
131
|
+
children: [" · ", purpose]
|
|
132
|
+
}) : null
|
|
133
|
+
]
|
|
134
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
135
|
+
"data-slot": "dns-record-status",
|
|
136
|
+
role: "status",
|
|
137
|
+
"aria-live": "polite",
|
|
138
|
+
className: cn("flex items-center gap-1.5 text-xs", status === "verified" && "text-success", status === "failed" && "text-destructive", (status === "pending" || status === "checking") && "text-muted-foreground"),
|
|
139
|
+
children: [
|
|
140
|
+
status === "checking" ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : null,
|
|
141
|
+
statusText,
|
|
142
|
+
checked && status !== "checking" ? /* @__PURE__ */ jsxs("span", {
|
|
143
|
+
className: "text-muted-foreground",
|
|
144
|
+
children: [" · checked ", checked]
|
|
145
|
+
}) : null
|
|
146
|
+
]
|
|
147
|
+
})]
|
|
148
|
+
}),
|
|
149
|
+
children,
|
|
150
|
+
/* @__PURE__ */ jsx("dl", {
|
|
151
|
+
className: "m-0 grid gap-x-4 gap-y-2 sm:grid-cols-[auto_1fr]",
|
|
152
|
+
children: parts.map((part) => /* @__PURE__ */ jsxs("div", {
|
|
153
|
+
className: "contents",
|
|
154
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
155
|
+
className: "text-xs font-medium text-muted-foreground sm:pt-1.5",
|
|
156
|
+
children: part.label
|
|
157
|
+
}), /* @__PURE__ */ jsxs("dd", {
|
|
158
|
+
className: "m-0 flex flex-col gap-1",
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ jsxs("div", {
|
|
161
|
+
className: "flex items-start gap-2",
|
|
162
|
+
children: [/* @__PURE__ */ jsx("code", {
|
|
163
|
+
"data-slot": "dns-record-part",
|
|
164
|
+
"data-part": part.key,
|
|
165
|
+
className: "min-w-0 flex-1 rounded-md bg-muted px-2 py-1 font-mono text-xs break-all",
|
|
166
|
+
children: part.text
|
|
167
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
168
|
+
type: "button",
|
|
169
|
+
"aria-label": `Copy ${part.label.toLowerCase()}`,
|
|
170
|
+
"data-state": copy?.part === part.key ? copy.state : "idle",
|
|
171
|
+
className: cn("shrink-0 rounded-md border border-input bg-background px-2 py-1 text-xs font-medium", "hover:bg-accent hover:text-accent-foreground", focusRing),
|
|
172
|
+
onClick: () => {
|
|
173
|
+
copyPart(part.key, part.text);
|
|
174
|
+
},
|
|
175
|
+
children: copy?.part === part.key && copy.state === "copied" ? "Copied" : "Copy"
|
|
176
|
+
})]
|
|
177
|
+
}),
|
|
178
|
+
copy?.part === part.key && copy.state === "error" ? /* @__PURE__ */ jsx("p", {
|
|
179
|
+
"data-slot": "dns-record-copy-error",
|
|
180
|
+
className: "text-xs text-destructive",
|
|
181
|
+
children: "Could not copy. Select it and copy by hand."
|
|
182
|
+
}) : null,
|
|
183
|
+
part.note ? /* @__PURE__ */ jsx("p", {
|
|
184
|
+
className: "text-xs text-muted-foreground",
|
|
185
|
+
children: part.note
|
|
186
|
+
}) : null
|
|
187
|
+
]
|
|
188
|
+
})]
|
|
189
|
+
}, part.key))
|
|
190
|
+
}),
|
|
191
|
+
status === "failed" && found && found.length > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
192
|
+
"data-slot": "dns-record-found",
|
|
193
|
+
className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-xs",
|
|
194
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
195
|
+
className: "font-medium text-destructive",
|
|
196
|
+
children: "Found instead:"
|
|
197
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
198
|
+
className: "m-0 mt-1 list-none p-0",
|
|
199
|
+
children: found.map((entry) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", {
|
|
200
|
+
className: "font-mono break-all",
|
|
201
|
+
children: entry
|
|
202
|
+
}) }, entry))
|
|
203
|
+
})]
|
|
204
|
+
}) : null,
|
|
205
|
+
/* @__PURE__ */ jsx("span", {
|
|
206
|
+
role: "status",
|
|
207
|
+
"aria-live": "polite",
|
|
208
|
+
className: "sr-only",
|
|
209
|
+
children: copy?.state === "copied" ? `Copied ${copy.part}` : copy?.state === "error" ? `Could not copy ${copy.part}. Select it and copy by hand.` : ""
|
|
210
|
+
}),
|
|
211
|
+
onCheck ? /* @__PURE__ */ jsx("div", {
|
|
212
|
+
className: "flex items-center gap-2",
|
|
213
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
214
|
+
variant: "outline",
|
|
215
|
+
size: "sm",
|
|
216
|
+
loading: status === "checking",
|
|
217
|
+
onClick: onCheck,
|
|
218
|
+
children: status === "verified" ? "Check again" : "Check now"
|
|
219
|
+
})
|
|
220
|
+
}) : null
|
|
221
|
+
]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
export { DnsRecord, dnsHostForms };
|
|
226
|
+
|
|
227
|
+
//# sourceMappingURL=dns-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dns-record.js","names":[],"sources":["../../../src/components/dns-record/dns-record.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useEffect,\n useId,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\n\nimport { Button } from \"@/components/button\";\nimport { Spinner } from \"@/components/spinner\";\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * \"Add this record at your DNS provider.\"\n *\n * Vercel, Resend, Cloudflare, Postmark, Google Workspace and every product\n * that verifies a domain or routes its mail draws this card by hand, and\n * they all learn the same three things the hard way.\n *\n * The parts are copied separately. A provider's form has a Name field, a\n * Type field and a Value field, so one button that copies the whole line\n * copies something nobody can paste anywhere.\n *\n * The Name field is a trap. Some providers want the host relative to the\n * zone, `_dmarc`; some want the full name, `_dmarc.acme.com`; and some take\n * the relative form and append the zone themselves, so a full name pasted\n * in becomes `_dmarc.acme.com.acme.com` and the check fails for a reason\n * nobody can see. So the host is shown both ways, with the sentence that\n * explains which to use.\n *\n * A failed check says what was found. \"Not verified\" sends people back to\n * their provider to stare at a record that is correct and has not\n * propagated yet; \"found v=spf1 -all, expected v=spf1 include:…\" sends them\n * to the typo. And nothing found at all is said as nothing found, with how\n * long that can take, because it is not the same as wrong.\n */\n\nexport type DnsRecordStatus = \"pending\" | \"checking\" | \"verified\" | \"failed\";\n\nexport interface DnsHostForms {\n /** Relative to the zone: `_dmarc`, or `@` for the zone itself. */\n relative: string;\n /** Fully qualified: `_dmarc.acme.com`. */\n full: string;\n}\n\n/**\n * The host both ways a provider might want it. Accepts either form and a\n * trailing dot, so the record can be declared however the backend has it.\n */\nexport function dnsHostForms(name: string, zone: string): DnsHostForms {\n const cleanZone = zone.replace(/\\.$/, \"\");\n const cleanName = name.replace(/\\.$/, \"\");\n if (cleanName === \"@\" || cleanName === \"\" || cleanName === cleanZone) {\n return { relative: \"@\", full: cleanZone };\n }\n if (cleanName.endsWith(`.${cleanZone}`)) {\n return { relative: cleanName.slice(0, -cleanZone.length - 1), full: cleanName };\n }\n return { relative: cleanName, full: `${cleanName}.${cleanZone}` };\n}\n\nexport interface DnsRecordProps extends Omit<ComponentPropsWithRef<\"section\">, \"children\"> {\n type: string;\n /** The host, relative or fully qualified. */\n name: string;\n value: string;\n /** The zone the record belongs to. Enables the host to be shown both ways. */\n zone?: string;\n ttl?: number | string;\n priority?: number;\n /** What the record is for, in the reader's language: \"Proves you own acme.com\". */\n purpose?: string;\n status?: DnsRecordStatus;\n /** What the check found instead, when it failed. The useful part. */\n found?: string[];\n checkedAt?: Date;\n locale?: string;\n onCheck?: () => void;\n children?: ReactNode;\n}\n\ntype CopyState = \"idle\" | \"copied\" | \"error\";\n\nexport function DnsRecord({\n className,\n type,\n name,\n value,\n zone,\n ttl,\n priority,\n purpose,\n status = \"pending\",\n found,\n checkedAt,\n locale,\n onCheck,\n children,\n ...props\n}: DnsRecordProps) {\n const id = useId();\n const forms = zone ? dnsHostForms(name, zone) : null;\n const host = forms ? forms.relative : name;\n\n // Which part was copied, and whether it worked, so the button that was\n // pressed is the one that says so.\n const [copy, setCopy] = useState<{ part: string; state: CopyState } | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n },\n [],\n );\n\n const copyPart = (part: string, text: string) => {\n const settle = (state: CopyState) => {\n setCopy({ part, state });\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n setCopy(null);\n }, 2000);\n };\n Promise.resolve()\n .then(() => navigator.clipboard.writeText(text))\n .then(\n () => {\n settle(\"copied\");\n },\n () => {\n settle(\"error\");\n },\n );\n };\n\n const statusText =\n status === \"verified\"\n ? \"Verified\"\n : status === \"checking\"\n ? \"Checking…\"\n : status === \"failed\"\n ? found && found.length > 0\n ? \"Not verified. Something else was found.\"\n : \"Not verified. Nothing found yet — DNS changes can take up to 48 hours to appear.\"\n : \"Not checked yet\";\n\n const checked =\n checkedAt !== undefined\n ? new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeStyle: \"short\" }).format(\n checkedAt,\n )\n : null;\n\n const parts: { key: string; label: string; text: string; note?: ReactNode }[] = [\n { key: \"type\", label: \"Type\", text: type },\n {\n key: \"name\",\n label: \"Name\",\n text: host,\n note: forms ? (\n forms.relative === \"@\" ? (\n <>\n Some providers want <code className=\"font-mono\">{forms.full}</code> or leave it\n blank instead of <code className=\"font-mono\">@</code>.\n </>\n ) : (\n <>\n Some providers want the full name, <code className=\"font-mono\">{forms.full}</code>.\n If the field already ends in <code className=\"font-mono\">{zone}</code>, enter just\n the part shown.\n </>\n )\n ) : undefined,\n },\n {\n key: \"value\",\n label: \"Value\",\n text: value,\n note:\n type.toUpperCase() === \"TXT\"\n ? \"Enter without quotes; your provider adds them if it needs to.\"\n : undefined,\n },\n ...(priority !== undefined\n ? [{ key: \"priority\", label: \"Priority\", text: String(priority) }]\n : []),\n ...(ttl !== undefined ? [{ key: \"ttl\", label: \"TTL\", text: String(ttl) }] : []),\n ];\n\n return (\n <section\n data-slot=\"dns-record\"\n data-status={status}\n aria-labelledby={`${id}-heading`}\n aria-busy={status === \"checking\" || undefined}\n className={cn(\n \"flex flex-col gap-3 rounded-lg border border-border bg-card p-4\",\n className,\n )}\n {...props}\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h3 id={`${id}-heading`} className=\"text-sm font-medium\">\n {type} record\n {purpose ? (\n <span className=\"font-normal text-muted-foreground\"> · {purpose}</span>\n ) : null}\n </h3>\n {/* Status as a word, and announced when it changes: a check was asked\n for, and the answer should arrive where the asker is. */}\n <p\n data-slot=\"dns-record-status\"\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"flex items-center gap-1.5 text-xs\",\n status === \"verified\" && \"text-success\",\n status === \"failed\" && \"text-destructive\",\n (status === \"pending\" || status === \"checking\") && \"text-muted-foreground\",\n )}\n >\n {status === \"checking\" ? <Spinner size=\"sm\" /> : null}\n {statusText}\n {checked && status !== \"checking\" ? (\n <span className=\"text-muted-foreground\"> · checked {checked}</span>\n ) : null}\n </p>\n </div>\n\n {children}\n\n <dl className=\"m-0 grid gap-x-4 gap-y-2 sm:grid-cols-[auto_1fr]\">\n {parts.map((part) => (\n <div key={part.key} className=\"contents\">\n <dt className=\"text-xs font-medium text-muted-foreground sm:pt-1.5\">\n {part.label}\n </dt>\n <dd className=\"m-0 flex flex-col gap-1\">\n <div className=\"flex items-start gap-2\">\n <code\n data-slot=\"dns-record-part\"\n data-part={part.key}\n className=\"min-w-0 flex-1 rounded-md bg-muted px-2 py-1 font-mono text-xs break-all\"\n >\n {part.text}\n </code>\n <button\n type=\"button\"\n aria-label={`Copy ${part.label.toLowerCase()}`}\n data-state={copy?.part === part.key ? copy.state : \"idle\"}\n className={cn(\n \"shrink-0 rounded-md border border-input bg-background px-2 py-1 text-xs font-medium\",\n \"hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n )}\n onClick={() => {\n copyPart(part.key, part.text);\n }}\n >\n {copy?.part === part.key && copy.state === \"copied\" ? \"Copied\" : \"Copy\"}\n </button>\n </div>\n {copy?.part === part.key && copy.state === \"error\" ? (\n <p data-slot=\"dns-record-copy-error\" className=\"text-xs text-destructive\">\n Could not copy. Select it and copy by hand.\n </p>\n ) : null}\n {part.note ? <p className=\"text-xs text-muted-foreground\">{part.note}</p> : null}\n </dd>\n </div>\n ))}\n </dl>\n\n {status === \"failed\" && found && found.length > 0 ? (\n <div\n data-slot=\"dns-record-found\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 p-3 text-xs\"\n >\n <p className=\"font-medium text-destructive\">Found instead:</p>\n <ul className=\"m-0 mt-1 list-none p-0\">\n {found.map((entry) => (\n <li key={entry}>\n <code className=\"font-mono break-all\">{entry}</code>\n </li>\n ))}\n </ul>\n </div>\n ) : null}\n\n {/* A separate region from the verification status, so \"Copied name\"\n never replaces or interrupts a check result that is being read.\n Present from the start, so the first copy is heard. */}\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {copy?.state === \"copied\"\n ? `Copied ${copy.part}`\n : copy?.state === \"error\"\n ? `Could not copy ${copy.part}. Select it and copy by hand.`\n : \"\"}\n </span>\n\n {onCheck ? (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" loading={status === \"checking\"} onClick={onCheck}>\n {status === \"verified\" ? \"Check again\" : \"Check now\"}\n </Button>\n </div>\n ) : null}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/dns-record/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "dns-record",
|
|
5
|
+
title: "DNS Record",
|
|
6
|
+
description: "Add this record at your provider: parts copied separately, checked with what was found.",
|
|
7
|
+
category: "data",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: ["button", "spinner"],
|
|
11
|
+
files: ["dns-record.tsx"],
|
|
12
|
+
a11y: "The card is a region named by the record's type and purpose. Each part is a definition with its own copy button, named by the part it copies, so five Copy buttons are five different buttons. Verification status is a word in a polite live region, so the answer to a check arrives where it was asked for, and the card is aria-busy while checking. Copy results are announced in a separate region, present from the start, so a copy never replaces a check result mid-sentence; failure is announced with what to do instead. What the check found is a list, not a colour."
|
|
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/dns-record/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"dns-record\",\n title: \"DNS Record\",\n description:\n \"Add this record at your provider: parts copied separately, checked with what was found.\",\n category: \"data\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [\"button\", \"spinner\"],\n files: [\"dns-record.tsx\"],\n a11y:\n \"The card is a region named by the record's type and purpose. Each part is a definition \" +\n \"with its own copy button, named by the part it copies, so five Copy buttons are five \" +\n \"different buttons. Verification status is a word in a polite live region, so the answer to \" +\n \"a check arrives where it was asked for, and the card is aria-busy while checking. Copy \" +\n \"results are announced in a separate region, present from the start, so a copy never \" +\n \"replaces a check result mid-sentence; failure is announced with what to do instead. What \" +\n \"the check found is a list, not a colour.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC,UAAU,SAAS;CAC1C,OAAO,CAAC,gBAAgB;CACxB,MACE;AAOJ,CAAC"}
|
|
@@ -107,7 +107,7 @@ function DrawerContent({ className, children, showHandle = true, ...props }) {
|
|
|
107
107
|
function DrawerHeader({ className, ...props }) {
|
|
108
108
|
return /* @__PURE__ */ jsx("div", {
|
|
109
109
|
"data-slot": "drawer-header",
|
|
110
|
-
className: cn("flex shrink-0 flex-col gap-1.5 px-6 pt-4 pb-2 text-
|
|
110
|
+
className: cn("flex shrink-0 flex-col gap-1.5 px-6 pt-4 pb-2 text-start", className),
|
|
111
111
|
...props
|
|
112
112
|
});
|
|
113
113
|
}
|