@dowel-ui/react 0.4.0 → 0.5.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 (146) hide show
  1. package/README.md +9 -5
  2. package/dist/components/ai-extraction-review/ai-extraction-review.d.ts +52 -0
  3. package/dist/components/ai-extraction-review/ai-extraction-review.d.ts.map +1 -0
  4. package/dist/components/ai-extraction-review/ai-extraction-review.js +249 -0
  5. package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -0
  6. package/dist/components/ai-extraction-review/extraction-model.d.ts +94 -0
  7. package/dist/components/ai-extraction-review/extraction-model.d.ts.map +1 -0
  8. package/dist/components/ai-extraction-review/extraction-model.js +91 -0
  9. package/dist/components/ai-extraction-review/extraction-model.js.map +1 -0
  10. package/dist/components/ai-extraction-review/index.d.ts +3 -0
  11. package/dist/components/ai-extraction-review/index.js +3 -0
  12. package/dist/components/ai-extraction-review/meta.js +17 -0
  13. package/dist/components/ai-extraction-review/meta.js.map +1 -0
  14. package/dist/components/ai-suggested-value/ai-suggested-value.d.ts +72 -0
  15. package/dist/components/ai-suggested-value/ai-suggested-value.d.ts.map +1 -0
  16. package/dist/components/ai-suggested-value/ai-suggested-value.js +126 -0
  17. package/dist/components/ai-suggested-value/ai-suggested-value.js.map +1 -0
  18. package/dist/components/ai-suggested-value/index.d.ts +2 -0
  19. package/dist/components/ai-suggested-value/index.js +2 -0
  20. package/dist/components/ai-suggested-value/meta.js +17 -0
  21. package/dist/components/ai-suggested-value/meta.js.map +1 -0
  22. package/dist/components/alert/alert.d.ts +1 -1
  23. package/dist/components/avatar/avatar.d.ts +1 -1
  24. package/dist/components/badge/badge.d.ts +1 -1
  25. package/dist/components/button/button.d.ts +1 -1
  26. package/dist/components/confirm-typed/confirm-typed.d.ts +47 -0
  27. package/dist/components/confirm-typed/confirm-typed.d.ts.map +1 -0
  28. package/dist/components/confirm-typed/confirm-typed.js +106 -0
  29. package/dist/components/confirm-typed/confirm-typed.js.map +1 -0
  30. package/dist/components/confirm-typed/index.d.ts +2 -0
  31. package/dist/components/confirm-typed/index.js +2 -0
  32. package/dist/components/confirm-typed/meta.js +17 -0
  33. package/dist/components/confirm-typed/meta.js.map +1 -0
  34. package/dist/components/cron-editor/cron-editor.d.ts +36 -0
  35. package/dist/components/cron-editor/cron-editor.d.ts.map +1 -0
  36. package/dist/components/cron-editor/cron-editor.js +493 -0
  37. package/dist/components/cron-editor/cron-editor.js.map +1 -0
  38. package/dist/components/cron-editor/cron-expression.d.ts +82 -0
  39. package/dist/components/cron-editor/cron-expression.d.ts.map +1 -0
  40. package/dist/components/cron-editor/cron-expression.js +352 -0
  41. package/dist/components/cron-editor/cron-expression.js.map +1 -0
  42. package/dist/components/cron-editor/index.d.ts +3 -0
  43. package/dist/components/cron-editor/index.js +3 -0
  44. package/dist/components/cron-editor/meta.js +17 -0
  45. package/dist/components/cron-editor/meta.js.map +1 -0
  46. package/dist/components/dns-record/dns-record.d.ts +61 -0
  47. package/dist/components/dns-record/dns-record.d.ts.map +1 -0
  48. package/dist/components/dns-record/dns-record.js +227 -0
  49. package/dist/components/dns-record/dns-record.js.map +1 -0
  50. package/dist/components/dns-record/index.d.ts +2 -0
  51. package/dist/components/dns-record/index.js +2 -0
  52. package/dist/components/dns-record/meta.js +17 -0
  53. package/dist/components/dns-record/meta.js.map +1 -0
  54. package/dist/components/permission-matrix/index.d.ts +3 -0
  55. package/dist/components/permission-matrix/index.js +3 -0
  56. package/dist/components/permission-matrix/meta.js +17 -0
  57. package/dist/components/permission-matrix/meta.js.map +1 -0
  58. package/dist/components/permission-matrix/permission-matrix.d.ts +46 -0
  59. package/dist/components/permission-matrix/permission-matrix.d.ts.map +1 -0
  60. package/dist/components/permission-matrix/permission-matrix.js +199 -0
  61. package/dist/components/permission-matrix/permission-matrix.js.map +1 -0
  62. package/dist/components/permission-matrix/permission-model.d.ts +53 -0
  63. package/dist/components/permission-matrix/permission-model.d.ts.map +1 -0
  64. package/dist/components/permission-matrix/permission-model.js +64 -0
  65. package/dist/components/permission-matrix/permission-model.js.map +1 -0
  66. package/dist/components/progress/progress.d.ts +1 -1
  67. package/dist/components/secret-field/index.d.ts +2 -0
  68. package/dist/components/secret-field/index.js +2 -0
  69. package/dist/components/secret-field/meta.js +17 -0
  70. package/dist/components/secret-field/meta.js.map +1 -0
  71. package/dist/components/secret-field/secret-field.d.ts +58 -0
  72. package/dist/components/secret-field/secret-field.d.ts.map +1 -0
  73. package/dist/components/secret-field/secret-field.js +168 -0
  74. package/dist/components/secret-field/secret-field.js.map +1 -0
  75. package/dist/components/session-expiry/index.d.ts +2 -0
  76. package/dist/components/session-expiry/index.js +2 -0
  77. package/dist/components/session-expiry/meta.js +17 -0
  78. package/dist/components/session-expiry/meta.js.map +1 -0
  79. package/dist/components/session-expiry/session-expiry.d.ts +64 -0
  80. package/dist/components/session-expiry/session-expiry.d.ts.map +1 -0
  81. package/dist/components/session-expiry/session-expiry.js +185 -0
  82. package/dist/components/session-expiry/session-expiry.js.map +1 -0
  83. package/dist/components/shortcut-recorder/index.d.ts +3 -0
  84. package/dist/components/shortcut-recorder/index.js +3 -0
  85. package/dist/components/shortcut-recorder/meta.js +17 -0
  86. package/dist/components/shortcut-recorder/meta.js.map +1 -0
  87. package/dist/components/shortcut-recorder/shortcut-model.d.ts +67 -0
  88. package/dist/components/shortcut-recorder/shortcut-model.d.ts.map +1 -0
  89. package/dist/components/shortcut-recorder/shortcut-model.js +168 -0
  90. package/dist/components/shortcut-recorder/shortcut-model.js.map +1 -0
  91. package/dist/components/shortcut-recorder/shortcut-recorder.d.ts +47 -0
  92. package/dist/components/shortcut-recorder/shortcut-recorder.d.ts.map +1 -0
  93. package/dist/components/shortcut-recorder/shortcut-recorder.js +212 -0
  94. package/dist/components/shortcut-recorder/shortcut-recorder.js.map +1 -0
  95. package/dist/components/spinner/spinner.d.ts +1 -1
  96. package/dist/components/sync-status/index.d.ts +2 -0
  97. package/dist/components/sync-status/index.js +2 -0
  98. package/dist/components/sync-status/meta.js +17 -0
  99. package/dist/components/sync-status/meta.js.map +1 -0
  100. package/dist/components/sync-status/sync-status.d.ts +65 -0
  101. package/dist/components/sync-status/sync-status.d.ts.map +1 -0
  102. package/dist/components/sync-status/sync-status.js +103 -0
  103. package/dist/components/sync-status/sync-status.js.map +1 -0
  104. package/dist/components/toast/toast.d.ts +1 -1
  105. package/dist/index.d.ts +25 -1
  106. package/dist/index.js +16 -2
  107. package/dist/registry/components.d.ts.map +1 -1
  108. package/dist/registry/components.js +73 -53
  109. package/dist/registry/components.js.map +1 -1
  110. package/package.json +4 -4
  111. package/src/components/ai-extraction-review/ai-extraction-review.tsx +492 -0
  112. package/src/components/ai-extraction-review/extraction-model.ts +159 -0
  113. package/src/components/ai-extraction-review/index.ts +13 -0
  114. package/src/components/ai-extraction-review/meta.ts +24 -0
  115. package/src/components/ai-suggested-value/ai-suggested-value.tsx +295 -0
  116. package/src/components/ai-suggested-value/index.ts +6 -0
  117. package/src/components/ai-suggested-value/meta.ts +24 -0
  118. package/src/components/confirm-typed/confirm-typed.tsx +191 -0
  119. package/src/components/confirm-typed/index.ts +1 -0
  120. package/src/components/confirm-typed/meta.ts +22 -0
  121. package/src/components/cron-editor/cron-editor.tsx +676 -0
  122. package/src/components/cron-editor/cron-expression.ts +552 -0
  123. package/src/components/cron-editor/index.ts +23 -0
  124. package/src/components/cron-editor/meta.ts +23 -0
  125. package/src/components/dns-record/dns-record.tsx +315 -0
  126. package/src/components/dns-record/index.ts +7 -0
  127. package/src/components/dns-record/meta.ts +21 -0
  128. package/src/components/permission-matrix/index.ts +14 -0
  129. package/src/components/permission-matrix/meta.ts +23 -0
  130. package/src/components/permission-matrix/permission-matrix.tsx +368 -0
  131. package/src/components/permission-matrix/permission-model.ts +120 -0
  132. package/src/components/secret-field/index.ts +6 -0
  133. package/src/components/secret-field/meta.ts +21 -0
  134. package/src/components/secret-field/secret-field.tsx +284 -0
  135. package/src/components/session-expiry/index.ts +9 -0
  136. package/src/components/session-expiry/meta.ts +23 -0
  137. package/src/components/session-expiry/session-expiry.tsx +321 -0
  138. package/src/components/shortcut-recorder/index.ts +19 -0
  139. package/src/components/shortcut-recorder/meta.ts +22 -0
  140. package/src/components/shortcut-recorder/shortcut-model.ts +204 -0
  141. package/src/components/shortcut-recorder/shortcut-recorder.tsx +336 -0
  142. package/src/components/sync-status/index.ts +9 -0
  143. package/src/components/sync-status/meta.ts +21 -0
  144. package/src/components/sync-status/sync-status.tsx +217 -0
  145. package/src/index.ts +10 -0
  146. package/src/registry/components.ts +20 -0
@@ -0,0 +1,227 @@
1
+ "use client";
2
+ import { focusRing } from "../../lib/styles.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { Spinner } from "../spinner/spinner.js";
5
+ import { Button } from "../button/button.js";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { useEffect, useId, useRef, useState } from "react";
8
+ //#region src/components/dns-record/dns-record.tsx
9
+ /**
10
+ * The host both ways a provider might want it. Accepts either form and a
11
+ * trailing dot, so the record can be declared however the backend has it.
12
+ */
13
+ function dnsHostForms(name, zone) {
14
+ const cleanZone = zone.replace(/\.$/, "");
15
+ const cleanName = name.replace(/\.$/, "");
16
+ if (cleanName === "@" || cleanName === "" || cleanName === cleanZone) return {
17
+ relative: "@",
18
+ full: cleanZone
19
+ };
20
+ if (cleanName.endsWith(`.${cleanZone}`)) return {
21
+ relative: cleanName.slice(0, -cleanZone.length - 1),
22
+ full: cleanName
23
+ };
24
+ return {
25
+ relative: cleanName,
26
+ full: `${cleanName}.${cleanZone}`
27
+ };
28
+ }
29
+ function DnsRecord({ className, type, name, value, zone, ttl, priority, purpose, status = "pending", found, checkedAt, locale, onCheck, children, ...props }) {
30
+ const id = useId();
31
+ const forms = zone ? dnsHostForms(name, zone) : null;
32
+ const host = forms ? forms.relative : name;
33
+ const [copy, setCopy] = useState(null);
34
+ const timer = useRef(null);
35
+ useEffect(() => () => {
36
+ if (timer.current) clearTimeout(timer.current);
37
+ }, []);
38
+ const copyPart = (part, text) => {
39
+ const settle = (state) => {
40
+ setCopy({
41
+ part,
42
+ state
43
+ });
44
+ if (timer.current) clearTimeout(timer.current);
45
+ timer.current = setTimeout(() => {
46
+ setCopy(null);
47
+ }, 2e3);
48
+ };
49
+ Promise.resolve().then(() => navigator.clipboard.writeText(text)).then(() => {
50
+ settle("copied");
51
+ }, () => {
52
+ settle("error");
53
+ });
54
+ };
55
+ const statusText = status === "verified" ? "Verified" : status === "checking" ? "Checking…" : status === "failed" ? found && found.length > 0 ? "Not verified. Something else was found." : "Not verified. Nothing found yet — DNS changes can take up to 48 hours to appear." : "Not checked yet";
56
+ const checked = checkedAt !== void 0 ? new Intl.DateTimeFormat(locale, {
57
+ dateStyle: "medium",
58
+ timeStyle: "short"
59
+ }).format(checkedAt) : null;
60
+ const parts = [
61
+ {
62
+ key: "type",
63
+ label: "Type",
64
+ text: type
65
+ },
66
+ {
67
+ key: "name",
68
+ label: "Name",
69
+ text: host,
70
+ note: forms ? forms.relative === "@" ? /* @__PURE__ */ jsxs(Fragment, { children: [
71
+ "Some providers want ",
72
+ /* @__PURE__ */ jsx("code", {
73
+ className: "font-mono",
74
+ children: forms.full
75
+ }),
76
+ " or leave it blank instead of ",
77
+ /* @__PURE__ */ jsx("code", {
78
+ className: "font-mono",
79
+ children: "@"
80
+ }),
81
+ "."
82
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
83
+ "Some providers want the full name, ",
84
+ /* @__PURE__ */ jsx("code", {
85
+ className: "font-mono",
86
+ children: forms.full
87
+ }),
88
+ ". If the field already ends in ",
89
+ /* @__PURE__ */ jsx("code", {
90
+ className: "font-mono",
91
+ children: zone
92
+ }),
93
+ ", enter just the part shown."
94
+ ] }) : void 0
95
+ },
96
+ {
97
+ key: "value",
98
+ label: "Value",
99
+ text: value,
100
+ note: type.toUpperCase() === "TXT" ? "Enter without quotes; your provider adds them if it needs to." : void 0
101
+ },
102
+ ...priority !== void 0 ? [{
103
+ key: "priority",
104
+ label: "Priority",
105
+ text: String(priority)
106
+ }] : [],
107
+ ...ttl !== void 0 ? [{
108
+ key: "ttl",
109
+ label: "TTL",
110
+ text: String(ttl)
111
+ }] : []
112
+ ];
113
+ return /* @__PURE__ */ jsxs("section", {
114
+ "data-slot": "dns-record",
115
+ "data-status": status,
116
+ "aria-labelledby": `${id}-heading`,
117
+ "aria-busy": status === "checking" || void 0,
118
+ className: cn("flex flex-col gap-3 rounded-lg border border-border bg-card p-4", className),
119
+ ...props,
120
+ children: [
121
+ /* @__PURE__ */ jsxs("div", {
122
+ className: "flex flex-wrap items-baseline justify-between gap-2",
123
+ children: [/* @__PURE__ */ jsxs("h3", {
124
+ id: `${id}-heading`,
125
+ className: "text-sm font-medium",
126
+ children: [
127
+ type,
128
+ " record",
129
+ purpose ? /* @__PURE__ */ jsxs("span", {
130
+ className: "font-normal text-muted-foreground",
131
+ children: [" · ", purpose]
132
+ }) : null
133
+ ]
134
+ }), /* @__PURE__ */ jsxs("p", {
135
+ "data-slot": "dns-record-status",
136
+ role: "status",
137
+ "aria-live": "polite",
138
+ className: cn("flex items-center gap-1.5 text-xs", status === "verified" && "text-success", status === "failed" && "text-destructive", (status === "pending" || status === "checking") && "text-muted-foreground"),
139
+ children: [
140
+ status === "checking" ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : null,
141
+ statusText,
142
+ checked && status !== "checking" ? /* @__PURE__ */ jsxs("span", {
143
+ className: "text-muted-foreground",
144
+ children: [" · checked ", checked]
145
+ }) : null
146
+ ]
147
+ })]
148
+ }),
149
+ children,
150
+ /* @__PURE__ */ jsx("dl", {
151
+ className: "m-0 grid gap-x-4 gap-y-2 sm:grid-cols-[auto_1fr]",
152
+ children: parts.map((part) => /* @__PURE__ */ jsxs("div", {
153
+ className: "contents",
154
+ children: [/* @__PURE__ */ jsx("dt", {
155
+ className: "text-xs font-medium text-muted-foreground sm:pt-1.5",
156
+ children: part.label
157
+ }), /* @__PURE__ */ jsxs("dd", {
158
+ className: "m-0 flex flex-col gap-1",
159
+ children: [
160
+ /* @__PURE__ */ jsxs("div", {
161
+ className: "flex items-start gap-2",
162
+ children: [/* @__PURE__ */ jsx("code", {
163
+ "data-slot": "dns-record-part",
164
+ "data-part": part.key,
165
+ className: "min-w-0 flex-1 rounded-md bg-muted px-2 py-1 font-mono text-xs break-all",
166
+ children: part.text
167
+ }), /* @__PURE__ */ jsx("button", {
168
+ type: "button",
169
+ "aria-label": `Copy ${part.label.toLowerCase()}`,
170
+ "data-state": copy?.part === part.key ? copy.state : "idle",
171
+ className: cn("shrink-0 rounded-md border border-input bg-background px-2 py-1 text-xs font-medium", "hover:bg-accent hover:text-accent-foreground", focusRing),
172
+ onClick: () => {
173
+ copyPart(part.key, part.text);
174
+ },
175
+ children: copy?.part === part.key && copy.state === "copied" ? "Copied" : "Copy"
176
+ })]
177
+ }),
178
+ copy?.part === part.key && copy.state === "error" ? /* @__PURE__ */ jsx("p", {
179
+ "data-slot": "dns-record-copy-error",
180
+ className: "text-xs text-destructive",
181
+ children: "Could not copy. Select it and copy by hand."
182
+ }) : null,
183
+ part.note ? /* @__PURE__ */ jsx("p", {
184
+ className: "text-xs text-muted-foreground",
185
+ children: part.note
186
+ }) : null
187
+ ]
188
+ })]
189
+ }, part.key))
190
+ }),
191
+ status === "failed" && found && found.length > 0 ? /* @__PURE__ */ jsxs("div", {
192
+ "data-slot": "dns-record-found",
193
+ className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-xs",
194
+ children: [/* @__PURE__ */ jsx("p", {
195
+ className: "font-medium text-destructive",
196
+ children: "Found instead:"
197
+ }), /* @__PURE__ */ jsx("ul", {
198
+ className: "m-0 mt-1 list-none p-0",
199
+ children: found.map((entry) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", {
200
+ className: "font-mono break-all",
201
+ children: entry
202
+ }) }, entry))
203
+ })]
204
+ }) : null,
205
+ /* @__PURE__ */ jsx("span", {
206
+ role: "status",
207
+ "aria-live": "polite",
208
+ className: "sr-only",
209
+ children: copy?.state === "copied" ? `Copied ${copy.part}` : copy?.state === "error" ? `Could not copy ${copy.part}. Select it and copy by hand.` : ""
210
+ }),
211
+ onCheck ? /* @__PURE__ */ jsx("div", {
212
+ className: "flex items-center gap-2",
213
+ children: /* @__PURE__ */ jsx(Button, {
214
+ variant: "outline",
215
+ size: "sm",
216
+ loading: status === "checking",
217
+ onClick: onCheck,
218
+ children: status === "verified" ? "Check again" : "Check now"
219
+ })
220
+ }) : null
221
+ ]
222
+ });
223
+ }
224
+ //#endregion
225
+ export { DnsRecord, dnsHostForms };
226
+
227
+ //# sourceMappingURL=dns-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-record.js","names":[],"sources":["../../../src/components/dns-record/dns-record.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useEffect,\n useId,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\n\nimport { Button } from \"@/components/button\";\nimport { Spinner } from \"@/components/spinner\";\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * \"Add this record at your DNS provider.\"\n *\n * Vercel, Resend, Cloudflare, Postmark, Google Workspace and every product\n * that verifies a domain or routes its mail draws this card by hand, and\n * they all learn the same three things the hard way.\n *\n * The parts are copied separately. A provider's form has a Name field, a\n * Type field and a Value field, so one button that copies the whole line\n * copies something nobody can paste anywhere.\n *\n * The Name field is a trap. Some providers want the host relative to the\n * zone, `_dmarc`; some want the full name, `_dmarc.acme.com`; and some take\n * the relative form and append the zone themselves, so a full name pasted\n * in becomes `_dmarc.acme.com.acme.com` and the check fails for a reason\n * nobody can see. So the host is shown both ways, with the sentence that\n * explains which to use.\n *\n * A failed check says what was found. \"Not verified\" sends people back to\n * their provider to stare at a record that is correct and has not\n * propagated yet; \"found v=spf1 -all, expected v=spf1 include:…\" sends them\n * to the typo. And nothing found at all is said as nothing found, with how\n * long that can take, because it is not the same as wrong.\n */\n\nexport type DnsRecordStatus = \"pending\" | \"checking\" | \"verified\" | \"failed\";\n\nexport interface DnsHostForms {\n /** Relative to the zone: `_dmarc`, or `@` for the zone itself. */\n relative: string;\n /** Fully qualified: `_dmarc.acme.com`. */\n full: string;\n}\n\n/**\n * The host both ways a provider might want it. Accepts either form and a\n * trailing dot, so the record can be declared however the backend has it.\n */\nexport function dnsHostForms(name: string, zone: string): DnsHostForms {\n const cleanZone = zone.replace(/\\.$/, \"\");\n const cleanName = name.replace(/\\.$/, \"\");\n if (cleanName === \"@\" || cleanName === \"\" || cleanName === cleanZone) {\n return { relative: \"@\", full: cleanZone };\n }\n if (cleanName.endsWith(`.${cleanZone}`)) {\n return { relative: cleanName.slice(0, -cleanZone.length - 1), full: cleanName };\n }\n return { relative: cleanName, full: `${cleanName}.${cleanZone}` };\n}\n\nexport interface DnsRecordProps extends Omit<ComponentPropsWithRef<\"section\">, \"children\"> {\n type: string;\n /** The host, relative or fully qualified. */\n name: string;\n value: string;\n /** The zone the record belongs to. Enables the host to be shown both ways. */\n zone?: string;\n ttl?: number | string;\n priority?: number;\n /** What the record is for, in the reader's language: \"Proves you own acme.com\". */\n purpose?: string;\n status?: DnsRecordStatus;\n /** What the check found instead, when it failed. The useful part. */\n found?: string[];\n checkedAt?: Date;\n locale?: string;\n onCheck?: () => void;\n children?: ReactNode;\n}\n\ntype CopyState = \"idle\" | \"copied\" | \"error\";\n\nexport function DnsRecord({\n className,\n type,\n name,\n value,\n zone,\n ttl,\n priority,\n purpose,\n status = \"pending\",\n found,\n checkedAt,\n locale,\n onCheck,\n children,\n ...props\n}: DnsRecordProps) {\n const id = useId();\n const forms = zone ? dnsHostForms(name, zone) : null;\n const host = forms ? forms.relative : name;\n\n // Which part was copied, and whether it worked, so the button that was\n // pressed is the one that says so.\n const [copy, setCopy] = useState<{ part: string; state: CopyState } | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n },\n [],\n );\n\n const copyPart = (part: string, text: string) => {\n const settle = (state: CopyState) => {\n setCopy({ part, state });\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n setCopy(null);\n }, 2000);\n };\n Promise.resolve()\n .then(() => navigator.clipboard.writeText(text))\n .then(\n () => {\n settle(\"copied\");\n },\n () => {\n settle(\"error\");\n },\n );\n };\n\n const statusText =\n status === \"verified\"\n ? \"Verified\"\n : status === \"checking\"\n ? \"Checking…\"\n : status === \"failed\"\n ? found && found.length > 0\n ? \"Not verified. Something else was found.\"\n : \"Not verified. Nothing found yet — DNS changes can take up to 48 hours to appear.\"\n : \"Not checked yet\";\n\n const checked =\n checkedAt !== undefined\n ? new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeStyle: \"short\" }).format(\n checkedAt,\n )\n : null;\n\n const parts: { key: string; label: string; text: string; note?: ReactNode }[] = [\n { key: \"type\", label: \"Type\", text: type },\n {\n key: \"name\",\n label: \"Name\",\n text: host,\n note: forms ? (\n forms.relative === \"@\" ? (\n <>\n Some providers want <code className=\"font-mono\">{forms.full}</code> or leave it\n blank instead of <code className=\"font-mono\">@</code>.\n </>\n ) : (\n <>\n Some providers want the full name, <code className=\"font-mono\">{forms.full}</code>.\n If the field already ends in <code className=\"font-mono\">{zone}</code>, enter just\n the part shown.\n </>\n )\n ) : undefined,\n },\n {\n key: \"value\",\n label: \"Value\",\n text: value,\n note:\n type.toUpperCase() === \"TXT\"\n ? \"Enter without quotes; your provider adds them if it needs to.\"\n : undefined,\n },\n ...(priority !== undefined\n ? [{ key: \"priority\", label: \"Priority\", text: String(priority) }]\n : []),\n ...(ttl !== undefined ? [{ key: \"ttl\", label: \"TTL\", text: String(ttl) }] : []),\n ];\n\n return (\n <section\n data-slot=\"dns-record\"\n data-status={status}\n aria-labelledby={`${id}-heading`}\n aria-busy={status === \"checking\" || undefined}\n className={cn(\n \"flex flex-col gap-3 rounded-lg border border-border bg-card p-4\",\n className,\n )}\n {...props}\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h3 id={`${id}-heading`} className=\"text-sm font-medium\">\n {type} record\n {purpose ? (\n <span className=\"font-normal text-muted-foreground\"> · {purpose}</span>\n ) : null}\n </h3>\n {/* Status as a word, and announced when it changes: a check was asked\n for, and the answer should arrive where the asker is. */}\n <p\n data-slot=\"dns-record-status\"\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"flex items-center gap-1.5 text-xs\",\n status === \"verified\" && \"text-success\",\n status === \"failed\" && \"text-destructive\",\n (status === \"pending\" || status === \"checking\") && \"text-muted-foreground\",\n )}\n >\n {status === \"checking\" ? <Spinner size=\"sm\" /> : null}\n {statusText}\n {checked && status !== \"checking\" ? (\n <span className=\"text-muted-foreground\"> · checked {checked}</span>\n ) : null}\n </p>\n </div>\n\n {children}\n\n <dl className=\"m-0 grid gap-x-4 gap-y-2 sm:grid-cols-[auto_1fr]\">\n {parts.map((part) => (\n <div key={part.key} className=\"contents\">\n <dt className=\"text-xs font-medium text-muted-foreground sm:pt-1.5\">\n {part.label}\n </dt>\n <dd className=\"m-0 flex flex-col gap-1\">\n <div className=\"flex items-start gap-2\">\n <code\n data-slot=\"dns-record-part\"\n data-part={part.key}\n className=\"min-w-0 flex-1 rounded-md bg-muted px-2 py-1 font-mono text-xs break-all\"\n >\n {part.text}\n </code>\n <button\n type=\"button\"\n aria-label={`Copy ${part.label.toLowerCase()}`}\n data-state={copy?.part === part.key ? copy.state : \"idle\"}\n className={cn(\n \"shrink-0 rounded-md border border-input bg-background px-2 py-1 text-xs font-medium\",\n \"hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n )}\n onClick={() => {\n copyPart(part.key, part.text);\n }}\n >\n {copy?.part === part.key && copy.state === \"copied\" ? \"Copied\" : \"Copy\"}\n </button>\n </div>\n {copy?.part === part.key && copy.state === \"error\" ? (\n <p data-slot=\"dns-record-copy-error\" className=\"text-xs text-destructive\">\n Could not copy. Select it and copy by hand.\n </p>\n ) : null}\n {part.note ? <p className=\"text-xs text-muted-foreground\">{part.note}</p> : null}\n </dd>\n </div>\n ))}\n </dl>\n\n {status === \"failed\" && found && found.length > 0 ? (\n <div\n data-slot=\"dns-record-found\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 p-3 text-xs\"\n >\n <p className=\"font-medium text-destructive\">Found instead:</p>\n <ul className=\"m-0 mt-1 list-none p-0\">\n {found.map((entry) => (\n <li key={entry}>\n <code className=\"font-mono break-all\">{entry}</code>\n </li>\n ))}\n </ul>\n </div>\n ) : null}\n\n {/* A separate region from the verification status, so \"Copied name\"\n never replaces or interrupts a check result that is being read.\n Present from the start, so the first copy is heard. */}\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {copy?.state === \"copied\"\n ? `Copied ${copy.part}`\n : copy?.state === \"error\"\n ? `Could not copy ${copy.part}. Select it and copy by hand.`\n : \"\"}\n </span>\n\n {onCheck ? (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" loading={status === \"checking\"} onClick={onCheck}>\n {status === \"verified\" ? \"Check again\" : \"Check now\"}\n </Button>\n </div>\n ) : null}\n </section>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { DnsHostForms, DnsRecord, DnsRecordProps, DnsRecordStatus, dnsHostForms } from "./dns-record.js";
2
+ export { type DnsHostForms, DnsRecord, type DnsRecordProps, type DnsRecordStatus, dnsHostForms };
@@ -0,0 +1,2 @@
1
+ import { DnsRecord, dnsHostForms } from "./dns-record.js";
2
+ export { DnsRecord, dnsHostForms };
@@ -0,0 +1,17 @@
1
+ import { defineMeta } from "../../registry/schema.js";
2
+ //#region src/components/dns-record/meta.ts
3
+ const meta = defineMeta({
4
+ name: "dns-record",
5
+ title: "DNS Record",
6
+ description: "Add this record at your provider: parts copied separately, checked with what was found.",
7
+ category: "data",
8
+ status: "stable",
9
+ dependencies: [],
10
+ registryDependencies: ["button", "spinner"],
11
+ files: ["dns-record.tsx"],
12
+ a11y: "The card is a region named by the record's type and purpose. Each part is a definition with its own copy button, named by the part it copies, so five Copy buttons are five different buttons. Verification status is a word in a polite live region, so the answer to a check arrives where it was asked for, and the card is aria-busy while checking. Copy results are announced in a separate region, present from the start, so a copy never replaces a check result mid-sentence; failure is announced with what to do instead. What the check found is a list, not a colour."
13
+ });
14
+ //#endregion
15
+ export { meta };
16
+
17
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/dns-record/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"dns-record\",\n title: \"DNS Record\",\n description:\n \"Add this record at your provider: parts copied separately, checked with what was found.\",\n category: \"data\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [\"button\", \"spinner\"],\n files: [\"dns-record.tsx\"],\n a11y:\n \"The card is a region named by the record's type and purpose. Each part is a definition \" +\n \"with its own copy button, named by the part it copies, so five Copy buttons are five \" +\n \"different buttons. Verification status is a word in a polite live region, so the answer to \" +\n \"a check arrives where it was asked for, and the card is aria-busy while checking. Copy \" +\n \"results are announced in a separate region, present from the start, so a copy never \" +\n \"replaces a check result mid-sentence; failure is announced with what to do instead. What \" +\n \"the check found is a list, not a colour.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC,UAAU,SAAS;CAC1C,OAAO,CAAC,gBAAgB;CACxB,MACE;AAOJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { CellState, Grants, GroupState, Permission, PermissionGroup, Role, cellState, countGranted, groupPermissions, groupState, inheritedFrom } from "./permission-model.js";
2
+ import { PermissionMatrix, PermissionMatrixProps } from "./permission-matrix.js";
3
+ export { type CellState, type Grants, type GroupState, type Permission, type PermissionGroup, PermissionMatrix, type PermissionMatrixProps, type Role, cellState, countGranted, groupPermissions, groupState, inheritedFrom };
@@ -0,0 +1,3 @@
1
+ import { cellState, countGranted, groupPermissions, groupState, inheritedFrom } from "./permission-model.js";
2
+ import { PermissionMatrix } from "./permission-matrix.js";
3
+ export { PermissionMatrix, cellState, countGranted, groupPermissions, groupState, inheritedFrom };
@@ -0,0 +1,17 @@
1
+ import { defineMeta } from "../../registry/schema.js";
2
+ //#region src/components/permission-matrix/meta.ts
3
+ const meta = defineMeta({
4
+ name: "permission-matrix",
5
+ title: "Permission Matrix",
6
+ description: "Roles across, permissions down, with inheritance, locked roles and one tab stop.",
7
+ category: "data",
8
+ status: "stable",
9
+ dependencies: [],
10
+ registryDependencies: ["checkbox"],
11
+ files: ["permission-model.ts", "permission-matrix.tsx"],
12
+ a11y: "A grid in the WAI-ARIA sense: role=\"grid\" with one tab stop, arrow keys between cells, Home and End along a row and Ctrl with them across the whole matrix — the right call for a matrix that is operated, where it was the wrong one for a diff that is read. Every checkbox is named by both coordinates, \"Delete projects for Editor\", so a reader arriving by arrow key knows where they are without re-reading the headers. An inherited grant is a checked box that cannot be changed here, marked aria-disabled rather than disabled so it stays in the keyboard path, with the role it came from as its description. A locked role says in each cell's description that nothing can be changed. Group toggles are tri-state, and the column header counts each role's grants in text."
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/permission-matrix/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"permission-matrix\",\n title: \"Permission Matrix\",\n description:\n \"Roles across, permissions down, with inheritance, locked roles and one tab stop.\",\n category: \"data\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [\"checkbox\"],\n files: [\"permission-model.ts\", \"permission-matrix.tsx\"],\n a11y:\n 'A grid in the WAI-ARIA sense: role=\"grid\" with one tab stop, arrow keys between cells, Home ' +\n \"and End along a row and Ctrl with them across the whole matrix — the right call for a \" +\n \"matrix that is operated, where it was the wrong one for a diff that is read. Every checkbox \" +\n 'is named by both coordinates, \"Delete projects for Editor\", so a reader arriving by arrow ' +\n \"key knows where they are without re-reading the headers. An inherited grant is a checked \" +\n \"box that cannot be changed here, marked aria-disabled rather than disabled so it stays in \" +\n \"the keyboard path, with the role it came from as its description. A locked role says in \" +\n \"each cell's description that nothing can be changed. Group toggles are tri-state, and the \" +\n \"column header counts each role's grants in text.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC,UAAU;CACjC,OAAO,CAAC,uBAAuB,uBAAuB;CACtD,MACE;AASJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { Grants, Permission, Role } from "./permission-model.js";
2
+ import { ComponentPropsWithRef } from "react";
3
+ //#region src/components/permission-matrix/permission-matrix.d.ts
4
+ /**
5
+ * Roles across, permissions down, a checkbox at every crossing.
6
+ *
7
+ * Every admin panel has one and every admin panel builds it, because the
8
+ * hard part is not the checkboxes. It is that a role can inherit from
9
+ * another, so a box can be ticked without anyone having ticked it; that an
10
+ * Owner has everything and none of it can be unticked; that a section of
11
+ * eight permissions wants one control for all of them; and that sixty
12
+ * checkboxes are sixty tab stops unless something is done about it.
13
+ *
14
+ * Something is done about it. This is a grid in the WAI-ARIA sense — one tab
15
+ * stop, arrow keys between cells, Home and End along a row — which is the
16
+ * right call here and was the wrong one for the diff viewer: a diff is read,
17
+ * a matrix is operated. Every checkbox is named by both coordinates, "Delete
18
+ * projects for Editor", so a reader arriving by arrow key knows where they
19
+ * are without re-reading the headers.
20
+ *
21
+ * A grant that arrived by inheritance is shown as a checked box that cannot
22
+ * be unchecked here, with the role it came from said beside it and in the
23
+ * box's description. A disabled control would be the obvious rendering, and
24
+ * it would take the box out of the tab order and the arrow-key path, so a
25
+ * keyboard user would step over the one cell whose state needs explaining.
26
+ *
27
+ * Changes are reported, never applied. A group toggle reports every
28
+ * permission it touched in one call, so an application can save it as one
29
+ * change rather than eight.
30
+ */
31
+ interface PermissionMatrixProps extends Omit<ComponentPropsWithRef<"table">, "onChange"> {
32
+ /** Names the matrix: "Permissions for Acme". */
33
+ label: string;
34
+ roles: Role[];
35
+ permissions: Permission[];
36
+ grants: Grants;
37
+ /**
38
+ * A change to make. Several ids arrive together when a group toggle was
39
+ * used. Omit to render the matrix as a record.
40
+ */
41
+ onChange?: (roleId: string, permissionIds: string[], granted: boolean) => void;
42
+ }
43
+ declare function PermissionMatrix({ className, label, roles, permissions, grants, onChange, ...props }: PermissionMatrixProps): import("react").JSX.Element;
44
+ //#endregion
45
+ export { PermissionMatrix, PermissionMatrixProps };
46
+ //# sourceMappingURL=permission-matrix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-matrix.d.ts","names":[],"sources":["../../../src/components/permission-matrix/permission-matrix.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAmDiB,8BAA8B,KAC7C;;EAIA;EACA,OAAO;EACP,aAAa;EACb,QAAQ;;;;;EAKR,YAAY,gBAAgB,yBAAyB;;iBAcvC,mBACd,WACA,OACA,OACA,aACA,QACA,aACG,SACF,wCAAqB,IAAA"}
@@ -0,0 +1,199 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { Checkbox } from "../checkbox/checkbox.js";
4
+ import { cellState, countGranted, groupPermissions, groupState } from "./permission-model.js";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { useId, useMemo, useState } from "react";
7
+ //#region src/components/permission-matrix/permission-matrix.tsx
8
+ const ARROWS = {
9
+ ArrowUp: [-1, 0],
10
+ ArrowDown: [1, 0],
11
+ ArrowLeft: [0, -1],
12
+ ArrowRight: [0, 1]
13
+ };
14
+ function PermissionMatrix({ className, label, roles, permissions, grants, onChange, ...props }) {
15
+ const uid = useId();
16
+ const rows = useMemo(() => groupPermissions(permissions).flatMap((group) => [...group.group !== void 0 ? [{
17
+ kind: "group",
18
+ label: group.group,
19
+ permissions: group.permissions
20
+ }] : [], ...group.permissions.map((permission) => ({
21
+ kind: "permission",
22
+ permission
23
+ }))]), [permissions]);
24
+ const [active, setActive] = useState([0, 0]);
25
+ const onKeyDown = (event) => {
26
+ const target = event.target;
27
+ const row = Number(target.dataset.row);
28
+ const col = Number(target.dataset.col);
29
+ if (Number.isNaN(row) || Number.isNaN(col)) return;
30
+ let next = null;
31
+ const arrow = ARROWS[event.key];
32
+ if (arrow) next = [row + arrow[0], col + arrow[1]];
33
+ else if (event.key === "Home") next = event.ctrlKey ? [0, 0] : [row, 0];
34
+ else if (event.key === "End") next = event.ctrlKey ? [rows.length - 1, roles.length - 1] : [row, roles.length - 1];
35
+ if (!next) return;
36
+ const [nextRow, nextCol] = next;
37
+ if (nextRow < 0 || nextRow >= rows.length || nextCol < 0 || nextCol >= roles.length) {
38
+ event.preventDefault();
39
+ return;
40
+ }
41
+ event.preventDefault();
42
+ event.currentTarget.querySelector(`[data-row="${String(nextRow)}"][data-col="${String(nextCol)}"]`)?.focus();
43
+ };
44
+ return /* @__PURE__ */ jsx("div", {
45
+ className: "relative w-full overflow-x-auto",
46
+ children: /* @__PURE__ */ jsxs("table", {
47
+ "data-slot": "permission-matrix",
48
+ role: "grid",
49
+ "aria-label": label,
50
+ "aria-readonly": onChange ? void 0 : true,
51
+ onKeyDown,
52
+ className: cn("w-full border-collapse text-sm", className),
53
+ ...props,
54
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
55
+ role: "row",
56
+ className: "border-b border-border",
57
+ children: [/* @__PURE__ */ jsx("th", {
58
+ role: "columnheader",
59
+ scope: "col",
60
+ className: "px-3 py-2 text-left text-xs font-medium",
61
+ children: "Permission"
62
+ }), roles.map((role) => {
63
+ const granted = countGranted(roles, grants, role, permissions);
64
+ return /* @__PURE__ */ jsxs("th", {
65
+ role: "columnheader",
66
+ scope: "col",
67
+ className: "px-3 py-2 text-center text-xs font-medium",
68
+ children: [role.label, /* @__PURE__ */ jsx("span", {
69
+ className: "block font-normal text-muted-foreground",
70
+ children: role.locked ? "all, locked" : /* @__PURE__ */ jsxs(Fragment, { children: [
71
+ String(granted),
72
+ "/",
73
+ String(permissions.length),
74
+ /* @__PURE__ */ jsx("span", {
75
+ className: "sr-only",
76
+ children: " granted"
77
+ })
78
+ ] })
79
+ })]
80
+ }, role.id);
81
+ })]
82
+ }) }), /* @__PURE__ */ jsx("tbody", { children: rows.map((row, rowIndex) => row.kind === "group" ? /* @__PURE__ */ jsxs("tr", {
83
+ role: "row",
84
+ className: "border-b border-border bg-muted/40",
85
+ children: [/* @__PURE__ */ jsxs("th", {
86
+ role: "rowheader",
87
+ scope: "row",
88
+ className: "px-3 py-1.5 text-left text-xs font-medium",
89
+ children: [row.label, /* @__PURE__ */ jsx("span", {
90
+ className: "sr-only",
91
+ children: ", all"
92
+ })]
93
+ }), roles.map((role, colIndex) => /* @__PURE__ */ jsx(GroupCell, {
94
+ role,
95
+ roles,
96
+ grants,
97
+ group: row.label,
98
+ permissions: row.permissions,
99
+ position: [rowIndex, colIndex],
100
+ active: active[0] === rowIndex && active[1] === colIndex,
101
+ onActivate: setActive,
102
+ onChange
103
+ }, role.id))]
104
+ }, `group-${row.label}`) : /* @__PURE__ */ jsxs("tr", {
105
+ role: "row",
106
+ className: "border-b border-border last:border-0 hover:bg-muted/30",
107
+ children: [/* @__PURE__ */ jsxs("th", {
108
+ role: "rowheader",
109
+ scope: "row",
110
+ className: cn("px-3 py-2 text-left font-normal", row.permission.group !== void 0 && "pl-6"),
111
+ children: [row.permission.label, row.permission.description ? /* @__PURE__ */ jsx("span", {
112
+ className: "block text-xs text-muted-foreground",
113
+ children: row.permission.description
114
+ }) : null]
115
+ }), roles.map((role, colIndex) => /* @__PURE__ */ jsx(PermissionCell, {
116
+ id: `${uid}-${role.id}-${row.permission.id}`,
117
+ role,
118
+ roles,
119
+ grants,
120
+ permission: row.permission,
121
+ position: [rowIndex, colIndex],
122
+ active: active[0] === rowIndex && active[1] === colIndex,
123
+ onActivate: setActive,
124
+ onChange
125
+ }, role.id))]
126
+ }, row.permission.id)) })]
127
+ })
128
+ });
129
+ }
130
+ function PermissionCell({ id, role, roles, grants, permission, position, active, onActivate, onChange }) {
131
+ const state = cellState(roles, grants, role, permission.id);
132
+ const fixed = state.fixed || !onChange;
133
+ const noteId = `${id}-note`;
134
+ const note = role.locked ? `${role.label} has every permission; this cannot be changed.` : state.inheritedFrom ? `Inherited from ${state.inheritedFrom.label}.` : null;
135
+ return /* @__PURE__ */ jsxs("td", {
136
+ role: "gridcell",
137
+ "data-slot": "permission-cell",
138
+ "data-granted": state.granted || void 0,
139
+ "data-inherited": state.inheritedFrom ? "" : void 0,
140
+ className: "px-3 py-2 text-center align-middle",
141
+ children: [/* @__PURE__ */ jsx(Checkbox, {
142
+ id,
143
+ checked: state.granted,
144
+ "aria-label": `${permission.label} for ${role.label}`,
145
+ "aria-describedby": note ? noteId : void 0,
146
+ "aria-disabled": fixed || void 0,
147
+ tabIndex: active ? 0 : -1,
148
+ "data-row": position[0],
149
+ "data-col": position[1],
150
+ className: cn(fixed && "opacity-55"),
151
+ onFocus: () => {
152
+ onActivate(position);
153
+ },
154
+ onCheckedChange: (next) => {
155
+ if (fixed) return;
156
+ onChange?.(role.id, [permission.id], next === true);
157
+ }
158
+ }), state.inheritedFrom ? /* @__PURE__ */ jsxs("span", {
159
+ id: noteId,
160
+ className: "mt-0.5 block text-2xs text-muted-foreground",
161
+ children: ["via ", state.inheritedFrom.label]
162
+ }) : note ? /* @__PURE__ */ jsx("span", {
163
+ id: noteId,
164
+ className: "sr-only",
165
+ children: note
166
+ }) : null]
167
+ });
168
+ }
169
+ function GroupCell({ role, roles, grants, group, permissions, position, active, onActivate, onChange }) {
170
+ const ids = permissions.map((permission) => permission.id);
171
+ const state = groupState(roles, grants, role, ids);
172
+ const changeable = permissions.filter((permission) => !cellState(roles, grants, role, permission.id).fixed).map((permission) => permission.id);
173
+ const fixed = role.locked || !onChange || changeable.length === 0;
174
+ return /* @__PURE__ */ jsx("td", {
175
+ role: "gridcell",
176
+ "data-slot": "permission-group-cell",
177
+ className: "px-3 py-1.5 text-center align-middle",
178
+ children: /* @__PURE__ */ jsx(Checkbox, {
179
+ checked: state === "all" ? true : state === "some" ? "indeterminate" : false,
180
+ "aria-label": `All ${group} for ${role.label}`,
181
+ "aria-disabled": fixed || void 0,
182
+ tabIndex: active ? 0 : -1,
183
+ "data-row": position[0],
184
+ "data-col": position[1],
185
+ className: cn(fixed && "opacity-55"),
186
+ onFocus: () => {
187
+ onActivate(position);
188
+ },
189
+ onCheckedChange: (next) => {
190
+ if (fixed) return;
191
+ onChange?.(role.id, changeable, next === true);
192
+ }
193
+ })
194
+ });
195
+ }
196
+ //#endregion
197
+ export { PermissionMatrix };
198
+
199
+ //# sourceMappingURL=permission-matrix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-matrix.js","names":[],"sources":["../../../src/components/permission-matrix/permission-matrix.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useId,\n useMemo,\n useState,\n type ComponentPropsWithRef,\n type KeyboardEvent,\n} from \"react\";\n\nimport { Checkbox } from \"@/components/checkbox\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n cellState,\n countGranted,\n groupPermissions,\n groupState,\n type Grants,\n type Permission,\n type Role,\n} from \"./permission-model\";\n\n/**\n * Roles across, permissions down, a checkbox at every crossing.\n *\n * Every admin panel has one and every admin panel builds it, because the\n * hard part is not the checkboxes. It is that a role can inherit from\n * another, so a box can be ticked without anyone having ticked it; that an\n * Owner has everything and none of it can be unticked; that a section of\n * eight permissions wants one control for all of them; and that sixty\n * checkboxes are sixty tab stops unless something is done about it.\n *\n * Something is done about it. This is a grid in the WAI-ARIA sense — one tab\n * stop, arrow keys between cells, Home and End along a row — which is the\n * right call here and was the wrong one for the diff viewer: a diff is read,\n * a matrix is operated. Every checkbox is named by both coordinates, \"Delete\n * projects for Editor\", so a reader arriving by arrow key knows where they\n * are without re-reading the headers.\n *\n * A grant that arrived by inheritance is shown as a checked box that cannot\n * be unchecked here, with the role it came from said beside it and in the\n * box's description. A disabled control would be the obvious rendering, and\n * it would take the box out of the tab order and the arrow-key path, so a\n * keyboard user would step over the one cell whose state needs explaining.\n *\n * Changes are reported, never applied. A group toggle reports every\n * permission it touched in one call, so an application can save it as one\n * change rather than eight.\n */\n\nexport interface PermissionMatrixProps extends Omit<\n ComponentPropsWithRef<\"table\">,\n \"onChange\"\n> {\n /** Names the matrix: \"Permissions for Acme\". */\n label: string;\n roles: Role[];\n permissions: Permission[];\n grants: Grants;\n /**\n * A change to make. Several ids arrive together when a group toggle was\n * used. Omit to render the matrix as a record.\n */\n onChange?: (roleId: string, permissionIds: string[], granted: boolean) => void;\n}\n\ntype MatrixRow =\n | { kind: \"group\"; label: string; permissions: Permission[] }\n | { kind: \"permission\"; permission: Permission };\n\nconst ARROWS: Record<string, [number, number]> = {\n ArrowUp: [-1, 0],\n ArrowDown: [1, 0],\n ArrowLeft: [0, -1],\n ArrowRight: [0, 1],\n};\n\nexport function PermissionMatrix({\n className,\n label,\n roles,\n permissions,\n grants,\n onChange,\n ...props\n}: PermissionMatrixProps) {\n const uid = useId();\n\n const rows = useMemo<MatrixRow[]>(\n () =>\n groupPermissions(permissions).flatMap((group): MatrixRow[] => [\n ...(group.group !== undefined\n ? [{ kind: \"group\" as const, label: group.group, permissions: group.permissions }]\n : []),\n ...group.permissions.map((permission) => ({ kind: \"permission\" as const, permission })),\n ]),\n [permissions],\n );\n\n // One tab stop. The cell that last had focus is the one Tab returns to.\n const [active, setActive] = useState<[number, number]>([0, 0]);\n\n const onKeyDown = (event: KeyboardEvent<HTMLTableElement>) => {\n const target = event.target as HTMLElement;\n const row = Number(target.dataset.row);\n const col = Number(target.dataset.col);\n if (Number.isNaN(row) || Number.isNaN(col)) return;\n\n let next: [number, number] | null = null;\n const arrow = ARROWS[event.key];\n if (arrow) {\n next = [row + arrow[0], col + arrow[1]];\n } else if (event.key === \"Home\") {\n next = event.ctrlKey ? [0, 0] : [row, 0];\n } else if (event.key === \"End\") {\n next = event.ctrlKey ? [rows.length - 1, roles.length - 1] : [row, roles.length - 1];\n }\n if (!next) return;\n\n const [nextRow, nextCol] = next;\n if (nextRow < 0 || nextRow >= rows.length || nextCol < 0 || nextCol >= roles.length) {\n event.preventDefault();\n return;\n }\n event.preventDefault();\n const cell = event.currentTarget.querySelector<HTMLElement>(\n `[data-row=\"${String(nextRow)}\"][data-col=\"${String(nextCol)}\"]`,\n );\n cell?.focus();\n };\n\n return (\n <div className=\"relative w-full overflow-x-auto\">\n <table\n data-slot=\"permission-matrix\"\n role=\"grid\"\n aria-label={label}\n aria-readonly={onChange ? undefined : true}\n onKeyDown={onKeyDown}\n className={cn(\"w-full border-collapse text-sm\", className)}\n {...props}\n >\n <thead>\n <tr role=\"row\" className=\"border-b border-border\">\n <th\n role=\"columnheader\"\n scope=\"col\"\n className=\"px-3 py-2 text-left text-xs font-medium\"\n >\n Permission\n </th>\n {roles.map((role) => {\n const granted = countGranted(roles, grants, role, permissions);\n return (\n <th\n key={role.id}\n role=\"columnheader\"\n scope=\"col\"\n className=\"px-3 py-2 text-center text-xs font-medium\"\n >\n {role.label}\n <span className=\"block font-normal text-muted-foreground\">\n {role.locked ? (\n \"all, locked\"\n ) : (\n <>\n {String(granted)}/{String(permissions.length)}\n <span className=\"sr-only\"> granted</span>\n </>\n )}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {rows.map((row, rowIndex) =>\n row.kind === \"group\" ? (\n <tr\n key={`group-${row.label}`}\n role=\"row\"\n className=\"border-b border-border bg-muted/40\"\n >\n <th\n role=\"rowheader\"\n scope=\"row\"\n className=\"px-3 py-1.5 text-left text-xs font-medium\"\n >\n {row.label}\n <span className=\"sr-only\">, all</span>\n </th>\n {roles.map((role, colIndex) => (\n <GroupCell\n key={role.id}\n role={role}\n roles={roles}\n grants={grants}\n group={row.label}\n permissions={row.permissions}\n position={[rowIndex, colIndex]}\n active={active[0] === rowIndex && active[1] === colIndex}\n onActivate={setActive}\n onChange={onChange}\n />\n ))}\n </tr>\n ) : (\n <tr\n key={row.permission.id}\n role=\"row\"\n className=\"border-b border-border last:border-0 hover:bg-muted/30\"\n >\n <th\n role=\"rowheader\"\n scope=\"row\"\n className={cn(\n \"px-3 py-2 text-left font-normal\",\n row.permission.group !== undefined && \"pl-6\",\n )}\n >\n {row.permission.label}\n {row.permission.description ? (\n <span className=\"block text-xs text-muted-foreground\">\n {row.permission.description}\n </span>\n ) : null}\n </th>\n {roles.map((role, colIndex) => (\n <PermissionCell\n key={role.id}\n id={`${uid}-${role.id}-${row.permission.id}`}\n role={role}\n roles={roles}\n grants={grants}\n permission={row.permission}\n position={[rowIndex, colIndex]}\n active={active[0] === rowIndex && active[1] === colIndex}\n onActivate={setActive}\n onChange={onChange}\n />\n ))}\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n );\n}\n\ninterface CellProps {\n role: Role;\n roles: Role[];\n grants: Grants;\n position: [number, number];\n active: boolean;\n onActivate: (position: [number, number]) => void;\n onChange?: (roleId: string, permissionIds: string[], granted: boolean) => void;\n}\n\nfunction PermissionCell({\n id,\n role,\n roles,\n grants,\n permission,\n position,\n active,\n onActivate,\n onChange,\n}: CellProps & { id: string; permission: Permission }) {\n const state = cellState(roles, grants, role, permission.id);\n const fixed = state.fixed || !onChange;\n const noteId = `${id}-note`;\n\n const note = role.locked\n ? `${role.label} has every permission; this cannot be changed.`\n : state.inheritedFrom\n ? `Inherited from ${state.inheritedFrom.label}.`\n : null;\n\n return (\n <td\n role=\"gridcell\"\n data-slot=\"permission-cell\"\n data-granted={state.granted || undefined}\n data-inherited={state.inheritedFrom ? \"\" : undefined}\n className=\"px-3 py-2 text-center align-middle\"\n >\n <Checkbox\n id={id}\n checked={state.granted}\n aria-label={`${permission.label} for ${role.label}`}\n aria-describedby={note ? noteId : undefined}\n // Fixed cells stay focusable, so the arrow-key path and the tab order\n // do not step over the one cell whose state needs explaining.\n aria-disabled={fixed || undefined}\n tabIndex={active ? 0 : -1}\n data-row={position[0]}\n data-col={position[1]}\n className={cn(fixed && \"opacity-55\")}\n onFocus={() => {\n onActivate(position);\n }}\n onCheckedChange={(next) => {\n if (fixed) return;\n onChange?.(role.id, [permission.id], next === true);\n }}\n />\n {state.inheritedFrom ? (\n <span id={noteId} className=\"mt-0.5 block text-2xs text-muted-foreground\">\n via {state.inheritedFrom.label}\n </span>\n ) : note ? (\n <span id={noteId} className=\"sr-only\">\n {note}\n </span>\n ) : null}\n </td>\n );\n}\n\nfunction GroupCell({\n role,\n roles,\n grants,\n group,\n permissions,\n position,\n active,\n onActivate,\n onChange,\n}: CellProps & { group: string; permissions: Permission[] }) {\n const ids = permissions.map((permission) => permission.id);\n const state = groupState(roles, grants, role, ids);\n // Only what can actually change here: inherited grants stay either way.\n const changeable = permissions\n .filter((permission) => !cellState(roles, grants, role, permission.id).fixed)\n .map((permission) => permission.id);\n const fixed = role.locked || !onChange || changeable.length === 0;\n\n return (\n <td\n role=\"gridcell\"\n data-slot=\"permission-group-cell\"\n className=\"px-3 py-1.5 text-center align-middle\"\n >\n <Checkbox\n checked={state === \"all\" ? true : state === \"some\" ? \"indeterminate\" : false}\n aria-label={`All ${group} for ${role.label}`}\n aria-disabled={fixed || undefined}\n tabIndex={active ? 0 : -1}\n data-row={position[0]}\n data-col={position[1]}\n className={cn(fixed && \"opacity-55\")}\n onFocus={() => {\n onActivate(position);\n }}\n onCheckedChange={(next) => {\n if (fixed) return;\n onChange?.(role.id, changeable, next === true);\n }}\n />\n </td>\n );\n}\n"],"mappings":"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"}