@inshorts/glint-ui 0.5.4 → 0.6.1

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 (340) hide show
  1. package/README.md +3 -1
  2. package/dist/alert.cjs +96 -0
  3. package/dist/alert.cjs.map +1 -0
  4. package/dist/alert.d.cts +29 -0
  5. package/dist/alert.d.ts +29 -0
  6. package/dist/alert.js +10 -0
  7. package/dist/alert.js.map +1 -0
  8. package/dist/autocomplete.cjs +578 -0
  9. package/dist/autocomplete.cjs.map +1 -0
  10. package/dist/autocomplete.d.cts +56 -0
  11. package/dist/autocomplete.d.ts +56 -0
  12. package/dist/autocomplete.js +11 -0
  13. package/dist/autocomplete.js.map +1 -0
  14. package/dist/badge.cjs +89 -0
  15. package/dist/badge.cjs.map +1 -0
  16. package/dist/badge.d.cts +25 -0
  17. package/dist/badge.d.ts +25 -0
  18. package/dist/badge.js +10 -0
  19. package/dist/badge.js.map +1 -0
  20. package/dist/breadcrumb.cjs +88 -0
  21. package/dist/breadcrumb.cjs.map +1 -0
  22. package/dist/breadcrumb.d.cts +14 -0
  23. package/dist/breadcrumb.d.ts +14 -0
  24. package/dist/breadcrumb.js +8 -0
  25. package/dist/breadcrumb.js.map +1 -0
  26. package/dist/button.cjs +143 -0
  27. package/dist/button.cjs.map +1 -0
  28. package/dist/button.d.cts +34 -0
  29. package/dist/button.d.ts +34 -0
  30. package/dist/button.js +9 -0
  31. package/dist/button.js.map +1 -0
  32. package/dist/card.cjs +111 -0
  33. package/dist/card.cjs.map +1 -0
  34. package/dist/card.d.cts +15 -0
  35. package/dist/card.d.ts +15 -0
  36. package/dist/card.js +18 -0
  37. package/dist/card.js.map +1 -0
  38. package/dist/checkbox.cjs +66 -0
  39. package/dist/checkbox.cjs.map +1 -0
  40. package/{src/checkbox.tsx → dist/checkbox.d.cts} +5 -22
  41. package/dist/checkbox.d.ts +25 -0
  42. package/dist/checkbox.js +10 -0
  43. package/dist/checkbox.js.map +1 -0
  44. package/dist/chunk-5MEOZHJA.js +513 -0
  45. package/dist/chunk-5MEOZHJA.js.map +1 -0
  46. package/dist/chunk-67ACNS42.js +29 -0
  47. package/dist/chunk-67ACNS42.js.map +1 -0
  48. package/dist/chunk-A6GRLKOO.js +85 -0
  49. package/dist/chunk-A6GRLKOO.js.map +1 -0
  50. package/dist/chunk-DS3QYIKL.js +90 -0
  51. package/dist/chunk-DS3QYIKL.js.map +1 -0
  52. package/dist/chunk-EWSBFU54.js +53 -0
  53. package/dist/chunk-EWSBFU54.js.map +1 -0
  54. package/dist/chunk-G4ZQUSAJ.js +38 -0
  55. package/dist/chunk-G4ZQUSAJ.js.map +1 -0
  56. package/dist/chunk-GMCUXEFI.js +67 -0
  57. package/dist/chunk-GMCUXEFI.js.map +1 -0
  58. package/dist/chunk-HBBOR43A.js +85 -0
  59. package/dist/chunk-HBBOR43A.js.map +1 -0
  60. package/dist/chunk-HCA5ODCD.js +11 -0
  61. package/dist/chunk-HCA5ODCD.js.map +1 -0
  62. package/dist/chunk-HYRJYUPI.js +26 -0
  63. package/dist/chunk-HYRJYUPI.js.map +1 -0
  64. package/dist/chunk-IRCTGJQB.js +26 -0
  65. package/dist/chunk-IRCTGJQB.js.map +1 -0
  66. package/dist/chunk-ISPICB4W.js +70 -0
  67. package/dist/chunk-ISPICB4W.js.map +1 -0
  68. package/dist/chunk-JDE5XYPT.js +30 -0
  69. package/dist/chunk-JDE5XYPT.js.map +1 -0
  70. package/dist/chunk-JSJEEKR3.js +59 -0
  71. package/dist/chunk-JSJEEKR3.js.map +1 -0
  72. package/dist/chunk-KR3BCFP7.js +85 -0
  73. package/dist/chunk-KR3BCFP7.js.map +1 -0
  74. package/dist/chunk-MLRSOTFI.js +81 -0
  75. package/dist/chunk-MLRSOTFI.js.map +1 -0
  76. package/dist/chunk-MQT5RAQV.js +202 -0
  77. package/dist/chunk-MQT5RAQV.js.map +1 -0
  78. package/dist/chunk-MUPW2TFJ.js +102 -0
  79. package/dist/chunk-MUPW2TFJ.js.map +1 -0
  80. package/dist/chunk-OUYT6SPU.js +76 -0
  81. package/dist/chunk-OUYT6SPU.js.map +1 -0
  82. package/dist/chunk-PM2VE7DJ.js +100 -0
  83. package/dist/chunk-PM2VE7DJ.js.map +1 -0
  84. package/dist/chunk-PPYR3O3L.js +44 -0
  85. package/dist/chunk-PPYR3O3L.js.map +1 -0
  86. package/dist/chunk-PUTC52SI.js +98 -0
  87. package/dist/chunk-PUTC52SI.js.map +1 -0
  88. package/dist/chunk-PWAKTEXF.js +18 -0
  89. package/dist/chunk-PWAKTEXF.js.map +1 -0
  90. package/dist/chunk-QTFWORA7.js +26 -0
  91. package/dist/chunk-QTFWORA7.js.map +1 -0
  92. package/dist/chunk-RUPL2H6V.js +49 -0
  93. package/dist/chunk-RUPL2H6V.js.map +1 -0
  94. package/dist/chunk-S7PVZAPL.js +28 -0
  95. package/dist/chunk-S7PVZAPL.js.map +1 -0
  96. package/dist/chunk-SQDV2AKJ.js +49 -0
  97. package/dist/chunk-SQDV2AKJ.js.map +1 -0
  98. package/dist/chunk-T2KXVH3O.js +297 -0
  99. package/dist/chunk-T2KXVH3O.js.map +1 -0
  100. package/dist/chunk-T4WCCHJQ.js +29 -0
  101. package/dist/chunk-T4WCCHJQ.js.map +1 -0
  102. package/dist/chunk-UC3ZIQPE.js +93 -0
  103. package/dist/chunk-UC3ZIQPE.js.map +1 -0
  104. package/dist/chunk-UE7JY7VR.js +28 -0
  105. package/dist/chunk-UE7JY7VR.js.map +1 -0
  106. package/dist/chunk-Y5WCT6XN.js +93 -0
  107. package/dist/chunk-Y5WCT6XN.js.map +1 -0
  108. package/dist/chunk-YDICVETX.js +151 -0
  109. package/dist/chunk-YDICVETX.js.map +1 -0
  110. package/dist/chunk-ZFSQVMLJ.js +126 -0
  111. package/dist/chunk-ZFSQVMLJ.js.map +1 -0
  112. package/dist/chunk-ZHATD73Y.js +46 -0
  113. package/dist/chunk-ZHATD73Y.js.map +1 -0
  114. package/dist/chunk-ZLAA4DGG.js +111 -0
  115. package/dist/chunk-ZLAA4DGG.js.map +1 -0
  116. package/dist/chunk-ZP252OJZ.js +56 -0
  117. package/dist/chunk-ZP252OJZ.js.map +1 -0
  118. package/dist/clipboard.cjs +125 -0
  119. package/dist/clipboard.cjs.map +1 -0
  120. package/dist/clipboard.d.cts +52 -0
  121. package/dist/clipboard.d.ts +52 -0
  122. package/dist/clipboard.js +23 -0
  123. package/dist/clipboard.js.map +1 -0
  124. package/dist/collapsible.cjs +94 -0
  125. package/dist/collapsible.cjs.map +1 -0
  126. package/dist/collapsible.d.cts +21 -0
  127. package/dist/collapsible.d.ts +21 -0
  128. package/dist/collapsible.js +9 -0
  129. package/dist/collapsible.js.map +1 -0
  130. package/dist/confirm-dialog.cjs +369 -0
  131. package/dist/confirm-dialog.cjs.map +1 -0
  132. package/dist/confirm-dialog.d.cts +16 -0
  133. package/dist/confirm-dialog.d.ts +16 -0
  134. package/dist/confirm-dialog.js +13 -0
  135. package/dist/confirm-dialog.js.map +1 -0
  136. package/dist/copy-button.cjs +102 -0
  137. package/dist/copy-button.cjs.map +1 -0
  138. package/dist/copy-button.d.cts +18 -0
  139. package/dist/copy-button.d.ts +18 -0
  140. package/dist/copy-button.js +10 -0
  141. package/dist/copy-button.js.map +1 -0
  142. package/dist/data-table.cjs +140 -0
  143. package/dist/data-table.cjs.map +1 -0
  144. package/dist/data-table.d.cts +81 -0
  145. package/dist/data-table.d.ts +81 -0
  146. package/dist/data-table.js +8 -0
  147. package/dist/data-table.js.map +1 -0
  148. package/dist/date-range-picker/dateRange.cjs +138 -0
  149. package/dist/date-range-picker/dateRange.cjs.map +1 -0
  150. package/dist/date-range-picker/dateRange.d.cts +57 -0
  151. package/dist/date-range-picker/dateRange.d.ts +57 -0
  152. package/dist/date-range-picker/dateRange.js +31 -0
  153. package/dist/date-range-picker/dateRange.js.map +1 -0
  154. package/dist/date-range-picker/index.cjs +375 -0
  155. package/dist/date-range-picker/index.cjs.map +1 -0
  156. package/dist/date-range-picker/index.d.cts +18 -0
  157. package/dist/date-range-picker/index.d.ts +18 -0
  158. package/dist/date-range-picker/index.js +10 -0
  159. package/dist/date-range-picker/index.js.map +1 -0
  160. package/dist/dialogLayer.cjs +121 -0
  161. package/dist/dialogLayer.cjs.map +1 -0
  162. package/dist/dialogLayer.d.cts +11 -0
  163. package/dist/dialogLayer.d.ts +11 -0
  164. package/dist/dialogLayer.js +8 -0
  165. package/dist/dialogLayer.js.map +1 -0
  166. package/dist/feature-access-gate.cjs +160 -0
  167. package/dist/feature-access-gate.cjs.map +1 -0
  168. package/dist/feature-access-gate.d.cts +14 -0
  169. package/dist/feature-access-gate.d.ts +14 -0
  170. package/dist/feature-access-gate.js +11 -0
  171. package/dist/feature-access-gate.js.map +1 -0
  172. package/dist/field.cjs +58 -0
  173. package/dist/field.cjs.map +1 -0
  174. package/dist/field.d.cts +23 -0
  175. package/dist/field.d.ts +23 -0
  176. package/dist/field.js +8 -0
  177. package/dist/field.js.map +1 -0
  178. package/dist/image-uploader.cjs +293 -0
  179. package/dist/image-uploader.cjs.map +1 -0
  180. package/dist/image-uploader.d.cts +34 -0
  181. package/dist/image-uploader.d.ts +34 -0
  182. package/dist/image-uploader.js +12 -0
  183. package/dist/image-uploader.js.map +1 -0
  184. package/dist/index.cjs +3008 -0
  185. package/dist/index.cjs.map +1 -0
  186. package/dist/index.d.cts +41 -0
  187. package/dist/index.d.ts +41 -0
  188. package/dist/index.js +232 -0
  189. package/dist/index.js.map +1 -0
  190. package/dist/input.cjs +58 -0
  191. package/dist/input.cjs.map +1 -0
  192. package/dist/input.d.cts +7 -0
  193. package/dist/input.d.ts +7 -0
  194. package/dist/input.js +10 -0
  195. package/dist/input.js.map +1 -0
  196. package/dist/label.cjs +67 -0
  197. package/dist/label.cjs.map +1 -0
  198. package/dist/label.d.cts +7 -0
  199. package/dist/label.d.ts +7 -0
  200. package/dist/label.js +9 -0
  201. package/dist/label.js.map +1 -0
  202. package/dist/modal.cjs +212 -0
  203. package/dist/modal.cjs.map +1 -0
  204. package/dist/modal.d.cts +39 -0
  205. package/dist/modal.d.ts +39 -0
  206. package/dist/modal.js +12 -0
  207. package/dist/modal.js.map +1 -0
  208. package/dist/page-header.cjs +78 -0
  209. package/dist/page-header.cjs.map +1 -0
  210. package/dist/page-header.d.cts +35 -0
  211. package/dist/page-header.d.ts +35 -0
  212. package/dist/page-header.js +8 -0
  213. package/dist/page-header.js.map +1 -0
  214. package/dist/pagination.cjs +237 -0
  215. package/dist/pagination.cjs.map +1 -0
  216. package/dist/pagination.d.cts +15 -0
  217. package/dist/pagination.d.ts +15 -0
  218. package/dist/pagination.js +11 -0
  219. package/dist/pagination.js.map +1 -0
  220. package/dist/panel-brand-mark.cjs +59 -0
  221. package/dist/panel-brand-mark.cjs.map +1 -0
  222. package/dist/panel-brand-mark.d.cts +13 -0
  223. package/dist/panel-brand-mark.d.ts +13 -0
  224. package/dist/panel-brand-mark.js +8 -0
  225. package/dist/panel-brand-mark.js.map +1 -0
  226. package/dist/panel-sign-in.cjs +256 -0
  227. package/dist/panel-sign-in.cjs.map +1 -0
  228. package/dist/panel-sign-in.d.cts +31 -0
  229. package/dist/panel-sign-in.d.ts +31 -0
  230. package/dist/panel-sign-in.js +14 -0
  231. package/dist/panel-sign-in.js.map +1 -0
  232. package/dist/segmented-control.cjs +122 -0
  233. package/dist/segmented-control.cjs.map +1 -0
  234. package/dist/segmented-control.d.cts +58 -0
  235. package/dist/segmented-control.d.ts +58 -0
  236. package/dist/segmented-control.js +9 -0
  237. package/dist/segmented-control.js.map +1 -0
  238. package/dist/select.cjs +648 -0
  239. package/dist/select.cjs.map +1 -0
  240. package/dist/select.d.cts +30 -0
  241. package/dist/select.d.ts +30 -0
  242. package/dist/select.js +12 -0
  243. package/dist/select.js.map +1 -0
  244. package/dist/separator.cjs +70 -0
  245. package/dist/separator.cjs.map +1 -0
  246. package/dist/separator.d.cts +13 -0
  247. package/dist/separator.d.ts +13 -0
  248. package/dist/separator.js +9 -0
  249. package/dist/separator.js.map +1 -0
  250. package/dist/side-drawer.cjs +321 -0
  251. package/dist/side-drawer.cjs.map +1 -0
  252. package/dist/side-drawer.d.cts +21 -0
  253. package/dist/side-drawer.d.ts +21 -0
  254. package/dist/side-drawer.js +13 -0
  255. package/dist/side-drawer.js.map +1 -0
  256. package/dist/spinner.cjs +57 -0
  257. package/dist/spinner.cjs.map +1 -0
  258. package/{src/spinner.tsx → dist/spinner.d.cts} +11 -22
  259. package/dist/spinner.d.ts +27 -0
  260. package/dist/spinner.js +8 -0
  261. package/dist/spinner.js.map +1 -0
  262. package/dist/spreadsheet-grid.cjs +238 -0
  263. package/dist/spreadsheet-grid.cjs.map +1 -0
  264. package/dist/spreadsheet-grid.d.cts +48 -0
  265. package/dist/spreadsheet-grid.d.ts +48 -0
  266. package/dist/spreadsheet-grid.js +8 -0
  267. package/dist/spreadsheet-grid.js.map +1 -0
  268. package/dist/state-panel.cjs +86 -0
  269. package/dist/state-panel.cjs.map +1 -0
  270. package/dist/state-panel.d.cts +23 -0
  271. package/dist/state-panel.d.ts +23 -0
  272. package/dist/state-panel.js +9 -0
  273. package/dist/state-panel.js.map +1 -0
  274. package/dist/switch.cjs +87 -0
  275. package/dist/switch.cjs.map +1 -0
  276. package/dist/switch.d.cts +13 -0
  277. package/dist/switch.d.ts +13 -0
  278. package/dist/switch.js +9 -0
  279. package/dist/switch.js.map +1 -0
  280. package/dist/table.cjs +139 -0
  281. package/dist/table.cjs.map +1 -0
  282. package/dist/table.d.cts +16 -0
  283. package/dist/table.d.ts +16 -0
  284. package/dist/table.js +22 -0
  285. package/dist/table.js.map +1 -0
  286. package/dist/tabs.cjs +168 -0
  287. package/dist/tabs.cjs.map +1 -0
  288. package/dist/tabs.d.cts +59 -0
  289. package/dist/tabs.d.ts +59 -0
  290. package/dist/tabs.js +11 -0
  291. package/dist/tabs.js.map +1 -0
  292. package/dist/textarea.cjs +66 -0
  293. package/dist/textarea.cjs.map +1 -0
  294. package/dist/textarea.d.cts +18 -0
  295. package/dist/textarea.d.ts +18 -0
  296. package/dist/textarea.js +10 -0
  297. package/dist/textarea.js.map +1 -0
  298. package/dist/utils.cjs +35 -0
  299. package/dist/utils.cjs.map +1 -0
  300. package/dist/utils.d.cts +5 -0
  301. package/dist/utils.d.ts +5 -0
  302. package/dist/utils.js +7 -0
  303. package/dist/utils.js.map +1 -0
  304. package/package.json +391 -14
  305. package/src/alert.tsx +0 -74
  306. package/src/autocomplete.tsx +0 -666
  307. package/src/badge.tsx +0 -60
  308. package/src/breadcrumb.tsx +0 -71
  309. package/src/button.tsx +0 -113
  310. package/src/card.tsx +0 -79
  311. package/src/clipboard.ts +0 -144
  312. package/src/collapsible.tsx +0 -63
  313. package/src/confirm-dialog.tsx +0 -84
  314. package/src/copy-button.tsx +0 -58
  315. package/src/data-table.tsx +0 -190
  316. package/src/date-range-picker/dateRange.ts +0 -142
  317. package/src/date-range-picker/index.tsx +0 -366
  318. package/src/dialogLayer.ts +0 -142
  319. package/src/feature-access-gate.tsx +0 -38
  320. package/src/field.tsx +0 -56
  321. package/src/image-uploader.tsx +0 -181
  322. package/src/index.ts +0 -37
  323. package/src/input.tsx +0 -16
  324. package/src/label.tsx +0 -23
  325. package/src/modal.tsx +0 -117
  326. package/src/page-header.tsx +0 -75
  327. package/src/pagination.tsx +0 -116
  328. package/src/panel-brand-mark.tsx +0 -32
  329. package/src/panel-sign-in.tsx +0 -134
  330. package/src/segmented-control.tsx +0 -155
  331. package/src/select.tsx +0 -105
  332. package/src/separator.tsx +0 -32
  333. package/src/side-drawer.tsx +0 -87
  334. package/src/spreadsheet-grid.tsx +0 -264
  335. package/src/state-panel.tsx +0 -46
  336. package/src/switch.tsx +0 -47
  337. package/src/table.tsx +0 -110
  338. package/src/tabs.tsx +0 -201
  339. package/src/textarea.tsx +0 -32
  340. package/src/utils.ts +0 -6
@@ -0,0 +1,85 @@
1
+ import {
2
+ useHasMounted
3
+ } from "./chunk-DS3QYIKL.js";
4
+ import {
5
+ useDialogLayer
6
+ } from "./chunk-HBBOR43A.js";
7
+ import {
8
+ Button
9
+ } from "./chunk-KR3BCFP7.js";
10
+ import {
11
+ cn
12
+ } from "./chunk-HCA5ODCD.js";
13
+
14
+ // src/side-drawer.tsx
15
+ import { X } from "lucide-react";
16
+ import * as React from "react";
17
+ import { createPortal } from "react-dom";
18
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
19
+ function SideDrawer({
20
+ isOpen,
21
+ title,
22
+ onClose,
23
+ children,
24
+ panelClassName
25
+ }) {
26
+ const mounted = useHasMounted();
27
+ const headingId = React.useId();
28
+ const panelRef = React.useRef(null);
29
+ useDialogLayer({
30
+ // Gated on `mounted` too: before that the panel is not rendered, so there
31
+ // would be nothing to move focus into.
32
+ active: isOpen && mounted,
33
+ containerRef: panelRef,
34
+ onEscape: onClose
35
+ });
36
+ if (!mounted || !isOpen) return null;
37
+ return createPortal(
38
+ /* @__PURE__ */ jsxs(Fragment, { children: [
39
+ /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ "aria-hidden": true,
43
+ onClick: onClose,
44
+ className: "fixed inset-0 z-[90] bg-overlay/30"
45
+ }
46
+ ),
47
+ /* @__PURE__ */ jsxs(
48
+ "aside",
49
+ {
50
+ ref: panelRef,
51
+ role: "dialog",
52
+ "aria-modal": "true",
53
+ "aria-labelledby": headingId,
54
+ tabIndex: -1,
55
+ className: cn(
56
+ "border-border bg-background fixed inset-y-0 right-0 z-[95] flex flex-col border-l shadow-2xl outline-none",
57
+ panelClassName ?? "w-full max-w-md"
58
+ ),
59
+ children: [
60
+ /* @__PURE__ */ jsxs("div", { className: "border-border flex items-center justify-between gap-3 border-b px-4 py-3", children: [
61
+ /* @__PURE__ */ jsx("h2", { id: headingId, className: "truncate text-base font-semibold", children: title }),
62
+ /* @__PURE__ */ jsx(
63
+ Button,
64
+ {
65
+ variant: "ghost",
66
+ size: "icon",
67
+ "aria-label": "Close panel",
68
+ onClick: onClose,
69
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", "aria-hidden": true })
70
+ }
71
+ )
72
+ ] }),
73
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children })
74
+ ]
75
+ }
76
+ )
77
+ ] }),
78
+ document.body
79
+ );
80
+ }
81
+
82
+ export {
83
+ SideDrawer
84
+ };
85
+ //# sourceMappingURL=chunk-A6GRLKOO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/side-drawer.tsx"],"sourcesContent":["\"use client\";\n\nimport { X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { Button } from \"./button\";\nimport { useDialogLayer } from \"./dialogLayer\";\nimport { useHasMounted } from \"./modal\";\nimport { cn } from \"./utils\";\n\ninterface SideDrawerProps {\n isOpen: boolean;\n title: string;\n onClose: () => void;\n children: React.ReactNode;\n /** Tailwind width class for the panel. Defaults to a reading-width column. */\n panelClassName?: string;\n}\n\n/**\n * A panel that slides in from the right.\n *\n * For content that belongs beside the page rather than on top of it — a queue\n * you glance at and dismiss, not a decision you have to answer. Unlike\n * `Modal`, it does not centre or dim the whole page to a stop: the table\n * underneath stays readable, which is the point of looking at both.\n */\nexport function SideDrawer({\n isOpen,\n title,\n onClose,\n children,\n panelClassName,\n}: SideDrawerProps) {\n const mounted = useHasMounted();\n const headingId = React.useId();\n const panelRef = React.useRef<HTMLElement>(null);\n\n useDialogLayer({\n // Gated on `mounted` too: before that the panel is not rendered, so there\n // would be nothing to move focus into.\n active: isOpen && mounted,\n containerRef: panelRef,\n onEscape: onClose,\n });\n\n if (!mounted || !isOpen) return null;\n\n return createPortal(\n <>\n <div\n aria-hidden\n onClick={onClose}\n className=\"fixed inset-0 z-[90] bg-overlay/30\"\n />\n <aside\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={headingId}\n tabIndex={-1}\n className={cn(\n \"border-border bg-background fixed inset-y-0 right-0 z-[95] flex flex-col border-l shadow-2xl outline-none\",\n panelClassName ?? \"w-full max-w-md\",\n )}\n >\n <div className=\"border-border flex items-center justify-between gap-3 border-b px-4 py-3\">\n <h2 id={headingId} className=\"truncate text-base font-semibold\">\n {title}\n </h2>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Close panel\"\n onClick={onClose}\n >\n <X className=\"h-4 w-4\" aria-hidden />\n </Button>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto p-4\">{children}</div>\n </aside>\n </>,\n document.body,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAEA,SAAS,SAAS;AAClB,YAAY,WAAW;AACvB,SAAS,oBAAoB;AA8CzB,mBACE,KAgBE,YAjBJ;AAtBG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAoB;AAClB,QAAM,UAAU,cAAc;AAC9B,QAAM,YAAkB,YAAM;AAC9B,QAAM,WAAiB,aAAoB,IAAI;AAE/C,iBAAe;AAAA;AAAA;AAAA,IAGb,QAAQ,UAAU;AAAA,IAClB,cAAc;AAAA,IACd,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,CAAC,WAAW,CAAC,OAAQ,QAAO;AAEhC,SAAO;AAAA,IACL,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,eAAW;AAAA,UACX,SAAS;AAAA,UACT,WAAU;AAAA;AAAA,MACZ;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAW;AAAA,UACX,mBAAiB;AAAA,UACjB,UAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA,kBAAkB;AAAA,UACpB;AAAA,UAEA;AAAA,iCAAC,SAAI,WAAU,4EACb;AAAA,kCAAC,QAAG,IAAI,WAAW,WAAU,oCAC1B,iBACH;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,cAAW;AAAA,kBACX,SAAS;AAAA,kBAET,8BAAC,KAAE,WAAU,WAAU,eAAW,MAAC;AAAA;AAAA,cACrC;AAAA,eACF;AAAA,YAEA,oBAAC,SAAI,WAAU,sCAAsC,UAAS;AAAA;AAAA;AAAA,MAChE;AAAA,OACF;AAAA,IACA,SAAS;AAAA,EACX;AACF;","names":[]}
@@ -0,0 +1,90 @@
1
+ import {
2
+ useDialogLayer
3
+ } from "./chunk-HBBOR43A.js";
4
+ import {
5
+ cn
6
+ } from "./chunk-HCA5ODCD.js";
7
+
8
+ // src/modal.tsx
9
+ import * as React from "react";
10
+ import { createPortal } from "react-dom";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ var emptySubscribe = () => () => {
13
+ };
14
+ function useHasMounted() {
15
+ return React.useSyncExternalStore(
16
+ emptySubscribe,
17
+ () => true,
18
+ () => false
19
+ );
20
+ }
21
+ function Modal({
22
+ isOpen,
23
+ labelledBy,
24
+ describedBy,
25
+ closeLabel,
26
+ onClose,
27
+ children,
28
+ dismissible = true,
29
+ role = "dialog",
30
+ panelClassName
31
+ }) {
32
+ const mounted = useHasMounted();
33
+ const panelRef = React.useRef(null);
34
+ useDialogLayer({
35
+ // Gated on `mounted` too: before that the panel is not rendered, so there
36
+ // would be nothing to move focus into.
37
+ active: isOpen && mounted,
38
+ containerRef: panelRef,
39
+ onEscape: dismissible ? onClose : void 0
40
+ });
41
+ if (!mounted || !isOpen) return null;
42
+ return createPortal(
43
+ /*
44
+ `overflow-y-auto` on the overlay, `my-auto` on the panel — not
45
+ `items-center`. A flex item centred with `align-items` overflows equally
46
+ in both directions once it is taller than its container, and the half
47
+ above the top edge cannot be scrolled to: with the page behind it locked,
48
+ an over-tall dialog was simply unreachable at both ends. Auto margins
49
+ centre a short panel the same way and collapse to zero on a tall one,
50
+ which scrolls.
51
+ */
52
+ /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[100] flex justify-center overflow-y-auto p-4", children: [
53
+ /* @__PURE__ */ jsx(
54
+ "button",
55
+ {
56
+ type: "button",
57
+ "aria-label": closeLabel,
58
+ tabIndex: -1,
59
+ className: "fixed inset-0 bg-overlay/50",
60
+ onClick: () => {
61
+ if (dismissible) onClose();
62
+ }
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsx(
66
+ "div",
67
+ {
68
+ ref: panelRef,
69
+ role,
70
+ "aria-modal": "true",
71
+ "aria-labelledby": labelledBy,
72
+ "aria-describedby": describedBy,
73
+ tabIndex: -1,
74
+ className: cn(
75
+ "border-border bg-background relative z-10 my-auto w-full rounded-lg border shadow-2xl outline-none",
76
+ panelClassName ?? "max-w-md p-6"
77
+ ),
78
+ children
79
+ }
80
+ )
81
+ ] }),
82
+ document.body
83
+ );
84
+ }
85
+
86
+ export {
87
+ useHasMounted,
88
+ Modal
89
+ };
90
+ //# sourceMappingURL=chunk-DS3QYIKL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modal.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { useDialogLayer } from \"./dialogLayer\";\nimport { cn } from \"./utils\";\n\nconst emptySubscribe = () => () => {};\n\n/**\n * Returns false during SSR and the first client render, then true once mounted.\n * `useSyncExternalStore` is the React-recommended way to read client-only state\n * without a setState-in-effect (which triggers cascading renders).\n */\nexport function useHasMounted(): boolean {\n return React.useSyncExternalStore(\n emptySubscribe,\n () => true,\n () => false,\n );\n}\n\ninterface ModalProps {\n isOpen: boolean;\n /** id of the element labelling the dialog; wired to aria-labelledby. */\n labelledBy: string;\n /**\n * Optional id(s) of elements describing the dialog (APG alertdialog body).\n * Space-separated when multiple.\n */\n describedBy?: string;\n /** Accessible label for the backdrop dismiss button. */\n closeLabel: string;\n onClose: () => void;\n children: React.ReactNode;\n /**\n * When false, Escape and backdrop clicks do not close the modal (e.g. while a\n * save is in flight). Defaults to true.\n */\n dismissible?: boolean;\n /** Use \"alertdialog\" for destructive confirmations. Defaults to \"dialog\". */\n role?: \"dialog\" | \"alertdialog\";\n /** Tailwind max-width class for the panel. Defaults to \"max-w-md\". */\n panelClassName?: string;\n}\n\n/**\n * Shared modal shell: renders a portal-backed, scroll-locked, Escape-dismissible\n * overlay with a centered panel. Callers provide the panel body (heading, form,\n * footer). Consolidates the boilerplate previously copied across every dialog.\n */\nexport function Modal({\n isOpen,\n labelledBy,\n describedBy,\n closeLabel,\n onClose,\n children,\n dismissible = true,\n role = \"dialog\",\n panelClassName,\n}: ModalProps) {\n const mounted = useHasMounted();\n const panelRef = React.useRef<HTMLDivElement>(null);\n\n useDialogLayer({\n // Gated on `mounted` too: before that the panel is not rendered, so there\n // would be nothing to move focus into.\n active: isOpen && mounted,\n containerRef: panelRef,\n onEscape: dismissible ? onClose : undefined,\n });\n\n if (!mounted || !isOpen) return null;\n\n return createPortal(\n /*\n `overflow-y-auto` on the overlay, `my-auto` on the panel — not\n `items-center`. A flex item centred with `align-items` overflows equally\n in both directions once it is taller than its container, and the half\n above the top edge cannot be scrolled to: with the page behind it locked,\n an over-tall dialog was simply unreachable at both ends. Auto margins\n centre a short panel the same way and collapse to zero on a tall one,\n which scrolls.\n */\n <div className=\"fixed inset-0 z-[100] flex justify-center overflow-y-auto p-4\">\n {/* `fixed`, not `absolute`: inside a scrolling overlay an absolute\n backdrop scrolls away with the content and leaves the rest of a tall\n dialog sitting on an undimmed page. */}\n <button\n type=\"button\"\n aria-label={closeLabel}\n tabIndex={-1}\n className=\"fixed inset-0 bg-overlay/50\"\n onClick={() => {\n if (dismissible) onClose();\n }}\n />\n <div\n ref={panelRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={labelledBy}\n aria-describedby={describedBy}\n tabIndex={-1}\n className={cn(\n \"border-border bg-background relative z-10 my-auto w-full rounded-lg border shadow-2xl outline-none\",\n panelClassName ?? \"max-w-md p-6\",\n )}\n >\n {children}\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":";;;;;;;;AAEA,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAmFzB,SAIE,KAJF;AA9EJ,IAAM,iBAAiB,MAAM,MAAM;AAAC;AAO7B,SAAS,gBAAyB;AACvC,SAAa;AAAA,IACX;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACF;AA+BO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,OAAO;AAAA,EACP;AACF,GAAe;AACb,QAAM,UAAU,cAAc;AAC9B,QAAM,WAAiB,aAAuB,IAAI;AAElD,iBAAe;AAAA;AAAA;AAAA,IAGb,QAAQ,UAAU;AAAA,IAClB,cAAc;AAAA,IACd,UAAU,cAAc,UAAU;AAAA,EACpC,CAAC;AAED,MAAI,CAAC,WAAW,CAAC,OAAQ,QAAO;AAEhC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUL,qBAAC,SAAI,WAAU,iEAIb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,UAAU;AAAA,UACV,WAAU;AAAA,UACV,SAAS,MAAM;AACb,gBAAI,YAAa,SAAQ;AAAA,UAC3B;AAAA;AAAA,MACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL;AAAA,UACA,cAAW;AAAA,UACX,mBAAiB;AAAA,UACjB,oBAAkB;AAAA,UAClB,UAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA,kBAAkB;AAAA,UACpB;AAAA,UAEC;AAAA;AAAA,MACH;AAAA,OACF;AAAA,IACA,SAAS;AAAA,EACX;AACF;","names":[]}
@@ -0,0 +1,53 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-HCA5ODCD.js";
4
+
5
+ // src/collapsible.tsx
6
+ import { ChevronDown } from "lucide-react";
7
+ import * as React from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ function CollapsibleSection({
10
+ title,
11
+ icon,
12
+ defaultOpen = true,
13
+ right,
14
+ className,
15
+ children
16
+ }) {
17
+ const [open, setOpen] = React.useState(defaultOpen);
18
+ const bodyId = React.useId();
19
+ return /* @__PURE__ */ jsxs("div", { className: cn("border-border rounded-lg border", className), children: [
20
+ /* @__PURE__ */ jsxs(
21
+ "button",
22
+ {
23
+ type: "button",
24
+ "aria-expanded": open,
25
+ "aria-controls": bodyId,
26
+ onClick: () => setOpen((current) => !current),
27
+ className: "hover:bg-muted/50 flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm font-semibold transition",
28
+ children: [
29
+ /* @__PURE__ */ jsx(
30
+ ChevronDown,
31
+ {
32
+ size: 14,
33
+ "aria-hidden": true,
34
+ className: cn(
35
+ "shrink-0 transition-transform",
36
+ open ? "" : "-rotate-90"
37
+ )
38
+ }
39
+ ),
40
+ icon,
41
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: title }),
42
+ right
43
+ ]
44
+ }
45
+ ),
46
+ /* @__PURE__ */ jsx("div", { id: bodyId, hidden: !open, className: "space-y-3 px-3 pt-1 pb-3", children })
47
+ ] });
48
+ }
49
+
50
+ export {
51
+ CollapsibleSection
52
+ };
53
+ //# sourceMappingURL=chunk-EWSBFU54.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/collapsible.tsx"],"sourcesContent":["\"use client\";\n\nimport { ChevronDown } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * A titled section that folds away.\n *\n * For sidebar panels that are long but not always in use — the body stays\n * mounted so its state (a loaded list, a half-filled filter) survives being\n * collapsed, rather than being thrown away and refetched on the way back.\n */\nexport function CollapsibleSection({\n title,\n icon,\n defaultOpen = true,\n right,\n className,\n children,\n}: {\n title: string;\n /** Optional leading glyph, shown between the chevron and the title. */\n icon?: React.ReactNode;\n defaultOpen?: boolean;\n /** Optional trailing content in the header, e.g. a count. */\n right?: React.ReactNode;\n className?: string;\n children: React.ReactNode;\n}) {\n const [open, setOpen] = React.useState(defaultOpen);\n const bodyId = React.useId();\n\n return (\n <div className={cn(\"border-border rounded-lg border\", className)}>\n <button\n type=\"button\"\n aria-expanded={open}\n aria-controls={bodyId}\n onClick={() => setOpen((current) => !current)}\n className=\"hover:bg-muted/50 flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm font-semibold transition\"\n >\n <ChevronDown\n size={14}\n aria-hidden\n className={cn(\n \"shrink-0 transition-transform\",\n open ? \"\" : \"-rotate-90\",\n )}\n />\n {icon}\n <span className=\"flex-1\">{title}</span>\n {right}\n </button>\n {/* Hidden rather than unmounted, so collapsing does not discard the\n panel's state or refetch what it had already loaded. */}\n <div id={bodyId} hidden={!open} className=\"space-y-3 px-3 pt-1 pb-3\">\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAEA,SAAS,mBAAmB;AAC5B,YAAY,WAAW;AAiCjB,SAOE,KAPF;AAtBC,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,WAAW;AAClD,QAAM,SAAe,YAAM;AAE3B,SACE,qBAAC,SAAI,WAAW,GAAG,mCAAmC,SAAS,GAC7D;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,SAAS,MAAM,QAAQ,CAAC,YAAY,CAAC,OAAO;AAAA,QAC5C,WAAU;AAAA,QAEV;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,eAAW;AAAA,cACX,WAAW;AAAA,gBACT;AAAA,gBACA,OAAO,KAAK;AAAA,cACd;AAAA;AAAA,UACF;AAAA,UACC;AAAA,UACD,oBAAC,UAAK,WAAU,UAAU,iBAAM;AAAA,UAC/B;AAAA;AAAA;AAAA,IACH;AAAA,IAGA,oBAAC,SAAI,IAAI,QAAQ,QAAQ,CAAC,MAAM,WAAU,4BACvC,UACH;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,38 @@
1
+ import {
2
+ Spinner
3
+ } from "./chunk-UE7JY7VR.js";
4
+ import {
5
+ cn
6
+ } from "./chunk-HCA5ODCD.js";
7
+
8
+ // src/state-panel.tsx
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ function StatePanel({
11
+ loading,
12
+ className,
13
+ children,
14
+ ...props
15
+ }) {
16
+ return /* @__PURE__ */ jsxs(
17
+ "div",
18
+ {
19
+ role: loading ? "status" : void 0,
20
+ "aria-live": loading ? "polite" : void 0,
21
+ className: cn(
22
+ "border-border bg-background/80 text-muted-foreground rounded-lg border px-4 py-6 text-center text-sm",
23
+ loading && "flex items-center justify-center gap-2",
24
+ className
25
+ ),
26
+ ...props,
27
+ children: [
28
+ loading ? /* @__PURE__ */ jsx(Spinner, { size: "md" }) : null,
29
+ children
30
+ ]
31
+ }
32
+ );
33
+ }
34
+
35
+ export {
36
+ StatePanel
37
+ };
38
+ //# sourceMappingURL=chunk-G4ZQUSAJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/state-panel.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { Spinner } from \"./spinner\";\nimport { cn } from \"./utils\";\n\n/**\n * The bordered block a list shows instead of rows: loading, or nothing found.\n *\n * Fourteen copies of the same\n * `border-border bg-background/80 … rounded-lg border px-4 py-6 text-center`\n * across the panels, each with its own wording — \"Loading users…\", \"Loading\n * taxonomy…\", \"Loading showcases…\". The wording is worth keeping (it says what\n * is loading), the markup is not.\n *\n * `loading` adds two things the hand-rolled versions did not have: a spinner,\n * so a slow panel does not look like an empty one, and `role=\"status\"` with\n * `aria-live=\"polite\"`, so a screen reader is told the list arrived instead of\n * silently finding rows on the next tab press.\n */\nexport interface StatePanelProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Shows a spinner and announces politely. */\n loading?: boolean;\n}\n\nexport function StatePanel({\n loading,\n className,\n children,\n ...props\n}: StatePanelProps) {\n return (\n <div\n role={loading ? \"status\" : undefined}\n aria-live={loading ? \"polite\" : undefined}\n className={cn(\n \"border-border bg-background/80 text-muted-foreground rounded-lg border px-4 py-6 text-center text-sm\",\n loading && \"flex items-center justify-center gap-2\",\n className,\n )}\n {...props}\n >\n {loading ? <Spinner size=\"md\" /> : null}\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AA+BI,SAUa,KAVb;AAPG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,UAAU,WAAW;AAAA,MAC3B,aAAW,UAAU,WAAW;AAAA,MAChC,WAAW;AAAA,QACT;AAAA,QACA,WAAW;AAAA,QACX;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,kBAAU,oBAAC,WAAQ,MAAK,MAAK,IAAK;AAAA,QAClC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -0,0 +1,67 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-HCA5ODCD.js";
4
+
5
+ // src/card.tsx
6
+ import * as React from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var Card = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
9
+ "div",
10
+ {
11
+ ref,
12
+ className: cn(
13
+ "bg-card text-card-foreground rounded-xl border shadow",
14
+ className
15
+ ),
16
+ ...props
17
+ }
18
+ ));
19
+ Card.displayName = "Card";
20
+ var CardHeader = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
21
+ "div",
22
+ {
23
+ ref,
24
+ className: cn("flex flex-col space-y-1.5 p-6", className),
25
+ ...props
26
+ }
27
+ ));
28
+ CardHeader.displayName = "CardHeader";
29
+ var CardTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
30
+ "div",
31
+ {
32
+ ref,
33
+ className: cn("leading-none font-semibold tracking-tight", className),
34
+ ...props
35
+ }
36
+ ));
37
+ CardTitle.displayName = "CardTitle";
38
+ var CardDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
39
+ "div",
40
+ {
41
+ ref,
42
+ className: cn("text-muted-foreground text-sm", className),
43
+ ...props
44
+ }
45
+ ));
46
+ CardDescription.displayName = "CardDescription";
47
+ var CardContent = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("p-6 pt-0", className), ...props }));
48
+ CardContent.displayName = "CardContent";
49
+ var CardFooter = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
50
+ "div",
51
+ {
52
+ ref,
53
+ className: cn("flex items-center p-6 pt-0", className),
54
+ ...props
55
+ }
56
+ ));
57
+ CardFooter.displayName = "CardFooter";
58
+
59
+ export {
60
+ Card,
61
+ CardHeader,
62
+ CardTitle,
63
+ CardDescription,
64
+ CardContent,
65
+ CardFooter
66
+ };
67
+ //# sourceMappingURL=chunk-GMCUXEFI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/card.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * Surface container for grouped content. Prefer a bare section when there is\n * no need for border / padding chrome — cards are for interactive or\n * self-contained blocks, not every stack of text.\n */\nexport const Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"bg-card text-card-foreground rounded-xl border shadow\",\n className,\n )}\n {...props}\n />\n));\nCard.displayName = \"Card\";\n\nexport const CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n {...props}\n />\n));\nCardHeader.displayName = \"CardHeader\";\n\nexport const CardTitle = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"leading-none font-semibold tracking-tight\", className)}\n {...props}\n />\n));\nCardTitle.displayName = \"CardTitle\";\n\nexport const CardDescription = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...props}\n />\n));\nCardDescription.displayName = \"CardDescription\";\n\nexport const CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n));\nCardContent.displayName = \"CardContent\";\n\nexport const CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex items-center p-6 pt-0\", className)}\n {...props}\n />\n));\nCardFooter.displayName = \"CardFooter\";\n"],"mappings":";;;;;AAAA,YAAY,WAAW;AAarB;AAJK,IAAM,OAAa,iBAGxB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACC,GAAG;AAAA;AACN,CACD;AACD,KAAK,cAAc;AAEZ,IAAM,aAAmB,iBAG9B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,WAAW,GAAG,iCAAiC,SAAS;AAAA,IACvD,GAAG;AAAA;AACN,CACD;AACD,WAAW,cAAc;AAElB,IAAM,YAAkB,iBAG7B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,WAAW,GAAG,6CAA6C,SAAS;AAAA,IACnE,GAAG;AAAA;AACN,CACD;AACD,UAAU,cAAc;AAEjB,IAAM,kBAAwB,iBAGnC,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,WAAW,GAAG,iCAAiC,SAAS;AAAA,IACvD,GAAG;AAAA;AACN,CACD;AACD,gBAAgB,cAAc;AAEvB,IAAM,cAAoB,iBAG/B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B,oBAAC,SAAI,KAAU,WAAW,GAAG,YAAY,SAAS,GAAI,GAAG,OAAO,CACjE;AACD,YAAY,cAAc;AAEnB,IAAM,aAAmB,iBAG9B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA,WAAW,GAAG,8BAA8B,SAAS;AAAA,IACpD,GAAG;AAAA;AACN,CACD;AACD,WAAW,cAAc;","names":[]}
@@ -0,0 +1,85 @@
1
+ // src/dialogLayer.ts
2
+ import * as React from "react";
3
+ var layers = [];
4
+ var FOCUSABLE = [
5
+ "a[href]",
6
+ "button:not([disabled])",
7
+ "input:not([disabled])",
8
+ "select:not([disabled])",
9
+ "textarea:not([disabled])",
10
+ '[tabindex]:not([tabindex="-1"])'
11
+ ].join(",");
12
+ function isVisible(element) {
13
+ if (element.closest("[hidden]")) return false;
14
+ if (element.getAttribute("aria-hidden") === "true") return false;
15
+ return typeof element.checkVisibility === "function" ? element.checkVisibility() : true;
16
+ }
17
+ function focusableWithin(container) {
18
+ return Array.from(container.querySelectorAll(FOCUSABLE)).filter(
19
+ isVisible
20
+ );
21
+ }
22
+ function useDialogLayer({
23
+ active,
24
+ containerRef,
25
+ onEscape
26
+ }) {
27
+ const escapeRef = React.useRef(onEscape);
28
+ React.useEffect(() => {
29
+ escapeRef.current = onEscape;
30
+ });
31
+ React.useEffect(() => {
32
+ if (!active) return;
33
+ const layer = /* @__PURE__ */ Symbol("dialog-layer");
34
+ layers.push(layer);
35
+ const isTopLayer = () => layers[layers.length - 1] === layer;
36
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
37
+ const container = containerRef.current;
38
+ if (container && !container.contains(document.activeElement)) {
39
+ const [first] = focusableWithin(container);
40
+ (container.hasAttribute("tabindex") ? container : first)?.focus();
41
+ }
42
+ function handleKeyDown(event) {
43
+ if (!isTopLayer()) return;
44
+ if (event.key === "Escape") {
45
+ escapeRef.current?.();
46
+ return;
47
+ }
48
+ if (event.key !== "Tab") return;
49
+ const panel = containerRef.current;
50
+ if (!panel) return;
51
+ const items = focusableWithin(panel);
52
+ if (items.length === 0) {
53
+ event.preventDefault();
54
+ panel.focus();
55
+ return;
56
+ }
57
+ const first = items[0];
58
+ const last = items[items.length - 1];
59
+ const active2 = document.activeElement;
60
+ const outside = !panel.contains(active2);
61
+ if (event.shiftKey && (active2 === first || outside)) {
62
+ event.preventDefault();
63
+ last.focus();
64
+ } else if (!event.shiftKey && (active2 === last || outside)) {
65
+ event.preventDefault();
66
+ first.focus();
67
+ }
68
+ }
69
+ document.addEventListener("keydown", handleKeyDown);
70
+ const previousOverflow = document.body.style.overflow;
71
+ document.body.style.overflow = "hidden";
72
+ return () => {
73
+ document.removeEventListener("keydown", handleKeyDown);
74
+ document.body.style.overflow = previousOverflow;
75
+ const index = layers.indexOf(layer);
76
+ if (index !== -1) layers.splice(index, 1);
77
+ if (previouslyFocused?.isConnected) previouslyFocused.focus();
78
+ };
79
+ }, [active, containerRef]);
80
+ }
81
+
82
+ export {
83
+ useDialogLayer
84
+ };
85
+ //# sourceMappingURL=chunk-HBBOR43A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dialogLayer.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * The keyboard and focus behaviour every dialog needs, in one place.\n *\n * `Modal` and `SideDrawer` each had a copy of the scroll lock and an Escape\n * handler, and neither did anything about focus: opening a dialog left focus\n * on the button behind it, Tab walked the page underneath — through the\n * full-screen backdrop button, among other things — and closing left focus\n * wherever it had wandered. That is the whole keyboard story for thirty\n * dialogs, so it is fixed once here rather than thirty times.\n */\n\n/**\n * Open dialogs, innermost last.\n *\n * Nesting is real here — \"Create audience group\" opens on top of \"Send\n * notification\" — and without an ordering both would answer the same Escape\n * and fight over the same Tab. Only the top layer acts.\n */\nconst layers: symbol[] = [];\n\nconst FOCUSABLE = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\",\");\n\n/**\n * Deliberately not `offsetParent` or `getClientRects`: the first is null for\n * anything `position: fixed`, which a dialog panel usually is, and the second\n * is empty for *everything* under a test renderer that does no layout — which\n * would collapse the trap to \"no focusable elements\" in every test.\n */\nfunction isVisible(element: HTMLElement): boolean {\n if (element.closest(\"[hidden]\")) return false;\n if (element.getAttribute(\"aria-hidden\") === \"true\") return false;\n return typeof element.checkVisibility === \"function\"\n ? element.checkVisibility()\n : true;\n}\n\nfunction focusableWithin(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n isVisible,\n );\n}\n\nexport function useDialogLayer({\n active,\n containerRef,\n onEscape,\n}: {\n active: boolean;\n /** The panel. Focus is moved into it and kept inside it. */\n containerRef: React.RefObject<HTMLElement | null>;\n /** Called on Escape, only while this is the top layer. Omit to ignore it. */\n onEscape?: () => void;\n}): void {\n // Read through a ref so a caller passing a fresh closure every render does\n // not tear the listener down and rebuild it on every keystroke.\n const escapeRef = React.useRef(onEscape);\n React.useEffect(() => {\n escapeRef.current = onEscape;\n });\n\n React.useEffect(() => {\n if (!active) return;\n\n const layer = Symbol(\"dialog-layer\");\n layers.push(layer);\n const isTopLayer = () => layers[layers.length - 1] === layer;\n\n const previouslyFocused =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n\n const container = containerRef.current;\n // Something inside may have claimed focus already — an `autoFocus` input —\n // and that choice is better than any this could make.\n if (container && !container.contains(document.activeElement)) {\n const [first] = focusableWithin(container);\n // The panel itself, not its first control: the first control is a Delete\n // button often enough that focusing it would put a destructive action\n // one Enter away from an accidental keypress.\n (container.hasAttribute(\"tabindex\") ? container : first)?.focus();\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (!isTopLayer()) return;\n\n if (event.key === \"Escape\") {\n escapeRef.current?.();\n return;\n }\n\n if (event.key !== \"Tab\") return;\n const panel = containerRef.current;\n if (!panel) return;\n\n const items = focusableWithin(panel);\n if (items.length === 0) {\n event.preventDefault();\n panel.focus();\n return;\n }\n\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = document.activeElement;\n const outside = !panel.contains(active);\n\n if (event.shiftKey && (active === first || outside)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || outside)) {\n event.preventDefault();\n first.focus();\n }\n }\n\n document.addEventListener(\"keydown\", handleKeyDown);\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n document.body.style.overflow = previousOverflow;\n const index = layers.indexOf(layer);\n if (index !== -1) layers.splice(index, 1);\n // Back where it came from, so closing a dialog opened by keyboard does\n // not drop the caret at the top of the document.\n if (previouslyFocused?.isConnected) previouslyFocused.focus();\n };\n }, [active, containerRef]);\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAoBvB,IAAM,SAAmB,CAAC;AAE1B,IAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAQV,SAAS,UAAU,SAA+B;AAChD,MAAI,QAAQ,QAAQ,UAAU,EAAG,QAAO;AACxC,MAAI,QAAQ,aAAa,aAAa,MAAM,OAAQ,QAAO;AAC3D,SAAO,OAAO,QAAQ,oBAAoB,aACtC,QAAQ,gBAAgB,IACxB;AACN;AAEA,SAAS,gBAAgB,WAAuC;AAC9D,SAAO,MAAM,KAAK,UAAU,iBAA8B,SAAS,CAAC,EAAE;AAAA,IACpE;AAAA,EACF;AACF;AAEO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAMS;AAGP,QAAM,YAAkB,aAAO,QAAQ;AACvC,EAAM,gBAAU,MAAM;AACpB,cAAU,UAAU;AAAA,EACtB,CAAC;AAED,EAAM,gBAAU,MAAM;AACpB,QAAI,CAAC,OAAQ;AAEb,UAAM,QAAQ,uBAAO,cAAc;AACnC,WAAO,KAAK,KAAK;AACjB,UAAM,aAAa,MAAM,OAAO,OAAO,SAAS,CAAC,MAAM;AAEvD,UAAM,oBACJ,SAAS,yBAAyB,cAC9B,SAAS,gBACT;AAEN,UAAM,YAAY,aAAa;AAG/B,QAAI,aAAa,CAAC,UAAU,SAAS,SAAS,aAAa,GAAG;AAC5D,YAAM,CAAC,KAAK,IAAI,gBAAgB,SAAS;AAIzC,OAAC,UAAU,aAAa,UAAU,IAAI,YAAY,QAAQ,MAAM;AAAA,IAClE;AAEA,aAAS,cAAc,OAAsB;AAC3C,UAAI,CAAC,WAAW,EAAG;AAEnB,UAAI,MAAM,QAAQ,UAAU;AAC1B,kBAAU,UAAU;AACpB;AAAA,MACF;AAEA,UAAI,MAAM,QAAQ,MAAO;AACzB,YAAM,QAAQ,aAAa;AAC3B,UAAI,CAAC,MAAO;AAEZ,YAAM,QAAQ,gBAAgB,KAAK;AACnC,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,eAAe;AACrB,cAAM,MAAM;AACZ;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,YAAMA,UAAS,SAAS;AACxB,YAAM,UAAU,CAAC,MAAM,SAASA,OAAM;AAEtC,UAAI,MAAM,aAAaA,YAAW,SAAS,UAAU;AACnD,cAAM,eAAe;AACrB,aAAK,MAAM;AAAA,MACb,WAAW,CAAC,MAAM,aAAaA,YAAW,QAAQ,UAAU;AAC1D,cAAM,eAAe;AACrB,cAAM,MAAM;AAAA,MACd;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,aAAa;AAClD,UAAM,mBAAmB,SAAS,KAAK,MAAM;AAC7C,aAAS,KAAK,MAAM,WAAW;AAE/B,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,aAAa;AACrD,eAAS,KAAK,MAAM,WAAW;AAC/B,YAAM,QAAQ,OAAO,QAAQ,KAAK;AAClC,UAAI,UAAU,GAAI,QAAO,OAAO,OAAO,CAAC;AAGxC,UAAI,mBAAmB,YAAa,mBAAkB,MAAM;AAAA,IAC9D;AAAA,EACF,GAAG,CAAC,QAAQ,YAAY,CAAC;AAC3B;","names":["active"]}
@@ -0,0 +1,11 @@
1
+ // src/utils.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export {
9
+ cn
10
+ };
11
+ //# sourceMappingURL=chunk-HCA5ODCD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";AAAA,SAA0B,YAAY;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
@@ -0,0 +1,26 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-HCA5ODCD.js";
4
+
5
+ // src/checkbox.tsx
6
+ import * as React from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var checkboxClass = "accent-primary border-border focus-visible:ring-ring size-4 shrink-0 cursor-pointer rounded border focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50";
9
+ var Checkbox = React.forwardRef(
10
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
11
+ "input",
12
+ {
13
+ ref,
14
+ type: "checkbox",
15
+ className: cn(checkboxClass, className),
16
+ ...props
17
+ }
18
+ )
19
+ );
20
+ Checkbox.displayName = "Checkbox";
21
+
22
+ export {
23
+ checkboxClass,
24
+ Checkbox
25
+ };
26
+ //# sourceMappingURL=chunk-HYRJYUPI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/checkbox.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The native checkbox, sized and coloured once.\n *\n * Twenty-six of these were scattered across thirteen files and twenty of them\n * carried **no class at all** — browser defaults, so they were a different size\n * to the six that set `size-4` and drew their tick in the operating system's\n * accent rather than the panel's.\n *\n * This deliberately stays a native `<input type=\"checkbox\">` rather than an\n * `appearance-none` box with an SVG tick. Native gets indeterminate state,\n * form participation, platform focus behaviour and assistive-technology support\n * for free, and a custom one would have to re-earn all of it to look slightly\n * different.\n *\n * `border` and `rounded` mostly do not render on a native checkbox — they are\n * kept because the six styled copies had them and removing them would be a\n * silent change to those. `accent-primary` and `size-4` are what actually show.\n */\nexport const checkboxClass =\n \"accent-primary border-border focus-visible:ring-ring size-4 shrink-0 cursor-pointer rounded border focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\";\n\nexport type CheckboxProps = Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n \"type\"\n>;\n\nexport const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(\n ({ className, ...props }, ref) => (\n <input\n ref={ref}\n type=\"checkbox\"\n className={cn(checkboxClass, className)}\n {...props}\n />\n ),\n);\n\nCheckbox.displayName = \"Checkbox\";\n"],"mappings":";;;;;AAAA,YAAY,WAAW;AAgCnB;AAVG,IAAM,gBACX;AAOK,IAAM,WAAiB;AAAA,EAC5B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,WAAW,GAAG,eAAe,SAAS;AAAA,MACrC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,cAAc;","names":[]}
@@ -0,0 +1,26 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-HCA5ODCD.js";
4
+
5
+ // src/textarea.tsx
6
+ import * as React from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var textareaClass = "border-border bg-background focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50";
9
+ var Textarea = React.forwardRef(
10
+ ({ className, rows = 3, ...props }, ref) => /* @__PURE__ */ jsx(
11
+ "textarea",
12
+ {
13
+ ref,
14
+ rows,
15
+ className: cn(textareaClass, className),
16
+ ...props
17
+ }
18
+ )
19
+ );
20
+ Textarea.displayName = "Textarea";
21
+
22
+ export {
23
+ textareaClass,
24
+ Textarea
25
+ };
26
+ //# sourceMappingURL=chunk-IRCTGJQB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/textarea.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The multi-line counterpart to `Input`.\n *\n * There was no shared textarea, so sixteen of them were styled individually\n * and no two agreed — different borders, different focus rings, some with no\n * focus ring at all, which is a keyboard user losing their place.\n *\n * Deliberately not `inputClass`: that fixes `h-9`, which is a one-line\n * control. Everything else is shared so a textarea and an input sitting in the\n * same form look related.\n */\nexport const textareaClass =\n \"border-border bg-background focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\";\n\nexport type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;\n\nexport const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n ({ className, rows = 3, ...props }, ref) => (\n <textarea\n ref={ref}\n rows={rows}\n className={cn(textareaClass, className)}\n {...props}\n />\n ),\n);\n\nTextarea.displayName = \"Textarea\";\n"],"mappings":";;;;;AAAA,YAAY,WAAW;AAsBnB;AAPG,IAAM,gBACX;AAIK,IAAM,WAAiB;AAAA,EAC5B,CAAC,EAAE,WAAW,OAAO,GAAG,GAAG,MAAM,GAAG,QAClC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,WAAW,GAAG,eAAe,SAAS;AAAA,MACrC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,cAAc;","names":[]}
@@ -0,0 +1,70 @@
1
+ import {
2
+ Modal
3
+ } from "./chunk-DS3QYIKL.js";
4
+ import {
5
+ Button
6
+ } from "./chunk-KR3BCFP7.js";
7
+
8
+ // src/confirm-dialog.tsx
9
+ import * as React from "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ function ConfirmDialog({
12
+ isOpen,
13
+ title,
14
+ description,
15
+ confirmLabel = "Delete",
16
+ cancelLabel = "Cancel",
17
+ isConfirming,
18
+ error,
19
+ onConfirm,
20
+ onClose
21
+ }) {
22
+ const titleId = React.useId();
23
+ const descriptionId = React.useId();
24
+ const errorId = React.useId();
25
+ const describedBy = [
26
+ description ? descriptionId : null,
27
+ error ? errorId : null
28
+ ].filter(Boolean).join(" ");
29
+ return /* @__PURE__ */ jsxs(
30
+ Modal,
31
+ {
32
+ isOpen,
33
+ role: "alertdialog",
34
+ labelledBy: titleId,
35
+ describedBy: describedBy || void 0,
36
+ closeLabel: "Close dialog",
37
+ onClose,
38
+ children: [
39
+ /* @__PURE__ */ jsx("h3", { id: titleId, className: "text-lg font-semibold", children: title }),
40
+ description ? /* @__PURE__ */ jsx("div", { id: descriptionId, className: "text-muted-foreground mt-2 text-sm", children: description }) : null,
41
+ error ? /* @__PURE__ */ jsx(
42
+ "div",
43
+ {
44
+ id: errorId,
45
+ role: "alert",
46
+ className: "border-destructive/30 bg-destructive/5 text-destructive mt-4 rounded-lg border px-3 py-2 text-sm",
47
+ children: error
48
+ }
49
+ ) : null,
50
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-2", children: [
51
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: isConfirming, children: cancelLabel }),
52
+ /* @__PURE__ */ jsx(
53
+ Button,
54
+ {
55
+ variant: "destructive",
56
+ onClick: onConfirm,
57
+ loading: isConfirming,
58
+ children: confirmLabel
59
+ }
60
+ )
61
+ ] })
62
+ ]
63
+ }
64
+ );
65
+ }
66
+
67
+ export {
68
+ ConfirmDialog
69
+ };
70
+ //# sourceMappingURL=chunk-ISPICB4W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/confirm-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport { Button } from \"./button\";\nimport { Modal } from \"./modal\";\n\ninterface ConfirmDialogProps {\n isOpen: boolean;\n title: string;\n description?: React.ReactNode;\n confirmLabel?: string;\n cancelLabel?: string;\n isConfirming?: boolean;\n error?: string | null;\n onConfirm: () => void;\n onClose: () => void;\n}\n\nexport function ConfirmDialog({\n isOpen,\n title,\n description,\n confirmLabel = \"Delete\",\n cancelLabel = \"Cancel\",\n isConfirming,\n error,\n onConfirm,\n onClose,\n}: ConfirmDialogProps) {\n const titleId = React.useId();\n const descriptionId = React.useId();\n const errorId = React.useId();\n\n const describedBy = [\n description ? descriptionId : null,\n error ? errorId : null,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Modal\n isOpen={isOpen}\n role=\"alertdialog\"\n labelledBy={titleId}\n describedBy={describedBy || undefined}\n closeLabel=\"Close dialog\"\n onClose={onClose}\n >\n <h3 id={titleId} className=\"text-lg font-semibold\">\n {title}\n </h3>\n {description ? (\n <div id={descriptionId} className=\"text-muted-foreground mt-2 text-sm\">\n {description}\n </div>\n ) : null}\n\n {error ? (\n <div\n id={errorId}\n role=\"alert\"\n className=\"border-destructive/30 bg-destructive/5 text-destructive mt-4 rounded-lg border px-3 py-2 text-sm\"\n >\n {error}\n </div>\n ) : null}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <Button variant=\"outline\" onClick={onClose} disabled={isConfirming}>\n {cancelLabel}\n </Button>\n <Button\n variant=\"destructive\"\n onClick={onConfirm}\n loading={isConfirming}\n >\n {confirmLabel}\n </Button>\n </div>\n </Modal>\n );\n}\n"],"mappings":";;;;;;;;AAEA,YAAY,WAAW;AAgDjB,cAmBA,YAnBA;AA/BC,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,UAAgB,YAAM;AAC5B,QAAM,gBAAsB,YAAM;AAClC,QAAM,UAAgB,YAAM;AAE5B,QAAM,cAAc;AAAA,IAClB,cAAc,gBAAgB;AAAA,IAC9B,QAAQ,UAAU;AAAA,EACpB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,YAAY;AAAA,MACZ,aAAa,eAAe;AAAA,MAC5B,YAAW;AAAA,MACX;AAAA,MAEA;AAAA,4BAAC,QAAG,IAAI,SAAS,WAAU,yBACxB,iBACH;AAAA,QACC,cACC,oBAAC,SAAI,IAAI,eAAe,WAAU,sCAC/B,uBACH,IACE;AAAA,QAEH,QACC;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,MAAK;AAAA,YACL,WAAU;AAAA,YAET;AAAA;AAAA,QACH,IACE;AAAA,QAEJ,qBAAC,SAAI,WAAU,+BACb;AAAA,8BAAC,UAAO,SAAQ,WAAU,SAAS,SAAS,UAAU,cACnD,uBACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,SAAS;AAAA,cACT,SAAS;AAAA,cAER;AAAA;AAAA,UACH;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}