@diwauhris/ui 1.0.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 (272) hide show
  1. package/README.md +75 -0
  2. package/lib/assets/ui.css +154 -0
  3. package/lib/index.cjs +183 -0
  4. package/lib/index.mjs +65 -0
  5. package/lib/index10.cjs +33 -0
  6. package/lib/index10.cjs.map +1 -0
  7. package/lib/index10.mjs +33 -0
  8. package/lib/index10.mjs.map +1 -0
  9. package/lib/index11.cjs +16 -0
  10. package/lib/index11.cjs.map +1 -0
  11. package/lib/index11.mjs +16 -0
  12. package/lib/index11.mjs.map +1 -0
  13. package/lib/index12.cjs +16 -0
  14. package/lib/index12.cjs.map +1 -0
  15. package/lib/index12.mjs +15 -0
  16. package/lib/index12.mjs.map +1 -0
  17. package/lib/index13.cjs +57 -0
  18. package/lib/index13.cjs.map +1 -0
  19. package/lib/index13.mjs +57 -0
  20. package/lib/index13.mjs.map +1 -0
  21. package/lib/index14.cjs +10 -0
  22. package/lib/index14.cjs.map +1 -0
  23. package/lib/index14.mjs +10 -0
  24. package/lib/index14.mjs.map +1 -0
  25. package/lib/index15.cjs +30 -0
  26. package/lib/index15.cjs.map +1 -0
  27. package/lib/index15.mjs +30 -0
  28. package/lib/index15.mjs.map +1 -0
  29. package/lib/index16.cjs +28 -0
  30. package/lib/index16.cjs.map +1 -0
  31. package/lib/index16.mjs +26 -0
  32. package/lib/index16.mjs.map +1 -0
  33. package/lib/index17.cjs +43 -0
  34. package/lib/index17.cjs.map +1 -0
  35. package/lib/index17.mjs +43 -0
  36. package/lib/index17.mjs.map +1 -0
  37. package/lib/index18.cjs +59 -0
  38. package/lib/index18.cjs.map +1 -0
  39. package/lib/index18.mjs +59 -0
  40. package/lib/index18.mjs.map +1 -0
  41. package/lib/index19.cjs +67 -0
  42. package/lib/index19.cjs.map +1 -0
  43. package/lib/index19.mjs +67 -0
  44. package/lib/index19.mjs.map +1 -0
  45. package/lib/index2.cjs +33 -0
  46. package/lib/index2.mjs +13 -0
  47. package/lib/index20.cjs +79 -0
  48. package/lib/index20.cjs.map +1 -0
  49. package/lib/index20.mjs +79 -0
  50. package/lib/index20.mjs.map +1 -0
  51. package/lib/index21.cjs +105 -0
  52. package/lib/index21.cjs.map +1 -0
  53. package/lib/index21.mjs +104 -0
  54. package/lib/index21.mjs.map +1 -0
  55. package/lib/index22.cjs +50 -0
  56. package/lib/index22.cjs.map +1 -0
  57. package/lib/index22.mjs +50 -0
  58. package/lib/index22.mjs.map +1 -0
  59. package/lib/index23.cjs +143 -0
  60. package/lib/index23.cjs.map +1 -0
  61. package/lib/index23.mjs +143 -0
  62. package/lib/index23.mjs.map +1 -0
  63. package/lib/index25.cjs +177 -0
  64. package/lib/index25.cjs.map +1 -0
  65. package/lib/index25.mjs +175 -0
  66. package/lib/index25.mjs.map +1 -0
  67. package/lib/index26.cjs +108 -0
  68. package/lib/index26.cjs.map +1 -0
  69. package/lib/index26.mjs +108 -0
  70. package/lib/index26.mjs.map +1 -0
  71. package/lib/index27.cjs +120 -0
  72. package/lib/index27.cjs.map +1 -0
  73. package/lib/index27.mjs +120 -0
  74. package/lib/index27.mjs.map +1 -0
  75. package/lib/index28.cjs +54 -0
  76. package/lib/index28.cjs.map +1 -0
  77. package/lib/index28.mjs +54 -0
  78. package/lib/index28.mjs.map +1 -0
  79. package/lib/index29.cjs +100 -0
  80. package/lib/index29.cjs.map +1 -0
  81. package/lib/index29.mjs +100 -0
  82. package/lib/index29.mjs.map +1 -0
  83. package/lib/index3.cjs +38 -0
  84. package/lib/index3.cjs.map +1 -0
  85. package/lib/index3.mjs +38 -0
  86. package/lib/index3.mjs.map +1 -0
  87. package/lib/index30.cjs +27 -0
  88. package/lib/index30.cjs.map +1 -0
  89. package/lib/index30.mjs +27 -0
  90. package/lib/index30.mjs.map +1 -0
  91. package/lib/index31.cjs +72 -0
  92. package/lib/index31.cjs.map +1 -0
  93. package/lib/index31.mjs +72 -0
  94. package/lib/index31.mjs.map +1 -0
  95. package/lib/index32.cjs +19 -0
  96. package/lib/index32.cjs.map +1 -0
  97. package/lib/index32.mjs +19 -0
  98. package/lib/index32.mjs.map +1 -0
  99. package/lib/index33.cjs +25 -0
  100. package/lib/index33.cjs.map +1 -0
  101. package/lib/index33.mjs +25 -0
  102. package/lib/index33.mjs.map +1 -0
  103. package/lib/index34.cjs +55 -0
  104. package/lib/index34.cjs.map +1 -0
  105. package/lib/index34.mjs +51 -0
  106. package/lib/index34.mjs.map +1 -0
  107. package/lib/index35.cjs +25 -0
  108. package/lib/index35.cjs.map +1 -0
  109. package/lib/index35.mjs +25 -0
  110. package/lib/index35.mjs.map +1 -0
  111. package/lib/index36.cjs +54 -0
  112. package/lib/index36.cjs.map +1 -0
  113. package/lib/index36.mjs +49 -0
  114. package/lib/index36.mjs.map +1 -0
  115. package/lib/index37.cjs +130 -0
  116. package/lib/index37.cjs.map +1 -0
  117. package/lib/index37.mjs +128 -0
  118. package/lib/index37.mjs.map +1 -0
  119. package/lib/index38.cjs +106 -0
  120. package/lib/index38.cjs.map +1 -0
  121. package/lib/index38.mjs +102 -0
  122. package/lib/index38.mjs.map +1 -0
  123. package/lib/index39.cjs +137 -0
  124. package/lib/index39.cjs.map +1 -0
  125. package/lib/index39.mjs +137 -0
  126. package/lib/index39.mjs.map +1 -0
  127. package/lib/index4.cjs +24 -0
  128. package/lib/index4.cjs.map +1 -0
  129. package/lib/index4.mjs +24 -0
  130. package/lib/index4.mjs.map +1 -0
  131. package/lib/index40.cjs +130 -0
  132. package/lib/index40.cjs.map +1 -0
  133. package/lib/index40.mjs +126 -0
  134. package/lib/index40.mjs.map +1 -0
  135. package/lib/index41.cjs +60 -0
  136. package/lib/index41.cjs.map +1 -0
  137. package/lib/index41.mjs +58 -0
  138. package/lib/index41.mjs.map +1 -0
  139. package/lib/index42.cjs +105 -0
  140. package/lib/index42.cjs.map +1 -0
  141. package/lib/index42.mjs +105 -0
  142. package/lib/index42.mjs.map +1 -0
  143. package/lib/index43.cjs +47 -0
  144. package/lib/index43.cjs.map +1 -0
  145. package/lib/index43.mjs +42 -0
  146. package/lib/index43.mjs.map +1 -0
  147. package/lib/index44.cjs +40 -0
  148. package/lib/index44.cjs.map +1 -0
  149. package/lib/index44.mjs +35 -0
  150. package/lib/index44.mjs.map +1 -0
  151. package/lib/index45.cjs +91 -0
  152. package/lib/index45.cjs.map +1 -0
  153. package/lib/index45.mjs +91 -0
  154. package/lib/index45.mjs.map +1 -0
  155. package/lib/index46.cjs +180 -0
  156. package/lib/index46.cjs.map +1 -0
  157. package/lib/index46.mjs +180 -0
  158. package/lib/index46.mjs.map +1 -0
  159. package/lib/index47.cjs +61 -0
  160. package/lib/index47.cjs.map +1 -0
  161. package/lib/index47.mjs +61 -0
  162. package/lib/index47.mjs.map +1 -0
  163. package/lib/index48.cjs +165 -0
  164. package/lib/index48.cjs.map +1 -0
  165. package/lib/index48.mjs +161 -0
  166. package/lib/index48.mjs.map +1 -0
  167. package/lib/index49.cjs +83 -0
  168. package/lib/index49.cjs.map +1 -0
  169. package/lib/index49.mjs +83 -0
  170. package/lib/index49.mjs.map +1 -0
  171. package/lib/index5.cjs +39 -0
  172. package/lib/index5.cjs.map +1 -0
  173. package/lib/index5.mjs +39 -0
  174. package/lib/index5.mjs.map +1 -0
  175. package/lib/index50.cjs +100 -0
  176. package/lib/index50.cjs.map +1 -0
  177. package/lib/index50.mjs +100 -0
  178. package/lib/index50.mjs.map +1 -0
  179. package/lib/index51.cjs +116 -0
  180. package/lib/index51.cjs.map +1 -0
  181. package/lib/index51.mjs +113 -0
  182. package/lib/index51.mjs.map +1 -0
  183. package/lib/index52.cjs +93 -0
  184. package/lib/index52.cjs.map +1 -0
  185. package/lib/index52.mjs +93 -0
  186. package/lib/index52.mjs.map +1 -0
  187. package/lib/index53.cjs +120 -0
  188. package/lib/index53.cjs.map +1 -0
  189. package/lib/index53.mjs +120 -0
  190. package/lib/index53.mjs.map +1 -0
  191. package/lib/index54.cjs +53 -0
  192. package/lib/index54.cjs.map +1 -0
  193. package/lib/index54.mjs +50 -0
  194. package/lib/index54.mjs.map +1 -0
  195. package/lib/index55.cjs +130 -0
  196. package/lib/index55.cjs.map +1 -0
  197. package/lib/index55.mjs +127 -0
  198. package/lib/index55.mjs.map +1 -0
  199. package/lib/index56.cjs +36 -0
  200. package/lib/index56.cjs.map +1 -0
  201. package/lib/index56.mjs +35 -0
  202. package/lib/index56.mjs.map +1 -0
  203. package/lib/index57.cjs +47 -0
  204. package/lib/index57.cjs.map +1 -0
  205. package/lib/index57.mjs +47 -0
  206. package/lib/index57.mjs.map +1 -0
  207. package/lib/index58.cjs +97 -0
  208. package/lib/index58.cjs.map +1 -0
  209. package/lib/index58.mjs +97 -0
  210. package/lib/index58.mjs.map +1 -0
  211. package/lib/index59.cjs +120 -0
  212. package/lib/index59.cjs.map +1 -0
  213. package/lib/index59.mjs +116 -0
  214. package/lib/index59.mjs.map +1 -0
  215. package/lib/index6.cjs +29 -0
  216. package/lib/index6.cjs.map +1 -0
  217. package/lib/index6.mjs +29 -0
  218. package/lib/index6.mjs.map +1 -0
  219. package/lib/index60.cjs +34 -0
  220. package/lib/index60.cjs.map +1 -0
  221. package/lib/index60.mjs +34 -0
  222. package/lib/index60.mjs.map +1 -0
  223. package/lib/index61.cjs +62 -0
  224. package/lib/index61.cjs.map +1 -0
  225. package/lib/index61.mjs +62 -0
  226. package/lib/index61.mjs.map +1 -0
  227. package/lib/index62.cjs +213 -0
  228. package/lib/index62.cjs.map +1 -0
  229. package/lib/index62.mjs +213 -0
  230. package/lib/index62.mjs.map +1 -0
  231. package/lib/index63.cjs +42 -0
  232. package/lib/index63.cjs.map +1 -0
  233. package/lib/index63.mjs +42 -0
  234. package/lib/index63.mjs.map +1 -0
  235. package/lib/index64.cjs +86 -0
  236. package/lib/index64.cjs.map +1 -0
  237. package/lib/index64.mjs +86 -0
  238. package/lib/index64.mjs.map +1 -0
  239. package/lib/index65.cjs +156 -0
  240. package/lib/index65.cjs.map +1 -0
  241. package/lib/index65.mjs +156 -0
  242. package/lib/index65.mjs.map +1 -0
  243. package/lib/index66.cjs +107 -0
  244. package/lib/index66.cjs.map +1 -0
  245. package/lib/index66.mjs +106 -0
  246. package/lib/index66.mjs.map +1 -0
  247. package/lib/index67.cjs +52 -0
  248. package/lib/index67.cjs.map +1 -0
  249. package/lib/index67.mjs +52 -0
  250. package/lib/index67.mjs.map +1 -0
  251. package/lib/index68.cjs +125 -0
  252. package/lib/index68.cjs.map +1 -0
  253. package/lib/index68.mjs +115 -0
  254. package/lib/index68.mjs.map +1 -0
  255. package/lib/index69.cjs +34 -0
  256. package/lib/index69.cjs.map +1 -0
  257. package/lib/index69.mjs +29 -0
  258. package/lib/index69.mjs.map +1 -0
  259. package/lib/index7.cjs +17 -0
  260. package/lib/index7.cjs.map +1 -0
  261. package/lib/index7.mjs +17 -0
  262. package/lib/index7.mjs.map +1 -0
  263. package/lib/index8.cjs +86 -0
  264. package/lib/index8.cjs.map +1 -0
  265. package/lib/index8.mjs +86 -0
  266. package/lib/index8.mjs.map +1 -0
  267. package/lib/index9.cjs +29 -0
  268. package/lib/index9.cjs.map +1 -0
  269. package/lib/index9.mjs +29 -0
  270. package/lib/index9.mjs.map +1 -0
  271. package/lib/types/index.d.ts +1 -0
  272. package/package.json +68 -0
@@ -0,0 +1,102 @@
1
+ import { cn } from "./index14.mjs";
2
+ import { typography } from "./index36.mjs";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import "react";
5
+ import { X } from "lucide-react";
6
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
7
+ //#region src/ui-library/gallery/dialog/Dialog.tsx
8
+ /**
9
+ * Dialog — Design System Component
10
+ *
11
+ * A focus-blocking modal dialog.
12
+ *
13
+ * Behavior is provided by @radix-ui/react-dialog, which handles:
14
+ * - Focus trap (Tab / Shift+Tab contained within dialog)
15
+ * - Focus restoration (returns to trigger on close)
16
+ * - Escape key dismissal (unless preventClose)
17
+ * - Backdrop interaction dismissal (unless preventClose)
18
+ * - Body scroll lock while open
19
+ * - Portal rendering to document.body
20
+ * - role="dialog" + aria-modal + aria-labelledby + aria-describedby
21
+ * - Modal semantics
22
+ *
23
+ * DIWA continues to own:
24
+ * - Public API (open, onClose, title, description, size, preventClose, children)
25
+ * - Backdrop visual treatment
26
+ * - Panel visual treatment (bg, border, radius, shadow, sizing)
27
+ * - Header layout and typography
28
+ * - Close button styling
29
+ * - DialogBody and DialogFooter layout
30
+ *
31
+ * Composition:
32
+ * <Dialog open={open} onClose={close} title="…">
33
+ * <DialogBody>…</DialogBody>
34
+ * <DialogFooter>…</DialogFooter>
35
+ * </Dialog>
36
+ */
37
+ var SIZE_CLASS = {
38
+ sm: "max-w-sm",
39
+ md: "max-w-lg",
40
+ lg: "max-w-2xl",
41
+ xl: "max-w-4xl"
42
+ };
43
+ function DialogBody({ children, className = "" }) {
44
+ return /* @__PURE__ */ jsx("div", {
45
+ className: `min-h-0 overflow-y-auto overscroll-contain px-6 py-4 ${className}`,
46
+ children
47
+ });
48
+ }
49
+ function DialogFooter({ children, className = "" }) {
50
+ return /* @__PURE__ */ jsx("div", {
51
+ className: `flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4 ${className}`,
52
+ children
53
+ });
54
+ }
55
+ function Dialog({ open, onClose, title, description, preventClose = false, size = "md", children }) {
56
+ const hasTitle = Boolean(title);
57
+ const hasDesc = Boolean(description);
58
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, {
59
+ open,
60
+ onOpenChange: (isOpen) => {
61
+ if (!isOpen && !preventClose) onClose();
62
+ },
63
+ children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [/* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm" }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
64
+ className: cn("fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2", "flex w-full flex-col overflow-hidden", "rounded-lg bg-white shadow-xl", "max-h-[90vh] outline-none", "p-4", SIZE_CLASS[size]),
65
+ onEscapeKeyDown: preventClose ? (e) => e.preventDefault() : void 0,
66
+ onInteractOutside: preventClose ? (e) => e.preventDefault() : void 0,
67
+ style: { padding: 0 },
68
+ children: [
69
+ hasTitle ? /* @__PURE__ */ jsxs("div", {
70
+ className: "shrink-0 border-b-2 border-slate-100 px-6 py-5 pr-14",
71
+ children: [/* @__PURE__ */ jsx(DialogPrimitive.Title, {
72
+ className: cn(typography.titleOverlay, "text-slate-900"),
73
+ children: title
74
+ }), hasDesc && /* @__PURE__ */ jsx(DialogPrimitive.Description, {
75
+ className: "mt-0.5 text-sm font-medium text-slate-500",
76
+ children: description
77
+ })]
78
+ }) : /* @__PURE__ */ jsx(DialogPrimitive.Title, {
79
+ className: "sr-only",
80
+ children: "Dialog"
81
+ }),
82
+ !preventClose && /* @__PURE__ */ jsx(DialogPrimitive.Close, {
83
+ asChild: true,
84
+ children: /* @__PURE__ */ jsx("button", {
85
+ type: "button",
86
+ "aria-label": "Close dialog",
87
+ className: "absolute right-4 top-4 rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
88
+ children: /* @__PURE__ */ jsx(X, {
89
+ size: 16,
90
+ "aria-hidden": "true"
91
+ })
92
+ })
93
+ }),
94
+ children
95
+ ]
96
+ })] })
97
+ });
98
+ }
99
+ //#endregion
100
+ export { Dialog, DialogBody, DialogFooter };
101
+
102
+ //# sourceMappingURL=index38.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index38.mjs","names":[],"sources":["../src/ui-library/gallery/dialog/Dialog.tsx"],"sourcesContent":["/**\n * Dialog — Design System Component\n *\n * A focus-blocking modal dialog.\n *\n * Behavior is provided by @radix-ui/react-dialog, which handles:\n * - Focus trap (Tab / Shift+Tab contained within dialog)\n * - Focus restoration (returns to trigger on close)\n * - Escape key dismissal (unless preventClose)\n * - Backdrop interaction dismissal (unless preventClose)\n * - Body scroll lock while open\n * - Portal rendering to document.body\n * - role=\"dialog\" + aria-modal + aria-labelledby + aria-describedby\n * - Modal semantics\n *\n * DIWA continues to own:\n * - Public API (open, onClose, title, description, size, preventClose, children)\n * - Backdrop visual treatment\n * - Panel visual treatment (bg, border, radius, shadow, sizing)\n * - Header layout and typography\n * - Close button styling\n * - DialogBody and DialogFooter layout\n *\n * Composition:\n * <Dialog open={open} onClose={close} title=\"…\">\n * <DialogBody>…</DialogBody>\n * <DialogFooter>…</DialogFooter>\n * </Dialog>\n */\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport { type ReactNode } from 'react';\nimport { X } from 'lucide-react';\nimport { cn } from '../../../lib/utils';\nimport { typography } from '../../tokens/typography';\n\nexport type DialogSize = 'sm' | 'md' | 'lg' | 'xl';\n\nexport interface DialogProps {\n open: boolean;\n onClose: () => void;\n title?: string;\n description?: string;\n /** When true, Escape and backdrop click will not close the dialog */\n preventClose?: boolean;\n size?: DialogSize;\n children?: ReactNode;\n}\n\nexport interface DialogBodyProps {\n children: ReactNode;\n className?: string;\n}\n\nexport interface DialogFooterProps {\n children: ReactNode;\n className?: string;\n}\n\n// ── Style maps ────────────────────────────────────────────────────────────────\n\nconst SIZE_CLASS: Record<DialogSize, string> = {\n sm: 'max-w-sm',\n md: 'max-w-lg',\n lg: 'max-w-2xl',\n xl: 'max-w-4xl',\n};\n\n// ── Sub-components — pure DIWA layout, no Radix involvement ──────────────────\n\nexport function DialogBody({ children, className = '' }: DialogBodyProps) {\n return (\n <div className={`min-h-0 overflow-y-auto overscroll-contain px-6 py-4 ${className}`}>\n {children}\n </div>\n );\n}\n\nexport function DialogFooter({ children, className = '' }: DialogFooterProps) {\n return (\n <div className={`flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4 ${className}`}>\n {children}\n </div>\n );\n}\n\n// ── Main component ────────────────────────────────────────────────────────────\n\nexport function Dialog({\n open,\n onClose,\n title,\n description,\n preventClose = false,\n size = 'md',\n children,\n}: DialogProps) {\n const hasTitle = Boolean(title);\n const hasDesc = Boolean(description);\n\n return (\n // Radix Dialog.Root drives open/close state.\n // onOpenChange is called when Radix wants to close (Escape, outside click).\n // We route it through DIWA's onClose, respecting preventClose.\n <DialogPrimitive.Root\n open={open}\n onOpenChange={(isOpen) => {\n // Radix calls onOpenChange(false) when closing.\n // Ignore opening attempts from Radix (we control open externally).\n if (!isOpen && !preventClose) onClose();\n }}\n >\n <DialogPrimitive.Portal>\n {/* Backdrop — DIWA visual treatment */}\n <DialogPrimitive.Overlay\n className=\"fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm\"\n />\n\n {/* Panel — DIWA visual treatment */}\n <DialogPrimitive.Content\n className={cn(\n 'fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2',\n 'flex w-full flex-col overflow-hidden',\n 'rounded-lg bg-white shadow-xl',\n 'max-h-[90vh] outline-none',\n 'p-4',\n SIZE_CLASS[size],\n )}\n // Suppress Radix's own Escape/outside-click when preventClose is set.\n // Radix calls these before triggering onOpenChange, so preventing here\n // ensures neither the overlay nor keyboard can dismiss the dialog.\n onEscapeKeyDown={preventClose ? (e) => e.preventDefault() : undefined}\n onInteractOutside={preventClose ? (e) => e.preventDefault() : undefined}\n // Remove padding added by className — override with our layout below\n style={{ padding: 0 }}\n >\n {/* Radix requires Dialog.Title for accessible naming.\n Render it visually when title prop is provided.\n When absent, render sr-only so Radix's ARIA wiring works\n without adding visible text the consumer didn't request. */}\n {hasTitle ? (\n // Header — visible when title or description are provided\n <div className=\"shrink-0 border-b-2 border-slate-100 px-6 py-5 pr-14\">\n <DialogPrimitive.Title className={cn(typography.titleOverlay, 'text-slate-900')}>\n {title}\n </DialogPrimitive.Title>\n {hasDesc && (\n <DialogPrimitive.Description className=\"mt-0.5 text-sm font-medium text-slate-500\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n ) : (\n // No visible title — render sr-only for Radix ARIA requirement\n <DialogPrimitive.Title className=\"sr-only\">\n Dialog\n </DialogPrimitive.Title>\n )}\n\n {/* Close button — uses Radix Dialog.Close so it participates\n in the Radix open/close flow. asChild avoids an extra DOM node. */}\n {!preventClose && (\n <DialogPrimitive.Close asChild>\n <button\n type=\"button\"\n aria-label=\"Close dialog\"\n className=\"absolute right-4 top-4 rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <X size={16} aria-hidden=\"true\" />\n </button>\n </DialogPrimitive.Close>\n )}\n\n {/* Content — children contain DialogBody + DialogFooter */}\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,IAAM,aAAyC;CAC7C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAIA,SAAgB,WAAW,EAAE,UAAU,YAAY,MAAuB;CACxE,OACE,oBAAC,OAAD;EAAK,WAAW,wDAAwD;EACrE;CACE,CAAA;AAET;AAEA,SAAgB,aAAa,EAAE,UAAU,YAAY,MAAyB;CAC5E,OACE,oBAAC,OAAD;EAAK,WAAW,2EAA2E;EACxF;CACE,CAAA;AAET;AAIA,SAAgB,OAAO,EACrB,MACA,SACA,OACA,aACA,eAAe,OACf,OAAO,MACP,YACc;CACd,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,UAAW,QAAQ,WAAW;CAEpC,OAIE,oBAAC,gBAAgB,MAAjB;EACQ;EACN,eAAe,WAAW;GAGxB,IAAI,CAAC,UAAU,CAAC,cAAc,QAAQ;EACxC;EAEA,UAAA,qBAAC,gBAAgB,QAAjB,EAAA,UAAA,CAEE,oBAAC,gBAAgB,SAAjB,EACE,WAAU,uDACX,CAAA,GAGD,qBAAC,gBAAgB,SAAjB;GACE,WAAW,GACT,iEACA,wCACA,iCACA,6BACA,OACA,WAAW,KACb;GAIA,iBAAiB,gBAAgB,MAAM,EAAE,eAAe,IAAI,KAAA;GAC5D,mBAAmB,gBAAgB,MAAM,EAAE,eAAe,IAAI,KAAA;GAE9D,OAAO,EAAE,SAAS,EAAE;GAftB,UAAA;IAqBG,WAEC,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,gBAAgB,OAAjB;MAAuB,WAAW,GAAG,WAAW,cAAc,gBAAgB;MAC3E,UAAA;KACoB,CAAA,GACtB,WACC,oBAAC,gBAAgB,aAAjB;MAA6B,WAAU;MACpC,UAAA;KAC0B,CAAA,CAE5B;IAGL,CAAA,IAAA,oBAAC,gBAAgB,OAAjB;KAAuB,WAAU;KAAU,UAAA;IAEpB,CAAA;IAKxB,CAAC,gBACA,oBAAC,gBAAgB,OAAjB;KAAuB,SAAA;KACrB,UAAA,oBAAC,UAAD;MACE,MAAK;MACL,cAAW;MACX,WAAU;MAEV,UAAA,oBAAC,GAAD;OAAG,MAAM;OAAI,eAAY;MAAQ,CAAA;KAC3B,CAAA;IACa,CAAA;IAIxB;GACsB;EACH,CAAA,CAAA,EAAA,CAAA;CACJ,CAAA;AAE1B"}
@@ -0,0 +1,137 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ let react = require("react");
3
+ let lucide_react = require("lucide-react");
4
+ let react_dom = require("react-dom");
5
+ //#region src/ui-library/gallery/drawer/Drawer.tsx
6
+ /**
7
+ * Drawer — Design System Component
8
+ *
9
+ * A slide-in panel anchored to the screen edge. Supports right (default)
10
+ * and bottom placements.
11
+ *
12
+ * Adaptation note: framer-motion animation replaced with CSS transitions
13
+ * (translate + opacity) for the standalone gallery which does not include
14
+ * framer-motion as a dependency.
15
+ */
16
+ var FOCUSABLE = "a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])";
17
+ var PLACEMENT_PANEL_CLASS = {
18
+ right: "fixed top-0 right-0 bottom-0 flex flex-col",
19
+ bottom: "fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl"
20
+ };
21
+ function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children }) {
22
+ const panelRef = (0, react.useRef)(null);
23
+ const titleId = (0, react.useId)();
24
+ const [rendered, setRendered] = (0, react.useState)(open);
25
+ const [visible, setVisible] = (0, react.useState)(false);
26
+ (0, react.useEffect)(() => {
27
+ if (open) {
28
+ setRendered(true);
29
+ requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));
30
+ } else {
31
+ setVisible(false);
32
+ const timer = window.setTimeout(() => setRendered(false), 300);
33
+ return () => window.clearTimeout(timer);
34
+ }
35
+ }, [open]);
36
+ (0, react.useEffect)(() => {
37
+ if (!open) return;
38
+ const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
39
+ const prevOverflow = document.body.style.overflow;
40
+ document.body.style.overflow = "hidden";
41
+ const timer = window.setTimeout(() => {
42
+ const panel = panelRef.current;
43
+ ((panel ? Array.from(panel.querySelectorAll(FOCUSABLE)) : [])[0] ?? panel)?.focus();
44
+ }, 0);
45
+ return () => {
46
+ window.clearTimeout(timer);
47
+ document.body.style.overflow = prevOverflow;
48
+ previousFocus?.focus();
49
+ };
50
+ }, [open]);
51
+ (0, react.useEffect)(() => {
52
+ if (!open) return;
53
+ const handleKeyDown = (e) => {
54
+ if (e.key === "Escape") {
55
+ e.preventDefault();
56
+ onClose();
57
+ return;
58
+ }
59
+ if (e.key !== "Tab") return;
60
+ const panel = panelRef.current;
61
+ if (!panel) return;
62
+ const focusable = Array.from(panel.querySelectorAll(FOCUSABLE));
63
+ if (!focusable.length) {
64
+ e.preventDefault();
65
+ panel.focus();
66
+ return;
67
+ }
68
+ const first = focusable[0];
69
+ const last = focusable[focusable.length - 1];
70
+ if (e.shiftKey && document.activeElement === first) {
71
+ e.preventDefault();
72
+ last.focus();
73
+ } else if (!e.shiftKey && document.activeElement === last) {
74
+ e.preventDefault();
75
+ first.focus();
76
+ }
77
+ };
78
+ document.addEventListener("keydown", handleKeyDown);
79
+ return () => document.removeEventListener("keydown", handleKeyDown);
80
+ }, [open, onClose]);
81
+ if (!rendered) return null;
82
+ const panelClass = [
83
+ PLACEMENT_PANEL_CLASS[placement],
84
+ placement === "right" ? width : maxHeight,
85
+ "bg-white shadow-xl focus:outline-none",
86
+ placement === "bottom" ? "overflow-y-auto" : "overflow-hidden",
87
+ "transition-transform duration-300 ease-out",
88
+ placement === "right" ? visible ? "translate-x-0" : "translate-x-full" : visible ? "translate-y-0" : "translate-y-full"
89
+ ].filter(Boolean).join(" ");
90
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
91
+ className: "fixed inset-0 z-50",
92
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
93
+ className: `fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? "opacity-100" : "opacity-0"}`,
94
+ onClick: onClose,
95
+ "aria-hidden": "true"
96
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
97
+ ref: panelRef,
98
+ role: "dialog",
99
+ "aria-modal": "true",
100
+ "aria-labelledby": titleId,
101
+ "aria-describedby": description ? `${titleId}-desc` : void 0,
102
+ tabIndex: -1,
103
+ className: panelClass,
104
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105
+ className: "flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0",
106
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
107
+ className: "min-w-0",
108
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
109
+ id: titleId,
110
+ className: "font-heading text-base font-bold text-slate-900 truncate",
111
+ children: title
112
+ }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
113
+ id: `${titleId}-desc`,
114
+ className: "text-xs text-slate-500 mt-0.5",
115
+ children: description
116
+ })]
117
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
118
+ type: "button",
119
+ onClick: onClose,
120
+ "aria-label": closeLabel,
121
+ className: "ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
122
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
123
+ size: 16,
124
+ "aria-hidden": "true"
125
+ })
126
+ })]
127
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
128
+ className: "flex-1 overflow-y-auto px-5 py-4",
129
+ children
130
+ })]
131
+ })]
132
+ }), document.body);
133
+ }
134
+ //#endregion
135
+ exports.Drawer = Drawer;
136
+
137
+ //# sourceMappingURL=index39.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index39.cjs","names":[],"sources":["../src/ui-library/gallery/drawer/Drawer.tsx"],"sourcesContent":["/**\n * Drawer — Design System Component\n *\n * A slide-in panel anchored to the screen edge. Supports right (default)\n * and bottom placements.\n *\n * Adaptation note: framer-motion animation replaced with CSS transitions\n * (translate + opacity) for the standalone gallery which does not include\n * framer-motion as a dependency.\n */\n\nimport { useEffect, useId, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { X } from 'lucide-react';\n\nexport type DrawerPlacement = 'right' | 'bottom';\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n title: ReactNode;\n description?: string;\n placement?: DrawerPlacement;\n width?: string;\n maxHeight?: string;\n closeLabel?: string;\n children: ReactNode;\n}\n\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nconst PLACEMENT_PANEL_CLASS: Record<DrawerPlacement, string> = {\n right: 'fixed top-0 right-0 bottom-0 flex flex-col',\n bottom: 'fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl',\n};\n\nexport function Drawer({\n open,\n onClose,\n title,\n description,\n placement = 'right',\n width = 'w-80',\n maxHeight = 'max-h-[85vh]',\n closeLabel = 'Close drawer',\n children,\n}: DrawerProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // rendered tracks whether the portal is mounted at all\n const [rendered, setRendered] = useState(open);\n // visible tracks the CSS transition state\n const [visible, setVisible] = useState(false);\n\n useEffect(() => {\n if (open) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setRendered(true);\n // Allow the element to mount before starting the transition\n requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));\n } else {\n setVisible(false);\n // Remove from DOM after transition completes\n const timer = window.setTimeout(() => setRendered(false), 300);\n return () => window.clearTimeout(timer);\n }\n }, [open]);\n\n // Effect 1: scroll lock + initial focus + focus restoration\n useEffect(() => {\n if (!open) return;\n const previousFocus = document.activeElement instanceof HTMLElement\n ? document.activeElement : null;\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n\n const timer = window.setTimeout(() => {\n const panel = panelRef.current;\n const focusable = panel ? Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)) : [];\n (focusable[0] ?? panel)?.focus();\n }, 0);\n\n return () => {\n window.clearTimeout(timer);\n document.body.style.overflow = prevOverflow;\n previousFocus?.focus();\n };\n }, [open]);\n\n // Effect 2: keyboard — Escape + focus trap\n useEffect(() => {\n if (!open) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') { e.preventDefault(); onClose(); return; }\n if (e.key !== 'Tab') return;\n const panel = panelRef.current;\n if (!panel) return;\n const focusable = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE));\n if (!focusable.length) { e.preventDefault(); panel.focus(); return; }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }\n else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [open, onClose]);\n\n if (!rendered) return null;\n\n const panelClass = [\n PLACEMENT_PANEL_CLASS[placement],\n placement === 'right' ? width : maxHeight,\n 'bg-white shadow-xl focus:outline-none',\n placement === 'bottom' ? 'overflow-y-auto' : 'overflow-hidden',\n 'transition-transform duration-300 ease-out',\n placement === 'right'\n ? (visible ? 'translate-x-0' : 'translate-x-full')\n : (visible ? 'translate-y-0' : 'translate-y-full'),\n ].filter(Boolean).join(' ');\n\n return createPortal(\n <div className=\"fixed inset-0 z-50\">\n {/* Backdrop */}\n <div\n className={`fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? 'opacity-100' : 'opacity-0'}`}\n onClick={onClose}\n aria-hidden=\"true\"\n />\n\n {/* Panel */}\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={description ? `${titleId}-desc` : undefined}\n tabIndex={-1}\n className={panelClass}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0\">\n <div className=\"min-w-0\">\n <h2 id={titleId} className=\"font-heading text-base font-bold text-slate-900 truncate\">\n {title}\n </h2>\n {description && (\n <p id={`${titleId}-desc`} className=\"text-xs text-slate-500 mt-0.5\">\n {description}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={closeLabel}\n className=\"ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <X size={16} aria-hidden=\"true\" />\n </button>\n </div>\n\n {/* Scrollable body */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {children}\n </div>\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from "react";
3
+ import { X } from "lucide-react";
4
+ import { createPortal } from "react-dom";
5
+ //#region src/ui-library/gallery/drawer/Drawer.tsx
6
+ /**
7
+ * Drawer — Design System Component
8
+ *
9
+ * A slide-in panel anchored to the screen edge. Supports right (default)
10
+ * and bottom placements.
11
+ *
12
+ * Adaptation note: framer-motion animation replaced with CSS transitions
13
+ * (translate + opacity) for the standalone gallery which does not include
14
+ * framer-motion as a dependency.
15
+ */
16
+ var FOCUSABLE = "a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])";
17
+ var PLACEMENT_PANEL_CLASS = {
18
+ right: "fixed top-0 right-0 bottom-0 flex flex-col",
19
+ bottom: "fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl"
20
+ };
21
+ function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children }) {
22
+ const panelRef = useRef(null);
23
+ const titleId = useId();
24
+ const [rendered, setRendered] = useState(open);
25
+ const [visible, setVisible] = useState(false);
26
+ useEffect(() => {
27
+ if (open) {
28
+ setRendered(true);
29
+ requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));
30
+ } else {
31
+ setVisible(false);
32
+ const timer = window.setTimeout(() => setRendered(false), 300);
33
+ return () => window.clearTimeout(timer);
34
+ }
35
+ }, [open]);
36
+ useEffect(() => {
37
+ if (!open) return;
38
+ const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
39
+ const prevOverflow = document.body.style.overflow;
40
+ document.body.style.overflow = "hidden";
41
+ const timer = window.setTimeout(() => {
42
+ const panel = panelRef.current;
43
+ ((panel ? Array.from(panel.querySelectorAll(FOCUSABLE)) : [])[0] ?? panel)?.focus();
44
+ }, 0);
45
+ return () => {
46
+ window.clearTimeout(timer);
47
+ document.body.style.overflow = prevOverflow;
48
+ previousFocus?.focus();
49
+ };
50
+ }, [open]);
51
+ useEffect(() => {
52
+ if (!open) return;
53
+ const handleKeyDown = (e) => {
54
+ if (e.key === "Escape") {
55
+ e.preventDefault();
56
+ onClose();
57
+ return;
58
+ }
59
+ if (e.key !== "Tab") return;
60
+ const panel = panelRef.current;
61
+ if (!panel) return;
62
+ const focusable = Array.from(panel.querySelectorAll(FOCUSABLE));
63
+ if (!focusable.length) {
64
+ e.preventDefault();
65
+ panel.focus();
66
+ return;
67
+ }
68
+ const first = focusable[0];
69
+ const last = focusable[focusable.length - 1];
70
+ if (e.shiftKey && document.activeElement === first) {
71
+ e.preventDefault();
72
+ last.focus();
73
+ } else if (!e.shiftKey && document.activeElement === last) {
74
+ e.preventDefault();
75
+ first.focus();
76
+ }
77
+ };
78
+ document.addEventListener("keydown", handleKeyDown);
79
+ return () => document.removeEventListener("keydown", handleKeyDown);
80
+ }, [open, onClose]);
81
+ if (!rendered) return null;
82
+ const panelClass = [
83
+ PLACEMENT_PANEL_CLASS[placement],
84
+ placement === "right" ? width : maxHeight,
85
+ "bg-white shadow-xl focus:outline-none",
86
+ placement === "bottom" ? "overflow-y-auto" : "overflow-hidden",
87
+ "transition-transform duration-300 ease-out",
88
+ placement === "right" ? visible ? "translate-x-0" : "translate-x-full" : visible ? "translate-y-0" : "translate-y-full"
89
+ ].filter(Boolean).join(" ");
90
+ return createPortal(/* @__PURE__ */ jsxs("div", {
91
+ className: "fixed inset-0 z-50",
92
+ children: [/* @__PURE__ */ jsx("div", {
93
+ className: `fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? "opacity-100" : "opacity-0"}`,
94
+ onClick: onClose,
95
+ "aria-hidden": "true"
96
+ }), /* @__PURE__ */ jsxs("div", {
97
+ ref: panelRef,
98
+ role: "dialog",
99
+ "aria-modal": "true",
100
+ "aria-labelledby": titleId,
101
+ "aria-describedby": description ? `${titleId}-desc` : void 0,
102
+ tabIndex: -1,
103
+ className: panelClass,
104
+ children: [/* @__PURE__ */ jsxs("div", {
105
+ className: "flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0",
106
+ children: [/* @__PURE__ */ jsxs("div", {
107
+ className: "min-w-0",
108
+ children: [/* @__PURE__ */ jsx("h2", {
109
+ id: titleId,
110
+ className: "font-heading text-base font-bold text-slate-900 truncate",
111
+ children: title
112
+ }), description && /* @__PURE__ */ jsx("p", {
113
+ id: `${titleId}-desc`,
114
+ className: "text-xs text-slate-500 mt-0.5",
115
+ children: description
116
+ })]
117
+ }), /* @__PURE__ */ jsx("button", {
118
+ type: "button",
119
+ onClick: onClose,
120
+ "aria-label": closeLabel,
121
+ className: "ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
122
+ children: /* @__PURE__ */ jsx(X, {
123
+ size: 16,
124
+ "aria-hidden": "true"
125
+ })
126
+ })]
127
+ }), /* @__PURE__ */ jsx("div", {
128
+ className: "flex-1 overflow-y-auto px-5 py-4",
129
+ children
130
+ })]
131
+ })]
132
+ }), document.body);
133
+ }
134
+ //#endregion
135
+ export { Drawer };
136
+
137
+ //# sourceMappingURL=index39.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index39.mjs","names":[],"sources":["../src/ui-library/gallery/drawer/Drawer.tsx"],"sourcesContent":["/**\n * Drawer — Design System Component\n *\n * A slide-in panel anchored to the screen edge. Supports right (default)\n * and bottom placements.\n *\n * Adaptation note: framer-motion animation replaced with CSS transitions\n * (translate + opacity) for the standalone gallery which does not include\n * framer-motion as a dependency.\n */\n\nimport { useEffect, useId, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { X } from 'lucide-react';\n\nexport type DrawerPlacement = 'right' | 'bottom';\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n title: ReactNode;\n description?: string;\n placement?: DrawerPlacement;\n width?: string;\n maxHeight?: string;\n closeLabel?: string;\n children: ReactNode;\n}\n\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nconst PLACEMENT_PANEL_CLASS: Record<DrawerPlacement, string> = {\n right: 'fixed top-0 right-0 bottom-0 flex flex-col',\n bottom: 'fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl',\n};\n\nexport function Drawer({\n open,\n onClose,\n title,\n description,\n placement = 'right',\n width = 'w-80',\n maxHeight = 'max-h-[85vh]',\n closeLabel = 'Close drawer',\n children,\n}: DrawerProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // rendered tracks whether the portal is mounted at all\n const [rendered, setRendered] = useState(open);\n // visible tracks the CSS transition state\n const [visible, setVisible] = useState(false);\n\n useEffect(() => {\n if (open) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setRendered(true);\n // Allow the element to mount before starting the transition\n requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));\n } else {\n setVisible(false);\n // Remove from DOM after transition completes\n const timer = window.setTimeout(() => setRendered(false), 300);\n return () => window.clearTimeout(timer);\n }\n }, [open]);\n\n // Effect 1: scroll lock + initial focus + focus restoration\n useEffect(() => {\n if (!open) return;\n const previousFocus = document.activeElement instanceof HTMLElement\n ? document.activeElement : null;\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n\n const timer = window.setTimeout(() => {\n const panel = panelRef.current;\n const focusable = panel ? Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)) : [];\n (focusable[0] ?? panel)?.focus();\n }, 0);\n\n return () => {\n window.clearTimeout(timer);\n document.body.style.overflow = prevOverflow;\n previousFocus?.focus();\n };\n }, [open]);\n\n // Effect 2: keyboard — Escape + focus trap\n useEffect(() => {\n if (!open) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') { e.preventDefault(); onClose(); return; }\n if (e.key !== 'Tab') return;\n const panel = panelRef.current;\n if (!panel) return;\n const focusable = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE));\n if (!focusable.length) { e.preventDefault(); panel.focus(); return; }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }\n else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [open, onClose]);\n\n if (!rendered) return null;\n\n const panelClass = [\n PLACEMENT_PANEL_CLASS[placement],\n placement === 'right' ? width : maxHeight,\n 'bg-white shadow-xl focus:outline-none',\n placement === 'bottom' ? 'overflow-y-auto' : 'overflow-hidden',\n 'transition-transform duration-300 ease-out',\n placement === 'right'\n ? (visible ? 'translate-x-0' : 'translate-x-full')\n : (visible ? 'translate-y-0' : 'translate-y-full'),\n ].filter(Boolean).join(' ');\n\n return createPortal(\n <div className=\"fixed inset-0 z-50\">\n {/* Backdrop */}\n <div\n className={`fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? 'opacity-100' : 'opacity-0'}`}\n onClick={onClose}\n aria-hidden=\"true\"\n />\n\n {/* Panel */}\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={description ? `${titleId}-desc` : undefined}\n tabIndex={-1}\n className={panelClass}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0\">\n <div className=\"min-w-0\">\n <h2 id={titleId} className=\"font-heading text-base font-bold text-slate-900 truncate\">\n {title}\n </h2>\n {description && (\n <p id={`${titleId}-desc`} className=\"text-xs text-slate-500 mt-0.5\">\n {description}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={closeLabel}\n className=\"ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <X size={16} aria-hidden=\"true\" />\n </button>\n </div>\n\n {/* Scrollable body */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {children}\n </div>\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":"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"}
package/lib/index4.cjs ADDED
@@ -0,0 +1,24 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ //#region src/ui-library/gallery/icon-button/IconButton.tsx
3
+ var BASE = "inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none";
4
+ var VARIANT_STYLES = {
5
+ default: "hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20",
6
+ edit: "hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20",
7
+ danger: "hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100"
8
+ };
9
+ function IconButton({ icon, variant = "default", className = "", ...props }) {
10
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11
+ type: "button",
12
+ className: [
13
+ BASE,
14
+ VARIANT_STYLES[variant],
15
+ className
16
+ ].filter(Boolean).join(" "),
17
+ ...props,
18
+ children: icon
19
+ });
20
+ }
21
+ //#endregion
22
+ exports.IconButton = IconButton;
23
+
24
+ //# sourceMappingURL=index4.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index4.cjs","names":[],"sources":["../src/ui-library/gallery/icon-button/IconButton.tsx"],"sourcesContent":["/**\n * IconButton — Design System Component\n *\n * A square icon-only button with three visual variants.\n *\n * Accessibility:\n * - aria-label is REQUIRED — icon buttons have no visible text.\n * - Mark the icon inside aria-hidden=\"true\" to avoid double-announcement.\n * - Focus ring is focus-visible only (keyboard, not mouse click).\n * - disabled attribute prevents interaction and announces via AT.\n * - title provides a browser tooltip as a secondary affordance but is NOT\n * a substitute for aria-label.\n *\n * Usage:\n * <IconButton icon={<Pencil size={16} aria-hidden=\"true\" />} aria-label=\"Edit Engineering\" variant=\"edit\" />\n * <IconButton icon={<Trash2 size={16} aria-hidden=\"true\" />} aria-label=\"Delete record\" variant=\"danger\" />\n * <IconButton icon={<Settings size={16} aria-hidden=\"true\" />} aria-label=\"Open settings\" />\n */\n\nimport type { ReactNode } from 'react';\n\nexport type IconButtonVariant = 'default' | 'edit' | 'danger';\n\nexport interface IconButtonProps extends React.ComponentProps<'button'> {\n /** Icon element. Use aria-hidden=\"true\" on the icon to avoid double-announcement. */\n icon: ReactNode;\n /** Visual variant. Default: \"default\" (slate hover) */\n variant?: IconButtonVariant;\n}\n\n// ── Style maps ─────────────────────────────────────────────────────────────\n\nconst BASE =\n 'inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out' +\n ' active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1' +\n ' disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none';\n\nconst VARIANT_STYLES: Record<IconButtonVariant, string> = {\n default: 'hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20',\n edit: 'hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20',\n danger: 'hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100',\n};\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nexport function IconButton({\n icon,\n variant = 'default',\n className = '',\n ...props\n}: IconButtonProps) {\n return (\n <button\n type=\"button\"\n className={[BASE, VARIANT_STYLES[variant], className].filter(Boolean).join(' ')}\n {...props}\n >\n {icon}\n </button>\n );\n}\n"],"mappings":";;AAgCA,IAAM,OACJ;AAIF,IAAM,iBAAoD;CACxD,SAAS;CACT,MAAS;CACT,QAAS;AACX;AAIA,SAAgB,WAAW,EACzB,MACA,UAAU,WACV,YAAY,IACZ,GAAG,SACe;CAClB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;EACE,MAAK;EACL,WAAW;GAAC;GAAM,eAAe;GAAU;EAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAC9E,GAAI;EAEH,UAAA;CACK,CAAA;AAEZ"}
package/lib/index4.mjs ADDED
@@ -0,0 +1,24 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ //#region src/ui-library/gallery/icon-button/IconButton.tsx
3
+ var BASE = "inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none";
4
+ var VARIANT_STYLES = {
5
+ default: "hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20",
6
+ edit: "hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20",
7
+ danger: "hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100"
8
+ };
9
+ function IconButton({ icon, variant = "default", className = "", ...props }) {
10
+ return /* @__PURE__ */ jsx("button", {
11
+ type: "button",
12
+ className: [
13
+ BASE,
14
+ VARIANT_STYLES[variant],
15
+ className
16
+ ].filter(Boolean).join(" "),
17
+ ...props,
18
+ children: icon
19
+ });
20
+ }
21
+ //#endregion
22
+ export { IconButton };
23
+
24
+ //# sourceMappingURL=index4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index4.mjs","names":[],"sources":["../src/ui-library/gallery/icon-button/IconButton.tsx"],"sourcesContent":["/**\n * IconButton — Design System Component\n *\n * A square icon-only button with three visual variants.\n *\n * Accessibility:\n * - aria-label is REQUIRED — icon buttons have no visible text.\n * - Mark the icon inside aria-hidden=\"true\" to avoid double-announcement.\n * - Focus ring is focus-visible only (keyboard, not mouse click).\n * - disabled attribute prevents interaction and announces via AT.\n * - title provides a browser tooltip as a secondary affordance but is NOT\n * a substitute for aria-label.\n *\n * Usage:\n * <IconButton icon={<Pencil size={16} aria-hidden=\"true\" />} aria-label=\"Edit Engineering\" variant=\"edit\" />\n * <IconButton icon={<Trash2 size={16} aria-hidden=\"true\" />} aria-label=\"Delete record\" variant=\"danger\" />\n * <IconButton icon={<Settings size={16} aria-hidden=\"true\" />} aria-label=\"Open settings\" />\n */\n\nimport type { ReactNode } from 'react';\n\nexport type IconButtonVariant = 'default' | 'edit' | 'danger';\n\nexport interface IconButtonProps extends React.ComponentProps<'button'> {\n /** Icon element. Use aria-hidden=\"true\" on the icon to avoid double-announcement. */\n icon: ReactNode;\n /** Visual variant. Default: \"default\" (slate hover) */\n variant?: IconButtonVariant;\n}\n\n// ── Style maps ─────────────────────────────────────────────────────────────\n\nconst BASE =\n 'inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out' +\n ' active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1' +\n ' disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none';\n\nconst VARIANT_STYLES: Record<IconButtonVariant, string> = {\n default: 'hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20',\n edit: 'hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20',\n danger: 'hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100',\n};\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nexport function IconButton({\n icon,\n variant = 'default',\n className = '',\n ...props\n}: IconButtonProps) {\n return (\n <button\n type=\"button\"\n className={[BASE, VARIANT_STYLES[variant], className].filter(Boolean).join(' ')}\n {...props}\n >\n {icon}\n </button>\n );\n}\n"],"mappings":";;AAgCA,IAAM,OACJ;AAIF,IAAM,iBAAoD;CACxD,SAAS;CACT,MAAS;CACT,QAAS;AACX;AAIA,SAAgB,WAAW,EACzB,MACA,UAAU,WACV,YAAY,IACZ,GAAG,SACe;CAClB,OACE,oBAAC,UAAD;EACE,MAAK;EACL,WAAW;GAAC;GAAM,eAAe;GAAU;EAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAC9E,GAAI;EAEH,UAAA;CACK,CAAA;AAEZ"}
@@ -0,0 +1,130 @@
1
+ const require_runtime = require("./index2.cjs");
2
+ const require_utils = require("./index14.cjs");
3
+ let react_jsx_runtime = require("react/jsx-runtime");
4
+ let react = require("react");
5
+ let _radix_ui_react_popover = require("@radix-ui/react-popover");
6
+ _radix_ui_react_popover = require_runtime.__toESM(_radix_ui_react_popover, 1);
7
+ //#region src/ui-library/gallery/popover/Popover.tsx
8
+ /**
9
+ * Popover — Design System Component
10
+ *
11
+ * An anchored floating panel that opens on trigger interaction.
12
+ *
13
+ * Behavior is provided by @radix-ui/react-popover, which handles:
14
+ * - Open/close state (both controlled and uncontrolled)
15
+ * - Trigger click interaction
16
+ * - Escape key closes the popover + focus returns to trigger
17
+ * - Outside-click (pointer-down) closes the popover
18
+ * - Focus moves into the content on open
19
+ * - Portal rendering to document.body
20
+ * - Viewport collision detection and automatic side-flip
21
+ * - aria-expanded, aria-haspopup, aria-controls wiring
22
+ * - role="dialog" on the content panel
23
+ *
24
+ * DIWA continues to own:
25
+ * - Public API (open, onOpenChange, defaultOpen, placement, title, maxWidth, className, children)
26
+ * - Trigger visual treatment (button styling)
27
+ * - Content panel visual treatment (bg, border, radius, shadow, padding)
28
+ * - Placement vocabulary (DIWA names mapped internally to Radix side+align)
29
+ * - Typography and color tokens
30
+ * - Component anatomy and composition model
31
+ *
32
+ * Composition:
33
+ * <Popover>
34
+ * <PopoverTrigger>Open</PopoverTrigger>
35
+ * <PopoverContent title="Settings">…</PopoverContent>
36
+ * </Popover>
37
+ *
38
+ * Placement: 'bottom-start' | 'bottom-end' | 'bottom' |
39
+ * 'top-start' | 'top-end' | 'top'
40
+ *
41
+ * Difference from Dialog/Modal:
42
+ * - Popover is anchored to a trigger and positioned near it.
43
+ * - Dialog/Modal is centered in the viewport and blocks the page.
44
+ * - Tooltip shows text-only hints on hover/focus without interaction.
45
+ */
46
+ function toRadixPlacement(placement) {
47
+ switch (placement) {
48
+ case "bottom-start": return {
49
+ side: "bottom",
50
+ align: "start"
51
+ };
52
+ case "bottom": return {
53
+ side: "bottom",
54
+ align: "center"
55
+ };
56
+ case "bottom-end": return {
57
+ side: "bottom",
58
+ align: "end"
59
+ };
60
+ case "top-start": return {
61
+ side: "top",
62
+ align: "start"
63
+ };
64
+ case "top": return {
65
+ side: "top",
66
+ align: "center"
67
+ };
68
+ case "top-end": return {
69
+ side: "top",
70
+ align: "end"
71
+ };
72
+ }
73
+ }
74
+ function Popover({ open, onOpenChange, defaultOpen = false, placement = "bottom-start", children }) {
75
+ const { side, align } = toRadixPlacement(placement);
76
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Root, {
77
+ open,
78
+ onOpenChange,
79
+ defaultOpen,
80
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverPlacementContext.Provider, {
81
+ value: {
82
+ side,
83
+ align
84
+ },
85
+ children
86
+ })
87
+ });
88
+ }
89
+ var PopoverPlacementContext = (0, react.createContext)({
90
+ side: "bottom",
91
+ align: "start"
92
+ });
93
+ function usePlacement() {
94
+ return (0, react.useContext)(PopoverPlacementContext);
95
+ }
96
+ function PopoverTrigger({ children }) {
97
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Trigger, {
98
+ asChild: true,
99
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
100
+ type: "button",
101
+ className: "inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 shadow-sm transition hover:border-slate-300 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
102
+ children
103
+ })
104
+ });
105
+ }
106
+ function PopoverContent({ title, children, maxWidth = "max-w-xs", className = "" }) {
107
+ const { side, align } = usePlacement();
108
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_popover.Content, {
109
+ side,
110
+ align,
111
+ sideOffset: 8,
112
+ className: require_utils.cn("z-50 w-max rounded-xl border border-slate-200 bg-white shadow-lg", "outline-none", maxWidth, className),
113
+ children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
114
+ className: "border-b border-slate-100 px-4 py-3",
115
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
116
+ className: "font-heading text-sm font-bold text-slate-900",
117
+ children: title
118
+ })
119
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
120
+ className: "px-4 py-3",
121
+ children
122
+ })]
123
+ }) });
124
+ }
125
+ //#endregion
126
+ exports.Popover = Popover;
127
+ exports.PopoverContent = PopoverContent;
128
+ exports.PopoverTrigger = PopoverTrigger;
129
+
130
+ //# sourceMappingURL=index40.cjs.map