@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,157 @@
|
|
|
1
|
+
import { Slot } from "radix-ui";
|
|
2
|
+
import type { ComponentPropsWithRef } from "react";
|
|
3
|
+
|
|
4
|
+
import { focusRing, mirrorForDirection } from "@/lib/styles";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where this page sits in the hierarchy.
|
|
9
|
+
*
|
|
10
|
+
* Three things separate a breadcrumb that works from one that only looks like
|
|
11
|
+
* one, and all three are about the last item.
|
|
12
|
+
*
|
|
13
|
+
* It is not a link. A link to the page you are already on is a control that
|
|
14
|
+
* does nothing, and in a screen reader's list of links it is indistinguishable
|
|
15
|
+
* from the ones that go somewhere.
|
|
16
|
+
*
|
|
17
|
+
* It carries `aria-current="page"`. Bolding it says "you are here" to people
|
|
18
|
+
* who can see it and to nobody else.
|
|
19
|
+
*
|
|
20
|
+
* The separators are hidden. A slash between every item is read aloud as
|
|
21
|
+
* "slash" — "Home slash Projects slash Settings" — which is the punctuation of
|
|
22
|
+
* the design leaking into the content.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export function Breadcrumb({ className, ...props }: ComponentPropsWithRef<"nav">) {
|
|
26
|
+
return (
|
|
27
|
+
<nav
|
|
28
|
+
// Named, because a page can have more than one navigation landmark and
|
|
29
|
+
// "navigation" on its own does not say which this is.
|
|
30
|
+
aria-label="Breadcrumb"
|
|
31
|
+
data-slot="breadcrumb"
|
|
32
|
+
className={cn("min-w-0", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function BreadcrumbList({ className, ...props }: ComponentPropsWithRef<"ol">) {
|
|
39
|
+
return (
|
|
40
|
+
<ol
|
|
41
|
+
data-slot="breadcrumb-list"
|
|
42
|
+
className={cn(
|
|
43
|
+
"flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function BreadcrumbItem({ className, ...props }: ComponentPropsWithRef<"li">) {
|
|
52
|
+
return (
|
|
53
|
+
<li
|
|
54
|
+
data-slot="breadcrumb-item"
|
|
55
|
+
className={cn("inline-flex items-center gap-1.5", className)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BreadcrumbLinkProps extends ComponentPropsWithRef<"a"> {
|
|
62
|
+
/** Render the child as the link, for a router's own component. */
|
|
63
|
+
asChild?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function BreadcrumbLink({ className, asChild = false, ...props }: BreadcrumbLinkProps) {
|
|
67
|
+
const Component = asChild ? Slot.Root : "a";
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Component
|
|
71
|
+
data-slot="breadcrumb-link"
|
|
72
|
+
className={cn(
|
|
73
|
+
"rounded-sm transition-colors duration-[var(--duration-fast)]",
|
|
74
|
+
"hover:text-foreground",
|
|
75
|
+
focusRing,
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The page you are on.
|
|
85
|
+
*
|
|
86
|
+
* A span, not a link: there is nowhere to go. `aria-current="page"` is what
|
|
87
|
+
* says "you are here" to anything that is not looking at the font weight.
|
|
88
|
+
*/
|
|
89
|
+
export function BreadcrumbPage({ className, ...props }: ComponentPropsWithRef<"span">) {
|
|
90
|
+
return (
|
|
91
|
+
<span
|
|
92
|
+
data-slot="breadcrumb-page"
|
|
93
|
+
aria-current="page"
|
|
94
|
+
className={cn("font-medium text-foreground", className)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The mark between items.
|
|
102
|
+
*
|
|
103
|
+
* Hidden from assistive technology and given no accessible name: the list
|
|
104
|
+
* structure already conveys the sequence, and reading "slash" between every
|
|
105
|
+
* item is the design's punctuation leaking into the content.
|
|
106
|
+
*/
|
|
107
|
+
export function BreadcrumbSeparator({
|
|
108
|
+
className,
|
|
109
|
+
children,
|
|
110
|
+
...props
|
|
111
|
+
}: ComponentPropsWithRef<"li">) {
|
|
112
|
+
return (
|
|
113
|
+
<li
|
|
114
|
+
data-slot="breadcrumb-separator"
|
|
115
|
+
role="presentation"
|
|
116
|
+
aria-hidden
|
|
117
|
+
className={cn("[&>svg]:size-3.5", className)}
|
|
118
|
+
{...props}
|
|
119
|
+
>
|
|
120
|
+
{children ?? (
|
|
121
|
+
<svg viewBox="0 0 24 24" fill="none" className={mirrorForDirection}>
|
|
122
|
+
<path
|
|
123
|
+
d="m9 18 6-6-6-6"
|
|
124
|
+
stroke="currentColor"
|
|
125
|
+
strokeWidth="2"
|
|
126
|
+
strokeLinecap="round"
|
|
127
|
+
strokeLinejoin="round"
|
|
128
|
+
/>
|
|
129
|
+
</svg>
|
|
130
|
+
)}
|
|
131
|
+
</li>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Stands in for the middle of a long trail.
|
|
137
|
+
*
|
|
138
|
+
* Named rather than hidden, because unlike a separator it is *content*: it
|
|
139
|
+
* says levels have been left out. A bare "…" announces as nothing at all.
|
|
140
|
+
*/
|
|
141
|
+
export function BreadcrumbEllipsis({ className, ...props }: ComponentPropsWithRef<"span">) {
|
|
142
|
+
return (
|
|
143
|
+
<span
|
|
144
|
+
data-slot="breadcrumb-ellipsis"
|
|
145
|
+
role="presentation"
|
|
146
|
+
className={cn("flex size-5 items-center justify-center", className)}
|
|
147
|
+
{...props}
|
|
148
|
+
>
|
|
149
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden className="size-4">
|
|
150
|
+
<circle cx="5" cy="12" r="1.5" fill="currentColor" />
|
|
151
|
+
<circle cx="12" cy="12" r="1.5" fill="currentColor" />
|
|
152
|
+
<circle cx="19" cy="12" r="1.5" fill="currentColor" />
|
|
153
|
+
</svg>
|
|
154
|
+
<span className="sr-only">Intermediate levels</span>
|
|
155
|
+
</span>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "breadcrumb",
|
|
5
|
+
title: "Breadcrumb",
|
|
6
|
+
description: "Where the current page sits in the hierarchy, and how to get back up it.",
|
|
7
|
+
category: "navigation",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: ["radix-ui"],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["breadcrumb.tsx"],
|
|
12
|
+
a11y:
|
|
13
|
+
'The current page is a span carrying aria-current="page", not a link: a link to the page ' +
|
|
14
|
+
"you are already on does nothing, and in a list of links it is indistinguishable from the " +
|
|
15
|
+
"ones that go somewhere. Separators are hidden from assistive technology, because the list " +
|
|
16
|
+
'already conveys the sequence and reading "slash" between every item is the design\'s ' +
|
|
17
|
+
"punctuation leaking into the content. The ellipsis is the exception and is named, because " +
|
|
18
|
+
"it is content: it says levels have been left out.",
|
|
19
|
+
});
|
|
@@ -70,12 +70,12 @@ export function Calendar({
|
|
|
70
70
|
),
|
|
71
71
|
// Range middles keep the muted surface so the two ends stay legible as
|
|
72
72
|
// the actual selection.
|
|
73
|
-
range_start: cn(defaults.range_start, "rounded-
|
|
73
|
+
range_start: cn(defaults.range_start, "rounded-s-md bg-accent"),
|
|
74
74
|
range_middle: cn(
|
|
75
75
|
defaults.range_middle,
|
|
76
76
|
"bg-accent [&>button]:bg-transparent [&>button]:text-accent-foreground",
|
|
77
77
|
),
|
|
78
|
-
range_end: cn(defaults.range_end, "rounded-
|
|
78
|
+
range_end: cn(defaults.range_end, "rounded-e-md bg-accent"),
|
|
79
79
|
today: cn(defaults.today, "[&>button]:font-semibold [&>button]:text-primary"),
|
|
80
80
|
outside: cn(defaults.outside, "text-muted-foreground opacity-50"),
|
|
81
81
|
disabled: cn(defaults.disabled, "opacity-40"),
|
|
@@ -143,7 +143,7 @@ export function CopyButton({
|
|
|
143
143
|
onClick={copy}
|
|
144
144
|
aria-label={label}
|
|
145
145
|
className={cn(
|
|
146
|
-
"
|
|
146
|
+
"ms-auto grid size-6 shrink-0 place-items-center rounded text-muted-foreground",
|
|
147
147
|
"transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground",
|
|
148
148
|
focusRing,
|
|
149
149
|
className,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Collapsible as CollapsiblePrimitive } from "radix-ui";
|
|
4
|
+
import type { ComponentPropsWithRef } from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A section that opens and closes.
|
|
10
|
+
*
|
|
11
|
+
* Unlike Accordion, there is no set: one region, one trigger, no notion of
|
|
12
|
+
* "only one open at a time". Reaching for an Accordion with a single item
|
|
13
|
+
* gives that item a `heading` role and a position in a list of one, both of
|
|
14
|
+
* which are noise.
|
|
15
|
+
*
|
|
16
|
+
* The height animation is driven by the primitive's own measured height rather
|
|
17
|
+
* than a guess, so content of any size opens smoothly, and it derives from
|
|
18
|
+
* `--duration-*` so the motion scale applies.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type CollapsibleProps = ComponentPropsWithRef<typeof CollapsiblePrimitive.Root>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrapped rather than re-exported.
|
|
25
|
+
*
|
|
26
|
+
* A bare `export const Collapsible = CollapsiblePrimitive.Root` leaves the
|
|
27
|
+
* inferred type pointing into the primitive's own package, which TypeScript
|
|
28
|
+
* cannot name in declaration output (TS2883). It also gives every other
|
|
29
|
+
* component's `data-slot` a hole where this one should be.
|
|
30
|
+
*/
|
|
31
|
+
export function Collapsible({ className, ...props }: CollapsibleProps) {
|
|
32
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" className={className} {...props} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function CollapsibleTrigger({
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: ComponentPropsWithRef<typeof CollapsiblePrimitive.Trigger>) {
|
|
39
|
+
return (
|
|
40
|
+
<CollapsiblePrimitive.Trigger
|
|
41
|
+
data-slot="collapsible-trigger"
|
|
42
|
+
className={cn("outline-none", className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function CollapsibleContent({
|
|
49
|
+
className,
|
|
50
|
+
...props
|
|
51
|
+
}: ComponentPropsWithRef<typeof CollapsiblePrimitive.Content>) {
|
|
52
|
+
return (
|
|
53
|
+
<CollapsiblePrimitive.Content
|
|
54
|
+
data-slot="collapsible-content"
|
|
55
|
+
className={cn(
|
|
56
|
+
"overflow-hidden",
|
|
57
|
+
"data-[state=closed]:animate-collapse-up data-[state=open]:animate-collapse-down",
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "collapsible",
|
|
5
|
+
title: "Collapsible",
|
|
6
|
+
description: "A single section that opens and closes, without an accordion's set semantics.",
|
|
7
|
+
category: "layout",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: ["radix-ui"],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["collapsible.tsx"],
|
|
12
|
+
a11y:
|
|
13
|
+
"The trigger carries aria-expanded and points at the region it controls, both from the " +
|
|
14
|
+
"primitive. Reach for this rather than an Accordion of one item: a single-item Accordion " +
|
|
15
|
+
"gives its trigger a heading role and a position in a list of one, neither of which is true.",
|
|
16
|
+
});
|
|
@@ -499,7 +499,7 @@ export function ComboboxItem({
|
|
|
499
499
|
}
|
|
500
500
|
}}
|
|
501
501
|
className={cn(
|
|
502
|
-
"relative flex cursor-default items-center gap-2 rounded-md py-1.5
|
|
502
|
+
"relative flex cursor-default items-center gap-2 rounded-md py-1.5 ps-2 pe-8 text-sm outline-none select-none",
|
|
503
503
|
"transition-colors duration-[var(--duration-instant)]",
|
|
504
504
|
"data-[active]:bg-accent data-[active]:text-accent-foreground",
|
|
505
505
|
"data-[disabled]:pointer-events-none data-[disabled]:opacity-55",
|
|
@@ -510,7 +510,7 @@ export function ComboboxItem({
|
|
|
510
510
|
>
|
|
511
511
|
{children ?? value}
|
|
512
512
|
{selected ? (
|
|
513
|
-
<span className="absolute
|
|
513
|
+
<span className="absolute end-2 grid size-4 place-items-center">
|
|
514
514
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="size-3.5">
|
|
515
515
|
<path
|
|
516
516
|
d="m5 13 4 4L19 7"
|
|
@@ -438,7 +438,7 @@ export function CommandShortcut({ className, ...props }: ComponentPropsWithRef<"
|
|
|
438
438
|
<span
|
|
439
439
|
data-slot="command-shortcut"
|
|
440
440
|
aria-hidden="true"
|
|
441
|
-
className={cn("
|
|
441
|
+
className={cn("ms-auto text-2xs tracking-wide text-muted-foreground", className)}
|
|
442
442
|
{...props}
|
|
443
443
|
/>
|
|
444
444
|
);
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useId,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type ComponentPropsWithRef,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
import { Button } from "@/components/button";
|
|
13
|
+
import { Input } from "@/components/input";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type the name to confirm.
|
|
18
|
+
*
|
|
19
|
+
* The GitHub pattern for deleting a repository, and the one every product
|
|
20
|
+
* copies for the action that cannot be undone: the name of the thing has to
|
|
21
|
+
* pass through the reader's fingers before the button works. Trivial to
|
|
22
|
+
* build, absent from every component library, and usually built wrong in
|
|
23
|
+
* the one place it matters — what happens when the text does not match.
|
|
24
|
+
* The common version disables the button and says nothing, so a keyboard
|
|
25
|
+
* or screen reader user presses it, or presses Enter, and nothing happens
|
|
26
|
+
* at all.
|
|
27
|
+
*
|
|
28
|
+
* Here a mismatch is a thing that is said. The button stays reachable and
|
|
29
|
+
* pressing it, or Enter, before the text matches announces what was
|
|
30
|
+
* expected and marks the field invalid. The moment the text matches, that
|
|
31
|
+
* is announced too, once, so a reader knows the action has become
|
|
32
|
+
* available without watching the button change colour.
|
|
33
|
+
*
|
|
34
|
+
* Pasting is allowed. Blocking it is a popular piece of friction, and it
|
|
35
|
+
* punishes exactly the people who cannot type a long name easily — switch
|
|
36
|
+
* users, voice users, anyone with a tremor — while stopping nobody who can
|
|
37
|
+
* select-all and copy. The name is on screen either way; the point is that
|
|
38
|
+
* it passed through the reader's attention, not their keyboard.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export interface ConfirmTypedProps extends Omit<ComponentPropsWithRef<"div">, "children"> {
|
|
42
|
+
/** The text that must be typed, exactly. */
|
|
43
|
+
expected: string;
|
|
44
|
+
/** What confirming does, as the button reads: "Delete project". */
|
|
45
|
+
action: string;
|
|
46
|
+
onConfirm: () => void;
|
|
47
|
+
/** While the action runs. The button stays focusable and says so. */
|
|
48
|
+
pending?: boolean;
|
|
49
|
+
/** Match regardless of case. Off by default: a name is a name. */
|
|
50
|
+
caseSensitive?: boolean;
|
|
51
|
+
variant?: "destructive" | "primary";
|
|
52
|
+
/** The typed text. Controlled, if you want it. */
|
|
53
|
+
value?: string;
|
|
54
|
+
onValueChange?: (value: string) => void;
|
|
55
|
+
/** The consequence, in a sentence or two, before the field. */
|
|
56
|
+
children?: ReactNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ConfirmTyped({
|
|
60
|
+
className,
|
|
61
|
+
expected,
|
|
62
|
+
action,
|
|
63
|
+
onConfirm,
|
|
64
|
+
pending = false,
|
|
65
|
+
caseSensitive = true,
|
|
66
|
+
variant = "destructive",
|
|
67
|
+
value: valueProp,
|
|
68
|
+
onValueChange,
|
|
69
|
+
children,
|
|
70
|
+
...props
|
|
71
|
+
}: ConfirmTypedProps) {
|
|
72
|
+
const id = useId();
|
|
73
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
74
|
+
|
|
75
|
+
const [uncontrolled, setUncontrolled] = useState("");
|
|
76
|
+
const value = valueProp ?? uncontrolled;
|
|
77
|
+
|
|
78
|
+
const [attempted, setAttempted] = useState(false);
|
|
79
|
+
const [announcement, setAnnouncement] = useState("");
|
|
80
|
+
|
|
81
|
+
// Surrounding whitespace never decides it: a trailing space is not a
|
|
82
|
+
// different name, and a reader cannot see it to know why it failed.
|
|
83
|
+
const normalise = (text: string) => (caseSensitive ? text.trim() : text.trim().toLowerCase());
|
|
84
|
+
const matched = value.length > 0 && normalise(value) === normalise(expected);
|
|
85
|
+
|
|
86
|
+
// Announce the match once, on the transition. Adjusted during render so the
|
|
87
|
+
// announcement lands in the same commit as the state it describes.
|
|
88
|
+
const [wasMatched, setWasMatched] = useState(matched);
|
|
89
|
+
if (wasMatched !== matched) {
|
|
90
|
+
setWasMatched(matched);
|
|
91
|
+
if (matched) {
|
|
92
|
+
setAnnouncement(`Matches. ${action} is available.`);
|
|
93
|
+
setAttempted(false);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const setValue = (next: string) => {
|
|
98
|
+
setUncontrolled(next);
|
|
99
|
+
onValueChange?.(next);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const attempt = () => {
|
|
103
|
+
if (pending) return;
|
|
104
|
+
if (matched) {
|
|
105
|
+
onConfirm();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Said, not swallowed. A press that does nothing tells a keyboard user
|
|
109
|
+
// nothing; this one tells them what was expected.
|
|
110
|
+
setAttempted(true);
|
|
111
|
+
setAnnouncement(`Does not match. Type ${expected} exactly.`);
|
|
112
|
+
inputRef.current?.focus();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
116
|
+
if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
attempt();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const mismatch = attempted && !matched;
|
|
122
|
+
const hint = matched
|
|
123
|
+
? "Matches."
|
|
124
|
+
: mismatch
|
|
125
|
+
? `Does not match. Type ${expected} exactly.`
|
|
126
|
+
: caseSensitive
|
|
127
|
+
? "Type it exactly as shown."
|
|
128
|
+
: "Type it as shown. Case does not matter.";
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div
|
|
132
|
+
data-slot="confirm-typed"
|
|
133
|
+
data-state={matched ? "matched" : mismatch ? "mismatched" : "idle"}
|
|
134
|
+
className={cn("flex flex-col gap-2", className)}
|
|
135
|
+
{...props}
|
|
136
|
+
>
|
|
137
|
+
{children}
|
|
138
|
+
|
|
139
|
+
<label htmlFor={`${id}-input`} className="text-sm">
|
|
140
|
+
To confirm, type{" "}
|
|
141
|
+
<strong className="rounded bg-muted px-1 py-0.5 font-mono text-xs font-medium select-all">
|
|
142
|
+
{expected}
|
|
143
|
+
</strong>
|
|
144
|
+
</label>
|
|
145
|
+
|
|
146
|
+
<Input
|
|
147
|
+
ref={inputRef}
|
|
148
|
+
id={`${id}-input`}
|
|
149
|
+
value={value}
|
|
150
|
+
autoComplete="off"
|
|
151
|
+
autoCapitalize="off"
|
|
152
|
+
spellCheck={false}
|
|
153
|
+
aria-describedby={`${id}-hint`}
|
|
154
|
+
aria-invalid={mismatch || undefined}
|
|
155
|
+
onChange={(event) => {
|
|
156
|
+
setValue(event.target.value);
|
|
157
|
+
}}
|
|
158
|
+
onKeyDown={onKeyDown}
|
|
159
|
+
/>
|
|
160
|
+
|
|
161
|
+
<p
|
|
162
|
+
id={`${id}-hint`}
|
|
163
|
+
data-slot="confirm-typed-hint"
|
|
164
|
+
className={cn(
|
|
165
|
+
"text-xs",
|
|
166
|
+
mismatch ? "text-destructive" : matched ? "text-success" : "text-muted-foreground",
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
{hint}
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
{/* Present from the first render, so the first announcement is heard. */}
|
|
173
|
+
<span role="status" aria-live="polite" className="sr-only">
|
|
174
|
+
{announcement}
|
|
175
|
+
</span>
|
|
176
|
+
|
|
177
|
+
<Button
|
|
178
|
+
variant={variant}
|
|
179
|
+
loading={pending}
|
|
180
|
+
data-ready={matched || undefined}
|
|
181
|
+
aria-describedby={matched ? undefined : `${id}-hint`}
|
|
182
|
+
// Dimmed rather than disabled while the text does not match: it stays
|
|
183
|
+
// reachable, and pressing it says why nothing happened.
|
|
184
|
+
className={cn("self-start", !matched && !pending && "opacity-55")}
|
|
185
|
+
onClick={attempt}
|
|
186
|
+
>
|
|
187
|
+
{pending ? `${action}…` : action}
|
|
188
|
+
</Button>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ConfirmTyped, type ConfirmTypedProps } from "./confirm-typed";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "confirm-typed",
|
|
5
|
+
title: "Confirm Typed",
|
|
6
|
+
description: "Type the name to confirm — and be told, not ignored, when it does not match.",
|
|
7
|
+
category: "form",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: ["button", "input"],
|
|
11
|
+
files: ["confirm-typed.tsx"],
|
|
12
|
+
a11y:
|
|
13
|
+
"The button stays reachable while the text does not match, dimmed rather than disabled, and " +
|
|
14
|
+
"pressing it or Enter announces what was expected, marks the field invalid and returns focus " +
|
|
15
|
+
"to it — the common version disables the button and says nothing, so a keyboard user cannot " +
|
|
16
|
+
"tell whether anything happened. The match is announced once, on the transition, naming the " +
|
|
17
|
+
"action that became available, so nobody has to watch the button change colour. Typing " +
|
|
18
|
+
"itself stays silent, because a verdict on every keystroke is noise. The hint is the field's " +
|
|
19
|
+
"description and the button's, and the status region exists from the first render so the " +
|
|
20
|
+
"first announcement is heard. Pasting is allowed: blocking it punishes people who cannot " +
|
|
21
|
+
"type a long name easily and stops nobody who can select-all and copy.",
|
|
22
|
+
});
|