@dowel-ui/react 0.1.1 → 0.3.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.
Files changed (142) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +193 -0
  3. package/dist/components/ai-action-ledger/ai-action-ledger.d.ts +77 -0
  4. package/dist/components/ai-action-ledger/ai-action-ledger.d.ts.map +1 -0
  5. package/dist/components/ai-action-ledger/ai-action-ledger.js +224 -0
  6. package/dist/components/ai-action-ledger/ai-action-ledger.js.map +1 -0
  7. package/dist/components/ai-action-ledger/index.d.ts +2 -0
  8. package/dist/components/ai-action-ledger/index.js +2 -0
  9. package/dist/components/ai-action-ledger/meta.js +17 -0
  10. package/dist/components/ai-action-ledger/meta.js.map +1 -0
  11. package/dist/components/ai-agent-plan/ai-agent-plan.d.ts +50 -0
  12. package/dist/components/ai-agent-plan/ai-agent-plan.d.ts.map +1 -0
  13. package/dist/components/ai-agent-plan/ai-agent-plan.js +156 -0
  14. package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -0
  15. package/dist/components/ai-agent-plan/index.d.ts +2 -0
  16. package/dist/components/ai-agent-plan/index.js +2 -0
  17. package/dist/components/ai-agent-plan/meta.js +17 -0
  18. package/dist/components/ai-agent-plan/meta.js.map +1 -0
  19. package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
  20. package/dist/components/ai-approval-request/ai-approval-request.d.ts +67 -0
  21. package/dist/components/ai-approval-request/ai-approval-request.d.ts.map +1 -0
  22. package/dist/components/ai-approval-request/ai-approval-request.js +218 -0
  23. package/dist/components/ai-approval-request/ai-approval-request.js.map +1 -0
  24. package/dist/components/ai-approval-request/index.d.ts +2 -0
  25. package/dist/components/ai-approval-request/index.js +2 -0
  26. package/dist/components/ai-approval-request/meta.js +17 -0
  27. package/dist/components/ai-approval-request/meta.js.map +1 -0
  28. package/dist/components/ai-inline-completion/ai-inline-completion.d.ts +45 -0
  29. package/dist/components/ai-inline-completion/ai-inline-completion.d.ts.map +1 -0
  30. package/dist/components/ai-inline-completion/ai-inline-completion.js +110 -0
  31. package/dist/components/ai-inline-completion/ai-inline-completion.js.map +1 -0
  32. package/dist/components/ai-inline-completion/index.d.ts +2 -0
  33. package/dist/components/ai-inline-completion/index.js +2 -0
  34. package/dist/components/ai-inline-completion/meta.js +17 -0
  35. package/dist/components/ai-inline-completion/meta.js.map +1 -0
  36. package/dist/components/ai-response/ai-response.d.ts.map +1 -1
  37. package/dist/components/ai-response/ai-response.js +1 -0
  38. package/dist/components/ai-response/ai-response.js.map +1 -1
  39. package/dist/components/ai-structured-output/ai-structured-output.d.ts +75 -0
  40. package/dist/components/ai-structured-output/ai-structured-output.d.ts.map +1 -0
  41. package/dist/components/ai-structured-output/ai-structured-output.js +137 -0
  42. package/dist/components/ai-structured-output/ai-structured-output.js.map +1 -0
  43. package/dist/components/ai-structured-output/index.d.ts +2 -0
  44. package/dist/components/ai-structured-output/index.js +2 -0
  45. package/dist/components/ai-structured-output/meta.js +17 -0
  46. package/dist/components/ai-structured-output/meta.js.map +1 -0
  47. package/dist/components/ai-tool/ai-tool.d.ts +1 -1
  48. package/dist/components/alert/alert.d.ts +1 -1
  49. package/dist/components/badge/badge.d.ts +1 -1
  50. package/dist/components/button/button.d.ts +1 -1
  51. package/dist/components/file-upload/file-upload.d.ts +46 -0
  52. package/dist/components/file-upload/file-upload.d.ts.map +1 -0
  53. package/dist/components/file-upload/file-upload.js +204 -0
  54. package/dist/components/file-upload/file-upload.js.map +1 -0
  55. package/dist/components/file-upload/index.d.ts +3 -0
  56. package/dist/components/file-upload/index.js +3 -0
  57. package/dist/components/file-upload/meta.js +17 -0
  58. package/dist/components/file-upload/meta.js.map +1 -0
  59. package/dist/components/file-upload/upload-queue.d.ts +87 -0
  60. package/dist/components/file-upload/upload-queue.d.ts.map +1 -0
  61. package/dist/components/file-upload/upload-queue.js +249 -0
  62. package/dist/components/file-upload/upload-queue.js.map +1 -0
  63. package/dist/components/meter/index.d.ts +2 -0
  64. package/dist/components/meter/index.js +2 -0
  65. package/dist/components/meter/meta.js +17 -0
  66. package/dist/components/meter/meta.js.map +1 -0
  67. package/dist/components/meter/meter.d.ts +69 -0
  68. package/dist/components/meter/meter.d.ts.map +1 -0
  69. package/dist/components/meter/meter.js +144 -0
  70. package/dist/components/meter/meter.js.map +1 -0
  71. package/dist/components/metric-delta/index.d.ts +2 -0
  72. package/dist/components/metric-delta/index.js +2 -0
  73. package/dist/components/metric-delta/meta.js +17 -0
  74. package/dist/components/metric-delta/meta.js.map +1 -0
  75. package/dist/components/metric-delta/metric-delta.d.ts +59 -0
  76. package/dist/components/metric-delta/metric-delta.d.ts.map +1 -0
  77. package/dist/components/metric-delta/metric-delta.js +82 -0
  78. package/dist/components/metric-delta/metric-delta.js.map +1 -0
  79. package/dist/components/progress/progress.d.ts +1 -1
  80. package/dist/components/progress/progress.js +1 -0
  81. package/dist/components/progress/progress.js.map +1 -1
  82. package/dist/components/record-diff/index.d.ts +2 -0
  83. package/dist/components/record-diff/index.js +2 -0
  84. package/dist/components/record-diff/meta.js +17 -0
  85. package/dist/components/record-diff/meta.js.map +1 -0
  86. package/dist/components/record-diff/record-diff.d.ts +56 -0
  87. package/dist/components/record-diff/record-diff.d.ts.map +1 -0
  88. package/dist/components/record-diff/record-diff.js +199 -0
  89. package/dist/components/record-diff/record-diff.js.map +1 -0
  90. package/dist/components/spinner/spinner.js +1 -0
  91. package/dist/components/spinner/spinner.js.map +1 -1
  92. package/dist/components/tags-input/index.d.ts +2 -0
  93. package/dist/components/tags-input/index.js +2 -0
  94. package/dist/components/tags-input/meta.js +17 -0
  95. package/dist/components/tags-input/meta.js.map +1 -0
  96. package/dist/components/tags-input/tags-input.d.ts +47 -0
  97. package/dist/components/tags-input/tags-input.d.ts.map +1 -0
  98. package/dist/components/tags-input/tags-input.js +182 -0
  99. package/dist/components/tags-input/tags-input.js.map +1 -0
  100. package/dist/components/toast/toast.d.ts +1 -1
  101. package/dist/index.d.ts +22 -1
  102. package/dist/index.js +12 -1
  103. package/dist/registry/components.d.ts.map +1 -1
  104. package/dist/registry/components.js +65 -45
  105. package/dist/registry/components.js.map +1 -1
  106. package/package.json +36 -4
  107. package/src/components/ai-action-ledger/ai-action-ledger.tsx +381 -0
  108. package/src/components/ai-action-ledger/index.ts +14 -0
  109. package/src/components/ai-action-ledger/meta.ts +19 -0
  110. package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
  111. package/src/components/ai-agent-plan/index.ts +10 -0
  112. package/src/components/ai-agent-plan/meta.ts +19 -0
  113. package/src/components/ai-approval-request/ai-approval-request.tsx +368 -0
  114. package/src/components/ai-approval-request/index.ts +7 -0
  115. package/src/components/ai-approval-request/meta.ts +21 -0
  116. package/src/components/ai-inline-completion/ai-inline-completion.tsx +214 -0
  117. package/src/components/ai-inline-completion/index.ts +1 -0
  118. package/src/components/ai-inline-completion/meta.ts +20 -0
  119. package/src/components/ai-response/ai-response.tsx +1 -0
  120. package/src/components/ai-structured-output/ai-structured-output.tsx +268 -0
  121. package/src/components/ai-structured-output/index.ts +10 -0
  122. package/src/components/ai-structured-output/meta.ts +18 -0
  123. package/src/components/file-upload/file-upload.tsx +322 -0
  124. package/src/components/file-upload/index.ts +20 -0
  125. package/src/components/file-upload/meta.ts +21 -0
  126. package/src/components/file-upload/upload-queue.ts +337 -0
  127. package/src/components/meter/index.ts +8 -0
  128. package/src/components/meter/meta.ts +20 -0
  129. package/src/components/meter/meter.tsx +219 -0
  130. package/src/components/metric-delta/index.ts +6 -0
  131. package/src/components/metric-delta/meta.ts +18 -0
  132. package/src/components/metric-delta/metric-delta.tsx +180 -0
  133. package/src/components/progress/progress.tsx +3 -0
  134. package/src/components/record-diff/index.ts +10 -0
  135. package/src/components/record-diff/meta.ts +19 -0
  136. package/src/components/record-diff/record-diff.tsx +341 -0
  137. package/src/components/spinner/spinner.tsx +5 -0
  138. package/src/components/tags-input/index.ts +1 -0
  139. package/src/components/tags-input/meta.ts +23 -0
  140. package/src/components/tags-input/tags-input.tsx +300 -0
  141. package/src/index.ts +10 -0
  142. package/src/registry/components.ts +20 -0
@@ -0,0 +1,199 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { createContext, useContext, useMemo, useState } from "react";
5
+ //#region src/components/record-diff/record-diff.tsx
6
+ /** Values that read as absent, so "" → null is not reported as a change. */
7
+ function isEmpty(value) {
8
+ return value === null || value === void 0 || value === "";
9
+ }
10
+ function classify(before, after) {
11
+ const hadBefore = !isEmpty(before);
12
+ const hasAfter = !isEmpty(after);
13
+ if (!hadBefore && hasAfter) return "added";
14
+ if (hadBefore && !hasAfter) return "removed";
15
+ if (!hadBefore && !hasAfter) return "unchanged";
16
+ return stableStringify(before) === stableStringify(after) ? "unchanged" : "changed";
17
+ }
18
+ function stableStringify(value) {
19
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "";
20
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
21
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${stableStringify(v)}`).join(",")}}`;
22
+ }
23
+ function defaultFormatValue(value) {
24
+ if (value === null || value === void 0) return "—";
25
+ if (value === "") return "empty";
26
+ if (typeof value === "string") return value;
27
+ if (typeof value === "boolean") return value ? "yes" : "no";
28
+ if (typeof value === "number" || typeof value === "bigint") return String(value);
29
+ if (Array.isArray(value)) return value.length === 0 ? "none" : value.map(defaultFormatValue).join(", ");
30
+ if (typeof value === "object") return JSON.stringify(value);
31
+ return `[${typeof value}]`;
32
+ }
33
+ /** Keys whose values must never be rendered, however the record was built. */
34
+ const DEFAULT_REDACT = /password|secret|token|api[-_]?key|authorization|credential/i;
35
+ const RecordDiffContext = createContext(null);
36
+ function useRecordDiffContext(component) {
37
+ const context = useContext(RecordDiffContext);
38
+ if (!context) throw new Error(`${component} must be rendered inside <RecordDiff>.`);
39
+ return context;
40
+ }
41
+ /** "billingEmail" and "billing_email" both read as "Billing email". */
42
+ function humanise(key) {
43
+ const spaced = key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
44
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
45
+ }
46
+ function diffRecords(before, after, options = {}) {
47
+ const { labels = {}, redact = DEFAULT_REDACT, fields } = options;
48
+ return (fields ?? [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])]).map((key) => ({
49
+ key,
50
+ label: labels[key] ?? humanise(key),
51
+ change: classify(before[key], after[key]),
52
+ before: before[key],
53
+ after: after[key],
54
+ redacted: redact.test(key)
55
+ }));
56
+ }
57
+ const CHANGE_STYLES = {
58
+ added: "text-success",
59
+ removed: "text-destructive",
60
+ changed: "text-foreground",
61
+ unchanged: "text-muted-foreground"
62
+ };
63
+ function RecordDiff({ className, before, after, labels, redact = DEFAULT_REDACT, fields: fieldKeys, collapseUnchanged = true, formatValue = defaultFormatValue, children, ...props }) {
64
+ const fields = useMemo(() => diffRecords(before, after, {
65
+ labels,
66
+ redact,
67
+ fields: fieldKeys
68
+ }), [
69
+ before,
70
+ after,
71
+ labels,
72
+ redact,
73
+ fieldKeys
74
+ ]);
75
+ const changedCount = fields.filter((field) => field.change !== "unchanged").length;
76
+ const context = useMemo(() => ({
77
+ fields,
78
+ changedCount,
79
+ formatValue
80
+ }), [
81
+ fields,
82
+ changedCount,
83
+ formatValue
84
+ ]);
85
+ return /* @__PURE__ */ jsx(RecordDiffContext.Provider, {
86
+ value: context,
87
+ children: /* @__PURE__ */ jsx("div", {
88
+ "data-slot": "record-diff",
89
+ "data-changed": changedCount,
90
+ className: cn("flex flex-col gap-2", className),
91
+ ...props,
92
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(RecordDiffSummary, {}), /* @__PURE__ */ jsx(RecordDiffTable, { collapseUnchanged })] })
93
+ })
94
+ });
95
+ }
96
+ /** "3 fields changed" — the thing a reviewer scanning a log needs first. */
97
+ function RecordDiffSummary({ className, ...props }) {
98
+ const { changedCount, fields } = useRecordDiffContext("RecordDiffSummary");
99
+ return /* @__PURE__ */ jsx("p", {
100
+ "data-slot": "record-diff-summary",
101
+ className: cn("text-xs text-muted-foreground", className),
102
+ ...props,
103
+ children: changedCount === 0 ? "No fields changed" : `${String(changedCount)} of ${String(fields.length)} field${fields.length === 1 ? "" : "s"} changed`
104
+ });
105
+ }
106
+ function RecordDiffTable({ className, collapseUnchanged = true, ...props }) {
107
+ const { fields, formatValue, changedCount } = useRecordDiffContext("RecordDiffTable");
108
+ const [showUnchanged, setShowUnchanged] = useState(!collapseUnchanged);
109
+ const unchangedCount = fields.length - changedCount;
110
+ const visible = showUnchanged ? fields : fields.filter((field) => field.change !== "unchanged");
111
+ return /* @__PURE__ */ jsxs("div", {
112
+ className: "flex flex-col gap-2",
113
+ children: [/* @__PURE__ */ jsx("div", {
114
+ className: "overflow-x-auto",
115
+ children: /* @__PURE__ */ jsxs("table", {
116
+ "data-slot": "record-diff-table",
117
+ className: cn("w-full border-collapse text-sm", className),
118
+ ...props,
119
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
120
+ className: "border-b border-border",
121
+ children: [
122
+ /* @__PURE__ */ jsx("th", {
123
+ scope: "col",
124
+ className: "px-3 py-2 text-left text-xs font-medium text-muted-foreground",
125
+ children: "Field"
126
+ }),
127
+ /* @__PURE__ */ jsx("th", {
128
+ scope: "col",
129
+ className: "px-3 py-2 text-left text-xs font-medium text-muted-foreground",
130
+ children: "Before"
131
+ }),
132
+ /* @__PURE__ */ jsx("th", {
133
+ scope: "col",
134
+ className: "px-3 py-2 text-left text-xs font-medium text-muted-foreground",
135
+ children: "After"
136
+ })
137
+ ]
138
+ }) }), /* @__PURE__ */ jsx("tbody", { children: visible.map((field) => /* @__PURE__ */ jsxs("tr", {
139
+ "data-slot": "record-diff-row",
140
+ "data-change": field.change,
141
+ className: "border-b border-border last:border-0",
142
+ children: [
143
+ /* @__PURE__ */ jsxs("th", {
144
+ scope: "row",
145
+ className: "px-3 py-2 text-left align-top font-medium whitespace-nowrap",
146
+ children: [field.label, /* @__PURE__ */ jsx("span", {
147
+ className: "sr-only",
148
+ children: `, ${field.change}`
149
+ })]
150
+ }),
151
+ /* @__PURE__ */ jsx("td", {
152
+ className: "px-3 py-2 align-top text-muted-foreground",
153
+ children: field.redacted ? /* @__PURE__ */ jsx(RedactedValue, {}) : field.change === "added" ? /* @__PURE__ */ jsx("span", {
154
+ className: "text-muted-foreground",
155
+ children: "—"
156
+ }) : /* @__PURE__ */ jsx("span", {
157
+ className: "line-through decoration-muted-foreground/50",
158
+ children: formatValue(field.before, field.key)
159
+ })
160
+ }),
161
+ /* @__PURE__ */ jsx("td", {
162
+ className: cn("px-3 py-2 align-top", CHANGE_STYLES[field.change]),
163
+ children: field.redacted ? /* @__PURE__ */ jsx(RedactedValue, {}) : field.change === "removed" ? /* @__PURE__ */ jsx("span", {
164
+ className: "text-muted-foreground",
165
+ children: "—"
166
+ }) : formatValue(field.after, field.key)
167
+ })
168
+ ]
169
+ }, field.key)) })]
170
+ })
171
+ }), collapseUnchanged && unchangedCount > 0 ? /* @__PURE__ */ jsx("button", {
172
+ type: "button",
173
+ "data-slot": "record-diff-toggle",
174
+ "aria-expanded": showUnchanged,
175
+ onClick: () => {
176
+ setShowUnchanged((shown) => !shown);
177
+ },
178
+ className: cn("self-start rounded-md px-1.5 py-0.5 text-xs text-muted-foreground", "transition-colors hover:text-foreground", "outline-none focus-visible:ring-2 focus-visible:ring-ring/55"),
179
+ children: showUnchanged ? `Hide ${String(unchangedCount)} unchanged` : `Show ${String(unchangedCount)} unchanged`
180
+ }) : null]
181
+ });
182
+ }
183
+ /**
184
+ * A value that is never rendered.
185
+ *
186
+ * Redaction happens before formatting, so a secret is not passed to a formatter
187
+ * that might log or transform it — the value simply does not reach the DOM.
188
+ */
189
+ function RedactedValue() {
190
+ return /* @__PURE__ */ jsx("span", {
191
+ "data-slot": "record-diff-redacted",
192
+ className: "text-muted-foreground italic",
193
+ children: "redacted"
194
+ });
195
+ }
196
+ //#endregion
197
+ export { RecordDiff, RecordDiffSummary, RecordDiffTable, diffRecords };
198
+
199
+ //# sourceMappingURL=record-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-diff.js","names":[],"sources":["../../../src/components/record-diff/record-diff.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useMemo,\n useState,\n type ComponentPropsWithRef,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * What changed about a record, field by field.\n *\n * Every diff package on npm compares lines of text. That is the wrong shape for\n * an audit entry, a settings revision or a config history, where the change is\n * \"role went from viewer to admin\" and rendering it as two lines of JSON makes\n * the reader do the comparison themselves.\n *\n * Scope is deliberately flat records. Nested objects and arrays are formatted as\n * values rather than recursed into, because a recursive differ is a different\n * and much larger component, and pretending otherwise would produce a shallow\n * one that quietly lies about deep changes. `formatValue` is the escape hatch.\n *\n * Redaction is by key pattern and applied before formatting, so a secret is\n * never handed to a formatter that might log it.\n */\n\nexport type FieldChange = \"added\" | \"removed\" | \"changed\" | \"unchanged\";\n\nexport interface RecordField {\n key: string;\n label: string;\n change: FieldChange;\n before: unknown;\n after: unknown;\n redacted: boolean;\n}\n\n/** Values that read as absent, so \"\" → null is not reported as a change. */\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined || value === \"\";\n}\n\nfunction classify(before: unknown, after: unknown): FieldChange {\n const hadBefore = !isEmpty(before);\n const hasAfter = !isEmpty(after);\n\n if (!hadBefore && hasAfter) return \"added\";\n if (hadBefore && !hasAfter) return \"removed\";\n // Both absent. Falling through would compare \"\" against null, which\n // serialise differently and would report a change that did not happen.\n if (!hadBefore && !hasAfter) return \"unchanged\";\n // Structural comparison, so {a:1} and {a:1} are equal. Key order differences\n // would report a false change, so keys are sorted before comparing.\n return stableStringify(before) === stableStringify(after) ? \"unchanged\" : \"changed\";\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"\";\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) =>\n a.localeCompare(b),\n );\n return `{${entries.map(([k, v]) => `${k}:${stableStringify(v)}`).join(\",\")}}`;\n}\n\nfunction defaultFormatValue(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (value === \"\") return \"empty\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"boolean\") return value ? \"yes\" : \"no\";\n if (typeof value === \"number\" || typeof value === \"bigint\") return String(value);\n if (Array.isArray(value)) {\n return value.length === 0 ? \"none\" : value.map(defaultFormatValue).join(\", \");\n }\n if (typeof value === \"object\") return JSON.stringify(value);\n // Symbols and functions have no sensible representation as a field value, and\n // String() would yield \"[object Object]\" or a function body. Say what it is.\n return `[${typeof value}]`;\n}\n\n/** Keys whose values must never be rendered, however the record was built. */\nconst DEFAULT_REDACT = /password|secret|token|api[-_]?key|authorization|credential/i;\n\nexport interface RecordDiffProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n before: Record<string, unknown>;\n after: Record<string, unknown>;\n /** Overrides the humanised key. */\n labels?: Record<string, string>;\n /** Keys to render as redacted. Defaults to common secret-shaped names. */\n redact?: RegExp;\n /** Only these keys, in this order. Anything else is ignored. */\n fields?: string[];\n /** Hides unchanged fields behind a toggle. */\n collapseUnchanged?: boolean;\n formatValue?: (value: unknown, key: string) => string;\n children?: React.ReactNode;\n}\n\ninterface RecordDiffContextValue {\n fields: RecordField[];\n changedCount: number;\n formatValue: (value: unknown, key: string) => string;\n}\n\nconst RecordDiffContext = createContext<RecordDiffContextValue | null>(null);\n\nfunction useRecordDiffContext(component: string): RecordDiffContextValue {\n const context = useContext(RecordDiffContext);\n if (!context) {\n throw new Error(`${component} must be rendered inside <RecordDiff>.`);\n }\n return context;\n}\n\n/** \"billingEmail\" and \"billing_email\" both read as \"Billing email\". */\nfunction humanise(key: string): string {\n const spaced = key\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();\n}\n\nexport function diffRecords(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n options: { labels?: Record<string, string>; redact?: RegExp; fields?: string[] } = {},\n): RecordField[] {\n const { labels = {}, redact = DEFAULT_REDACT, fields } = options;\n\n // The union of both sides, so a removed key is still reported. Iterating only\n // `after` is how removals go missing from audit logs.\n const keys = fields ?? [...new Set([...Object.keys(before), ...Object.keys(after)])];\n\n return keys.map((key) => ({\n key,\n label: labels[key] ?? humanise(key),\n change: classify(before[key], after[key]),\n before: before[key],\n after: after[key],\n redacted: redact.test(key),\n }));\n}\n\nconst CHANGE_STYLES: Record<FieldChange, string> = {\n added: \"text-success\",\n removed: \"text-destructive\",\n changed: \"text-foreground\",\n unchanged: \"text-muted-foreground\",\n};\n\nexport function RecordDiff({\n className,\n before,\n after,\n labels,\n redact = DEFAULT_REDACT,\n fields: fieldKeys,\n collapseUnchanged = true,\n formatValue = defaultFormatValue,\n children,\n ...props\n}: RecordDiffProps) {\n const fields = useMemo(\n () => diffRecords(before, after, { labels, redact, fields: fieldKeys }),\n [before, after, labels, redact, fieldKeys],\n );\n\n const changedCount = fields.filter((field) => field.change !== \"unchanged\").length;\n\n const context = useMemo<RecordDiffContextValue>(\n () => ({ fields, changedCount, formatValue }),\n [fields, changedCount, formatValue],\n );\n\n return (\n <RecordDiffContext.Provider value={context}>\n <div\n data-slot=\"record-diff\"\n data-changed={changedCount}\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n >\n {children ?? (\n <>\n <RecordDiffSummary />\n <RecordDiffTable collapseUnchanged={collapseUnchanged} />\n </>\n )}\n </div>\n </RecordDiffContext.Provider>\n );\n}\n\n/** \"3 fields changed\" — the thing a reviewer scanning a log needs first. */\nexport function RecordDiffSummary({ className, ...props }: ComponentPropsWithRef<\"p\">) {\n const { changedCount, fields } = useRecordDiffContext(\"RecordDiffSummary\");\n\n return (\n <p\n data-slot=\"record-diff-summary\"\n className={cn(\"text-xs text-muted-foreground\", className)}\n {...props}\n >\n {changedCount === 0\n ? \"No fields changed\"\n : `${String(changedCount)} of ${String(fields.length)} field${fields.length === 1 ? \"\" : \"s\"} changed`}\n </p>\n );\n}\n\nexport interface RecordDiffTableProps extends ComponentPropsWithRef<\"table\"> {\n collapseUnchanged?: boolean;\n}\n\nexport function RecordDiffTable({\n className,\n collapseUnchanged = true,\n ...props\n}: RecordDiffTableProps) {\n const { fields, formatValue, changedCount } = useRecordDiffContext(\"RecordDiffTable\");\n const [showUnchanged, setShowUnchanged] = useState(!collapseUnchanged);\n\n const unchangedCount = fields.length - changedCount;\n const visible = showUnchanged\n ? fields\n : fields.filter((field) => field.change !== \"unchanged\");\n\n return (\n <div className=\"flex flex-col gap-2\">\n <div className=\"overflow-x-auto\">\n <table\n data-slot=\"record-diff-table\"\n className={cn(\"w-full border-collapse text-sm\", className)}\n {...props}\n >\n <thead>\n <tr className=\"border-b border-border\">\n <th\n scope=\"col\"\n className=\"px-3 py-2 text-left text-xs font-medium text-muted-foreground\"\n >\n Field\n </th>\n <th\n scope=\"col\"\n className=\"px-3 py-2 text-left text-xs font-medium text-muted-foreground\"\n >\n Before\n </th>\n <th\n scope=\"col\"\n className=\"px-3 py-2 text-left text-xs font-medium text-muted-foreground\"\n >\n After\n </th>\n </tr>\n </thead>\n <tbody>\n {visible.map((field) => (\n <tr\n key={field.key}\n data-slot=\"record-diff-row\"\n data-change={field.change}\n className=\"border-b border-border last:border-0\"\n >\n {/* scope=\"row\" so each value is announced with its field name\n rather than as a loose cell in a wall of table. */}\n <th\n scope=\"row\"\n className=\"px-3 py-2 text-left align-top font-medium whitespace-nowrap\"\n >\n {field.label}\n {/* The change kind must not be carried by row colour alone. */}\n <span className=\"sr-only\">{`, ${field.change}`}</span>\n </th>\n <td className=\"px-3 py-2 align-top text-muted-foreground\">\n {field.redacted ? (\n <RedactedValue />\n ) : field.change === \"added\" ? (\n <span className=\"text-muted-foreground\">—</span>\n ) : (\n <span className=\"line-through decoration-muted-foreground/50\">\n {formatValue(field.before, field.key)}\n </span>\n )}\n </td>\n <td className={cn(\"px-3 py-2 align-top\", CHANGE_STYLES[field.change])}>\n {field.redacted ? (\n <RedactedValue />\n ) : field.change === \"removed\" ? (\n <span className=\"text-muted-foreground\">—</span>\n ) : (\n formatValue(field.after, field.key)\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n {collapseUnchanged && unchangedCount > 0 ? (\n <button\n type=\"button\"\n data-slot=\"record-diff-toggle\"\n aria-expanded={showUnchanged}\n onClick={() => {\n setShowUnchanged((shown) => !shown);\n }}\n className={cn(\n \"self-start rounded-md px-1.5 py-0.5 text-xs text-muted-foreground\",\n \"transition-colors hover:text-foreground\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring/55\",\n )}\n >\n {showUnchanged\n ? `Hide ${String(unchangedCount)} unchanged`\n : `Show ${String(unchangedCount)} unchanged`}\n </button>\n ) : null}\n </div>\n );\n}\n\n/**\n * A value that is never rendered.\n *\n * Redaction happens before formatting, so a secret is not passed to a formatter\n * that might log or transform it — the value simply does not reach the DOM.\n */\nfunction RedactedValue() {\n return (\n <span data-slot=\"record-diff-redacted\" className=\"text-muted-foreground italic\">\n redacted\n </span>\n );\n}\n"],"mappings":"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"}
@@ -18,6 +18,7 @@ function Spinner({ className, size, label, ...props }) {
18
18
  viewBox: "0 0 24 24",
19
19
  fill: "none",
20
20
  "aria-hidden": "true",
21
+ "data-motion": "indicator",
21
22
  className: cn(spinnerVariants({ size }), className),
22
23
  ...props,
23
24
  children: [/* @__PURE__ */ jsx("circle", {
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.js","names":[],"sources":["../../../src/components/spinner/spinner.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst spinnerVariants = cva(\"animate-spin text-current\", {\n variants: {\n size: {\n xs: \"size-3\",\n sm: \"size-3.5\",\n md: \"size-4\",\n lg: \"size-5\",\n xl: \"size-6\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n});\n\nexport interface SpinnerProps\n extends Omit<ComponentPropsWithRef<\"svg\">, \"children\">, VariantProps<typeof spinnerVariants> {\n /**\n * Announced to assistive technology while the spinner is visible.\n *\n * Omit it when the spinner sits inside a control that already communicates\n * its busy state (a loading Button, for example) — announcing twice is worse\n * than not announcing at all.\n */\n label?: string;\n}\n\n/** Indeterminate loading indicator. */\nexport function Spinner({ className, size, label, ...props }: SpinnerProps) {\n return (\n <>\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n className={cn(spinnerVariants({ size }), className)}\n {...props}\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n opacity=\"0.22\"\n />\n <path\n d=\"M12 2.5a9.5 9.5 0 0 1 9.5 9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n {label ? (\n <span role=\"status\" className=\"sr-only\">\n {label}\n </span>\n ) : null}\n </>\n );\n}\n\nexport { spinnerVariants };\n"],"mappings":";;;;AAKA,MAAM,kBAAkB,IAAI,6BAA6B;CACvD,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EACf,MAAM,KACR;AACF,CAAC;;AAeD,SAAgB,QAAQ,EAAE,WAAW,MAAM,OAAO,GAAG,SAAuB;CAC1E,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;EACE,SAAQ;EACR,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,gBAAgB,EAAE,KAAK,CAAC,GAAG,SAAS;EAClD,GAAI;EALN,UAAA,CAOE,oBAAC,UAAD;GACE,IAAG;GACH,IAAG;GACH,GAAE;GACF,QAAO;GACP,aAAY;GACZ,SAAQ;EACT,CAAA,GACD,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACf,CAAA,CACE;CACJ,CAAA,GAAA,QACC,oBAAC,QAAD;EAAM,MAAK;EAAS,WAAU;EAC3B,UAAA;CACG,CAAA,IACJ,IACJ,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"spinner.js","names":[],"sources":["../../../src/components/spinner/spinner.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst spinnerVariants = cva(\"animate-spin text-current\", {\n variants: {\n size: {\n xs: \"size-3\",\n sm: \"size-3.5\",\n md: \"size-4\",\n lg: \"size-5\",\n xl: \"size-6\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n});\n\nexport interface SpinnerProps\n extends Omit<ComponentPropsWithRef<\"svg\">, \"children\">, VariantProps<typeof spinnerVariants> {\n /**\n * Announced to assistive technology while the spinner is visible.\n *\n * Omit it when the spinner sits inside a control that already communicates\n * its busy state (a loading Button, for example) — announcing twice is worse\n * than not announcing at all.\n */\n label?: string;\n}\n\n/** Indeterminate loading indicator. */\nexport function Spinner({ className, size, label, ...props }: SpinnerProps) {\n return (\n <>\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n // Exempt from the reduced-motion blanket: this is not decoration. A\n // spinner that stops turning reports that the application has hung,\n // which is a worse experience than the motion it was avoiding. It is\n // slowed instead — see --motion-scale-indicator.\n data-motion=\"indicator\"\n className={cn(spinnerVariants({ size }), className)}\n {...props}\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n opacity=\"0.22\"\n />\n <path\n d=\"M12 2.5a9.5 9.5 0 0 1 9.5 9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n {label ? (\n <span role=\"status\" className=\"sr-only\">\n {label}\n </span>\n ) : null}\n </>\n );\n}\n\nexport { spinnerVariants };\n"],"mappings":";;;;AAKA,MAAM,kBAAkB,IAAI,6BAA6B;CACvD,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EACf,MAAM,KACR;AACF,CAAC;;AAeD,SAAgB,QAAQ,EAAE,WAAW,MAAM,OAAO,GAAG,SAAuB;CAC1E,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;EACE,SAAQ;EACR,MAAK;EACL,eAAY;EAKZ,eAAY;EACZ,WAAW,GAAG,gBAAgB,EAAE,KAAK,CAAC,GAAG,SAAS;EAClD,GAAI;EAVN,UAAA,CAYE,oBAAC,UAAD;GACE,IAAG;GACH,IAAG;GACH,GAAE;GACF,QAAO;GACP,aAAY;GACZ,SAAQ;EACT,CAAA,GACD,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACf,CAAA,CACE;CACJ,CAAA,GAAA,QACC,oBAAC,QAAD;EAAM,MAAK;EAAS,WAAU;EAC3B,UAAA;CACG,CAAA,IACJ,IACJ,EAAA,CAAA;AAEN"}
@@ -0,0 +1,2 @@
1
+ import { TagValidator, TagsInput, TagsInputProps } from "./tags-input.js";
2
+ export { type TagValidator, TagsInput, type TagsInputProps };
@@ -0,0 +1,2 @@
1
+ import { TagsInput } from "./tags-input.js";
2
+ export { TagsInput };
@@ -0,0 +1,17 @@
1
+ import { defineMeta } from "../../registry/schema.js";
2
+ //#region src/components/tags-input/meta.ts
3
+ const meta = defineMeta({
4
+ name: "tags-input",
5
+ title: "Tags Input",
6
+ description: "A list of short values, where invalid entries stay visible instead of vanishing.",
7
+ category: "form",
8
+ status: "stable",
9
+ dependencies: [],
10
+ registryDependencies: [],
11
+ files: ["tags-input.tsx"],
12
+ a11y: "No WAI-ARIA pattern covers a token field, and the near misses misdescribe it — these are committed values, not options being chosen, so calling the container a listbox would promise navigation that does not exist. It is a labelled group holding a list, with a remove button per token carrying the tag in its accessible name. An invalid token's reason is inside the token, so it is announced with it rather than as a detached error. Additions, removals and refusals all reach a polite live region, because a refusal that looks like nothing happening is the failure this component exists to prevent. Remove buttons sit in the natural tab order rather than behind a roving tabindex: it costs a tab stop per token, and buys a pattern every assistive technology already understands."
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/tags-input/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"tags-input\",\n title: \"Tags Input\",\n description:\n \"A list of short values, where invalid entries stay visible instead of vanishing.\",\n category: \"form\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [],\n files: [\"tags-input.tsx\"],\n a11y:\n \"No WAI-ARIA pattern covers a token field, and the near misses misdescribe it — these are \" +\n \"committed values, not options being chosen, so calling the container a listbox would promise \" +\n \"navigation that does not exist. It is a labelled group holding a list, with a remove button \" +\n \"per token carrying the tag in its accessible name. An invalid token's reason is inside the \" +\n \"token, so it is announced with it rather than as a detached error. Additions, removals and \" +\n \"refusals all reach a polite live region, because a refusal that looks like nothing happening \" +\n \"is the failure this component exists to prevent. Remove buttons sit in the natural tab order \" +\n \"rather than behind a roving tabindex: it costs a tab stop per token, and buys a pattern every \" +\n \"assistive technology already understands.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC;CACvB,OAAO,CAAC,gBAAgB;CACxB,MACE;AASJ,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { ComponentPropsWithRef } from "react";
2
+ //#region src/components/tags-input/tags-input.d.ts
3
+ /**
4
+ * A field whose value is a list of short strings: invited emails, allowed
5
+ * domains, labels, stop sequences.
6
+ *
7
+ * The behaviour that makes this worth shipping is what happens to input that
8
+ * fails validation. Every implementation surveyed either refuses to create the
9
+ * token, or creates it and silently throws it away — and both leave the reader
10
+ * staring at a field that did not do what they asked, with nothing to correct.
11
+ * Here an invalid entry becomes a token like any other, marked invalid and
12
+ * carrying its reason, so it can be seen, read out, and fixed by removing it.
13
+ * `value` holds every token including the invalid ones; the consumer runs the
14
+ * same `validate` to decide whether the field is submittable.
15
+ *
16
+ * Deliberately no suggestion list. A token field with an anchored listbox is a
17
+ * multi-select combobox, and this library already has 544 lines of hand-rolled
18
+ * combobox ARIA. A second copy of that would be the expensive kind of
19
+ * duplication — the kind that drifts. When multi-select is wanted, it belongs in
20
+ * Combobox.
21
+ */
22
+ /** Returns `true` for a valid tag, or a reason it is not. */
23
+ type TagValidator = (tag: string, existing: string[]) => true | string;
24
+ interface TagsInputProps extends Omit<ComponentPropsWithRef<"div">, "onChange" | "children"> {
25
+ value: string[];
26
+ onValueChange: (tags: string[]) => void;
27
+ /** Names the field. Required — an unlabelled list of tokens is unreadable. */
28
+ label: string;
29
+ /** Characters that commit the token being typed, besides Enter. */
30
+ delimiters?: string[];
31
+ /**
32
+ * Decides whether a tag is acceptable. An invalid tag is still added, marked,
33
+ * and given its reason, rather than being refused or dropped.
34
+ */
35
+ validate?: TagValidator;
36
+ /** Rejects a tag already present. Rejections are announced, never silent. */
37
+ allowDuplicates?: boolean;
38
+ /** Refuses further tags once reached, and says so. */
39
+ max?: number;
40
+ placeholder?: string;
41
+ disabled?: boolean;
42
+ inputProps?: Omit<ComponentPropsWithRef<"input">, "value" | "onChange" | "disabled">;
43
+ }
44
+ declare function TagsInput({ className, value, onValueChange, label, delimiters, validate, allowDuplicates, max, placeholder, disabled, inputProps, ...props }: TagsInputProps): import("react").JSX.Element;
45
+ //#endregion
46
+ export { TagValidator, TagsInput, TagsInputProps };
47
+ //# sourceMappingURL=tags-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags-input.d.ts","names":[],"sources":["../../../src/components/tags-input/tags-input.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;KAmCY,gBAAgB,aAAa;UAExB,uBAAuB,KACtC;EAGA;EACA,gBAAgB;;EAEhB;;EAEA;;;;;EAKA,WAAW;;EAEX;;EAEA;EACA;EACA;EACA,aAAa,KAAK;;iBAaJ,YACd,WACA,OACA,eACA,OACA,YACA,UACA,iBACA,KACA,aACA,UACA,eACG,SACF,iCAAc,IAAA"}
@@ -0,0 +1,182 @@
1
+ "use client";
2
+ import { disabledStyles, focusRing } from "../../lib/styles.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { useId, useRef, useState } from "react";
6
+ //#region src/components/tags-input/tags-input.tsx
7
+ /** Splits pasted or typed text on any delimiter, dropping empty fragments. */
8
+ function splitOn(text, delimiters) {
9
+ if (delimiters.length === 0) return [text];
10
+ const pattern = new RegExp(`[${delimiters.map((d) => `\\${d}`).join("")}]`);
11
+ return text.split(pattern).map((part) => part.trim()).filter((part) => part.length > 0);
12
+ }
13
+ function TagsInput({ className, value, onValueChange, label, delimiters = [",", ";"], validate, allowDuplicates = false, max, placeholder, disabled = false, inputProps, ...props }) {
14
+ const [draft, setDraft] = useState("");
15
+ const [announcement, setAnnouncement] = useState("");
16
+ const inputRef = useRef(null);
17
+ const labelId = useId();
18
+ const inputId = useId();
19
+ const hintId = useId();
20
+ const atLimit = max !== void 0 && value.length >= max;
21
+ function reasonFor(tag) {
22
+ if (!validate) return null;
23
+ const result = validate(tag, value);
24
+ return result === true ? null : result;
25
+ }
26
+ /** Adds tags, reporting anything refused rather than dropping it in silence. */
27
+ function commit(candidates) {
28
+ const accepted = [];
29
+ const refused = [];
30
+ for (const candidate of candidates.flatMap((part) => splitOn(part, delimiters))) {
31
+ const tag = candidate.trim();
32
+ if (tag.length === 0) continue;
33
+ if (!allowDuplicates && [...value, ...accepted].includes(tag)) {
34
+ refused.push(`${tag} is already added`);
35
+ continue;
36
+ }
37
+ if (max !== void 0 && value.length + accepted.length >= max) {
38
+ refused.push(`${tag} not added, limit of ${String(max)} reached`);
39
+ continue;
40
+ }
41
+ accepted.push(tag);
42
+ }
43
+ if (accepted.length > 0) onValueChange([...value, ...accepted]);
44
+ const parts = [];
45
+ if (accepted.length > 0) {
46
+ const invalid = accepted.filter((tag) => reasonFor(tag) !== null);
47
+ parts.push(`Added ${accepted.join(", ")}`);
48
+ if (invalid.length > 0) parts.push(`${String(invalid.length)} need attention`);
49
+ }
50
+ parts.push(...refused);
51
+ setAnnouncement(parts.join(". "));
52
+ }
53
+ function removeAt(index) {
54
+ const removed = value[index];
55
+ onValueChange(value.filter((_, position) => position !== index));
56
+ setAnnouncement(removed === void 0 ? "" : `Removed ${removed}`);
57
+ inputRef.current?.focus();
58
+ }
59
+ function handleKeyDown(event) {
60
+ if (event.key === "Enter") {
61
+ event.preventDefault();
62
+ if (draft.trim().length > 0) {
63
+ commit([draft]);
64
+ setDraft("");
65
+ }
66
+ return;
67
+ }
68
+ if (delimiters.includes(event.key)) {
69
+ event.preventDefault();
70
+ if (draft.trim().length > 0) {
71
+ commit([draft]);
72
+ setDraft("");
73
+ }
74
+ return;
75
+ }
76
+ if (event.key === "Backspace" && draft.length === 0 && value.length > 0) {
77
+ event.preventDefault();
78
+ removeAt(value.length - 1);
79
+ }
80
+ }
81
+ function handlePaste(event) {
82
+ const parts = splitOn(event.clipboardData.getData("text"), delimiters);
83
+ if (parts.length <= 1) return;
84
+ event.preventDefault();
85
+ commit(parts);
86
+ setDraft("");
87
+ }
88
+ return /* @__PURE__ */ jsxs("div", {
89
+ "data-slot": "tags-input",
90
+ className: cn("flex flex-col gap-1.5", className),
91
+ ...props,
92
+ children: [
93
+ /* @__PURE__ */ jsx("label", {
94
+ id: labelId,
95
+ htmlFor: inputId,
96
+ className: "w-fit text-sm font-medium",
97
+ children: label
98
+ }),
99
+ /* @__PURE__ */ jsxs("div", {
100
+ role: "group",
101
+ "aria-labelledby": labelId,
102
+ "data-disabled": disabled || void 0,
103
+ className: cn("flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-background px-2 py-1.5", "focus-within:ring-2 focus-within:ring-ring/55", disabled && "pointer-events-none opacity-55"),
104
+ children: [value.length > 0 ? /* @__PURE__ */ jsx("ul", {
105
+ className: "contents",
106
+ children: value.map((tag, index) => {
107
+ const reason = reasonFor(tag);
108
+ return /* @__PURE__ */ jsxs("li", {
109
+ "data-slot": "tags-input-tag",
110
+ "data-invalid": reason !== null || void 0,
111
+ className: cn("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium", reason === null ? "border-transparent bg-secondary text-secondary-foreground" : "border-destructive/40 bg-destructive/10 text-destructive"),
112
+ children: [
113
+ tag,
114
+ reason === null ? null : /* @__PURE__ */ jsxs("span", {
115
+ className: "sr-only",
116
+ children: [", invalid: ", reason]
117
+ }),
118
+ /* @__PURE__ */ jsx("button", {
119
+ type: "button",
120
+ "data-slot": "tags-input-remove",
121
+ disabled,
122
+ "aria-label": `Remove ${tag}`,
123
+ onClick: () => {
124
+ removeAt(index);
125
+ },
126
+ className: cn("-mr-0.5 grid size-4 place-items-center rounded-full", "transition-colors hover:bg-foreground/10", focusRing, disabledStyles),
127
+ children: /* @__PURE__ */ jsx("svg", {
128
+ viewBox: "0 0 24 24",
129
+ "aria-hidden": "true",
130
+ className: "size-2.5",
131
+ children: /* @__PURE__ */ jsx("path", {
132
+ d: "M6 6l12 12M18 6L6 18",
133
+ stroke: "currentColor",
134
+ strokeWidth: "3",
135
+ strokeLinecap: "round"
136
+ })
137
+ })
138
+ })
139
+ ]
140
+ }, `${tag}-${String(index)}`);
141
+ })
142
+ }) : null, /* @__PURE__ */ jsx("input", {
143
+ ref: inputRef,
144
+ type: "text",
145
+ id: inputId,
146
+ "data-slot": "tags-input-field",
147
+ value: draft,
148
+ disabled,
149
+ placeholder: value.length === 0 ? placeholder : void 0,
150
+ "aria-describedby": hintId,
151
+ onChange: (event) => {
152
+ setDraft(event.target.value);
153
+ },
154
+ onKeyDown: handleKeyDown,
155
+ onPaste: handlePaste,
156
+ onBlur: () => {
157
+ if (draft.trim().length > 0) {
158
+ commit([draft]);
159
+ setDraft("");
160
+ }
161
+ },
162
+ className: cn("min-w-24 flex-1 self-stretch bg-transparent text-sm outline-none", "placeholder:text-muted-foreground"),
163
+ ...inputProps
164
+ })]
165
+ }),
166
+ /* @__PURE__ */ jsx("span", {
167
+ id: hintId,
168
+ className: "text-xs text-muted-foreground",
169
+ children: atLimit ? `Limit of ${String(max ?? 0)} reached` : `Press Enter${delimiters.length > 0 ? ` or ${delimiters.join(" ")}` : ""} to add`
170
+ }),
171
+ /* @__PURE__ */ jsx("span", {
172
+ "aria-live": "polite",
173
+ className: "sr-only",
174
+ children: announcement
175
+ })
176
+ ]
177
+ });
178
+ }
179
+ //#endregion
180
+ export { TagsInput };
181
+
182
+ //# sourceMappingURL=tags-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags-input.js","names":[],"sources":["../../../src/components/tags-input/tags-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useId,\n useRef,\n useState,\n type ClipboardEvent,\n type ComponentPropsWithRef,\n type KeyboardEvent,\n} from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A field whose value is a list of short strings: invited emails, allowed\n * domains, labels, stop sequences.\n *\n * The behaviour that makes this worth shipping is what happens to input that\n * fails validation. Every implementation surveyed either refuses to create the\n * token, or creates it and silently throws it away — and both leave the reader\n * staring at a field that did not do what they asked, with nothing to correct.\n * Here an invalid entry becomes a token like any other, marked invalid and\n * carrying its reason, so it can be seen, read out, and fixed by removing it.\n * `value` holds every token including the invalid ones; the consumer runs the\n * same `validate` to decide whether the field is submittable.\n *\n * Deliberately no suggestion list. A token field with an anchored listbox is a\n * multi-select combobox, and this library already has 544 lines of hand-rolled\n * combobox ARIA. A second copy of that would be the expensive kind of\n * duplication — the kind that drifts. When multi-select is wanted, it belongs in\n * Combobox.\n */\n\n/** Returns `true` for a valid tag, or a reason it is not. */\nexport type TagValidator = (tag: string, existing: string[]) => true | string;\n\nexport interface TagsInputProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"onChange\" | \"children\"\n> {\n value: string[];\n onValueChange: (tags: string[]) => void;\n /** Names the field. Required — an unlabelled list of tokens is unreadable. */\n label: string;\n /** Characters that commit the token being typed, besides Enter. */\n delimiters?: string[];\n /**\n * Decides whether a tag is acceptable. An invalid tag is still added, marked,\n * and given its reason, rather than being refused or dropped.\n */\n validate?: TagValidator;\n /** Rejects a tag already present. Rejections are announced, never silent. */\n allowDuplicates?: boolean;\n /** Refuses further tags once reached, and says so. */\n max?: number;\n placeholder?: string;\n disabled?: boolean;\n inputProps?: Omit<ComponentPropsWithRef<\"input\">, \"value\" | \"onChange\" | \"disabled\">;\n}\n\n/** Splits pasted or typed text on any delimiter, dropping empty fragments. */\nfunction splitOn(text: string, delimiters: string[]): string[] {\n if (delimiters.length === 0) return [text];\n const pattern = new RegExp(`[${delimiters.map((d) => `\\\\${d}`).join(\"\")}]`);\n return text\n .split(pattern)\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n}\n\nexport function TagsInput({\n className,\n value,\n onValueChange,\n label,\n delimiters = [\",\", \";\"],\n validate,\n allowDuplicates = false,\n max,\n placeholder,\n disabled = false,\n inputProps,\n ...props\n}: TagsInputProps) {\n const [draft, setDraft] = useState(\"\");\n const [announcement, setAnnouncement] = useState(\"\");\n const inputRef = useRef<HTMLInputElement | null>(null);\n const labelId = useId();\n const inputId = useId();\n const hintId = useId();\n\n const atLimit = max !== undefined && value.length >= max;\n\n function reasonFor(tag: string): string | null {\n if (!validate) return null;\n const result = validate(tag, value);\n return result === true ? null : result;\n }\n\n /** Adds tags, reporting anything refused rather than dropping it in silence. */\n function commit(candidates: string[]) {\n const accepted: string[] = [];\n const refused: string[] = [];\n\n // Split defensively rather than trusting the caller. A draft can arrive\n // holding a delimiter from autofill, an IME commit, or a programmatic set,\n // and a token with a comma inside it is never what was meant.\n for (const candidate of candidates.flatMap((part) => splitOn(part, delimiters))) {\n const tag = candidate.trim();\n if (tag.length === 0) continue;\n\n if (!allowDuplicates && [...value, ...accepted].includes(tag)) {\n refused.push(`${tag} is already added`);\n continue;\n }\n if (max !== undefined && value.length + accepted.length >= max) {\n refused.push(`${tag} not added, limit of ${String(max)} reached`);\n continue;\n }\n // An invalid tag is still added. It is the reader's text and they need to\n // see it to fix it; refusing silently is how these fields lose input.\n accepted.push(tag);\n }\n\n if (accepted.length > 0) onValueChange([...value, ...accepted]);\n\n const parts: string[] = [];\n if (accepted.length > 0) {\n const invalid = accepted.filter((tag) => reasonFor(tag) !== null);\n parts.push(`Added ${accepted.join(\", \")}`);\n if (invalid.length > 0) parts.push(`${String(invalid.length)} need attention`);\n }\n parts.push(...refused);\n setAnnouncement(parts.join(\". \"));\n }\n\n function removeAt(index: number) {\n const removed = value[index];\n onValueChange(value.filter((_, position) => position !== index));\n setAnnouncement(removed === undefined ? \"\" : `Removed ${removed}`);\n inputRef.current?.focus();\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\") {\n // Never let a token commit submit the form as well.\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n if (delimiters.includes(event.key)) {\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n // Backspace on an empty field removes the last token — the expected\n // shortcut, and the only way to correct without reaching for the mouse.\n if (event.key === \"Backspace\" && draft.length === 0 && value.length > 0) {\n event.preventDefault();\n removeAt(value.length - 1);\n }\n }\n\n function handlePaste(event: ClipboardEvent<HTMLInputElement>) {\n const text = event.clipboardData.getData(\"text\");\n const parts = splitOn(text, delimiters);\n // Only intercept a paste that actually contains several tags; a single\n // value should behave like an ordinary paste and stay editable.\n if (parts.length <= 1) return;\n\n event.preventDefault();\n commit(parts);\n setDraft(\"\");\n }\n\n return (\n <div data-slot=\"tags-input\" className={cn(\"flex flex-col gap-1.5\", className)} {...props}>\n <label id={labelId} htmlFor={inputId} className=\"w-fit text-sm font-medium\">\n {label}\n </label>\n\n {/* A group rather than a listbox: these are committed values, not options\n being chosen from, and calling it a listbox would promise navigation\n semantics that do not exist here. */}\n <div\n role=\"group\"\n aria-labelledby={labelId}\n data-disabled={disabled || undefined}\n className={cn(\n \"flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-background px-2 py-1.5\",\n \"focus-within:ring-2 focus-within:ring-ring/55\",\n disabled && \"pointer-events-none opacity-55\",\n )}\n >\n {value.length > 0 ? (\n <ul className=\"contents\">\n {value.map((tag, index) => {\n const reason = reasonFor(tag);\n return (\n <li\n key={`${tag}-${String(index)}`}\n data-slot=\"tags-input-tag\"\n data-invalid={reason !== null || undefined}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium\",\n reason === null\n ? \"border-transparent bg-secondary text-secondary-foreground\"\n : \"border-destructive/40 bg-destructive/10 text-destructive\",\n )}\n >\n {tag}\n {/* The reason travels with the token, so it is announced when\n the token is, rather than as a detached error elsewhere. */}\n {reason === null ? null : (\n <span className=\"sr-only\">, invalid: {reason}</span>\n )}\n <button\n type=\"button\"\n data-slot=\"tags-input-remove\"\n disabled={disabled}\n aria-label={`Remove ${tag}`}\n onClick={() => {\n removeAt(index);\n }}\n className={cn(\n \"-mr-0.5 grid size-4 place-items-center rounded-full\",\n \"transition-colors hover:bg-foreground/10\",\n focusRing,\n disabledStyles,\n )}\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-2.5\">\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n\n <input\n ref={inputRef}\n type=\"text\"\n id={inputId}\n data-slot=\"tags-input-field\"\n value={draft}\n disabled={disabled}\n placeholder={value.length === 0 ? placeholder : undefined}\n aria-describedby={hintId}\n onChange={(event) => {\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onBlur={() => {\n // Committing on blur stops a typed-but-uncommitted value being lost\n // when the reader tabs away thinking they are done.\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n }}\n className={cn(\n \"min-w-24 flex-1 self-stretch bg-transparent text-sm outline-none\",\n \"placeholder:text-muted-foreground\",\n )}\n {...inputProps}\n />\n </div>\n\n <span id={hintId} className=\"text-xs text-muted-foreground\">\n {atLimit\n ? `Limit of ${String(max ?? 0)} reached`\n : `Press Enter${delimiters.length > 0 ? ` or ${delimiters.join(\" \")}` : \"\"} to add`}\n </span>\n\n {/* Additions, removals and refusals all land here. A refusal that is only\n visible as \"nothing happened\" is the failure this component exists to\n avoid. */}\n <span aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { ComponentPropsWithRef } from "react";
12
12
  * toast list from anywhere on the page.
13
13
  */
14
14
  declare const toastVariants: (props?: ({
15
- variant?: "success" | "warning" | "destructive" | "default" | "info" | null | undefined;
15
+ variant?: "default" | "destructive" | "success" | "warning" | "info" | null | undefined;
16
16
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
17
17
  declare const ToastProvider: import("react").FC<Toast.ToastProviderProps>;
18
18
  interface ToastProps extends ComponentPropsWithRef<typeof Toast.Root>, VariantProps<typeof toastVariants> {}