@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,213 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ let react = require("react");
3
+ let lucide_react = require("lucide-react");
4
+ //#region src/ui-library/gallery/tree-view/TreeView.tsx
5
+ /**
6
+ * TreeView — Design System Component
7
+ *
8
+ * Hierarchical tree with expand/collapse, selection, and keyboard navigation.
9
+ *
10
+ * WAI-ARIA Tree pattern:
11
+ * role="tree" on the root <ul>
12
+ * role="treeitem" + aria-expanded + aria-selected on each item
13
+ * role="group" on nested <ul> children
14
+ *
15
+ * Keyboard (WAI-ARIA authoring guide):
16
+ * ArrowDown — next visible item
17
+ * ArrowUp — previous visible item
18
+ * ArrowRight — expand collapsed node, or move to first child
19
+ * ArrowLeft — collapse expanded node, or move to parent
20
+ * Home — first item in tree
21
+ * End — last visible item in tree
22
+ * Enter — select item
23
+ * Space — select item
24
+ *
25
+ * Data model:
26
+ * const items: TreeItem[] = [
27
+ * { id: '1', label: 'Parent', children: [
28
+ * { id: '1.1', label: 'Child' },
29
+ * ]},
30
+ * ];
31
+ *
32
+ * <TreeView
33
+ * items={items}
34
+ * selectedId={selected}
35
+ * onSelect={setSelected}
36
+ * />
37
+ */
38
+ function flattenVisible(items, expanded) {
39
+ const result = [];
40
+ for (const item of items) {
41
+ result.push(item);
42
+ if (item.children && expanded.has(item.id)) result.push(...flattenVisible(item.children, expanded));
43
+ }
44
+ return result;
45
+ }
46
+ function findParent(items, id) {
47
+ for (const item of items) if (item.children) {
48
+ if (item.children.some((c) => c.id === id)) return item;
49
+ const found = findParent(item.children, id);
50
+ if (found) return found;
51
+ }
52
+ return null;
53
+ }
54
+ function TreeNode({ item, depth, selectedId, expanded, toggleExpand, onSelect, baseId }) {
55
+ const hasChildren = Boolean(item.children?.length);
56
+ const isExpanded = expanded.has(item.id);
57
+ const isSelected = item.id === selectedId;
58
+ const handleClick = () => {
59
+ if (item.disabled) return;
60
+ if (hasChildren) toggleExpand(item.id);
61
+ onSelect(item.id);
62
+ };
63
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
64
+ id: `${baseId}-${item.id}`,
65
+ role: "treeitem",
66
+ "aria-expanded": hasChildren ? isExpanded : void 0,
67
+ "aria-selected": isSelected,
68
+ "aria-disabled": item.disabled || void 0,
69
+ "aria-level": depth,
70
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
71
+ type: "button",
72
+ onClick: handleClick,
73
+ disabled: item.disabled,
74
+ style: { paddingLeft: `${(depth - 1) * 16 + 8}px` },
75
+ className: [
76
+ "flex w-full items-center gap-1.5 rounded-lg py-1.5 pr-3 text-sm font-medium transition",
77
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
78
+ isSelected ? "bg-blue-50 font-semibold text-brand-blue" : item.disabled ? "cursor-not-allowed text-slate-300" : "text-slate-700 hover:bg-slate-100"
79
+ ].filter(Boolean).join(" "),
80
+ children: [
81
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
82
+ className: `flex h-4 w-4 shrink-0 items-center justify-center transition-transform ${hasChildren ? "" : "invisible"} ${isExpanded ? "rotate-90" : ""}`,
83
+ "aria-hidden": "true",
84
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { size: 12 })
85
+ }),
86
+ item.icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
87
+ className: "shrink-0 text-slate-400",
88
+ "aria-hidden": "true",
89
+ children: item.icon
90
+ }),
91
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
92
+ className: "truncate",
93
+ children: item.label
94
+ })
95
+ ]
96
+ }), hasChildren && isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
97
+ role: "group",
98
+ children: item.children.map((child) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNode, {
99
+ item: child,
100
+ depth: depth + 1,
101
+ selectedId,
102
+ expanded,
103
+ toggleExpand,
104
+ onSelect,
105
+ baseId
106
+ }, child.id))
107
+ })]
108
+ });
109
+ }
110
+ function TreeView({ items, selectedId, onSelect, "aria-label": ariaLabel, className = "" }) {
111
+ const baseId = (0, react.useId)().replace(/:/g, "");
112
+ const [expanded, setExpanded] = (0, react.useState)(() => {
113
+ const s = /* @__PURE__ */ new Set();
114
+ items.forEach((i) => {
115
+ if (i.children?.length) s.add(i.id);
116
+ });
117
+ return s;
118
+ });
119
+ const treeRef = (0, react.useRef)(null);
120
+ const toggleExpand = (0, react.useCallback)((id) => {
121
+ setExpanded((prev) => {
122
+ const next = new Set(prev);
123
+ if (next.has(id)) next.delete(id);
124
+ else next.add(id);
125
+ return next;
126
+ });
127
+ }, []);
128
+ const handleSelect = (0, react.useCallback)((id) => {
129
+ onSelect?.(id);
130
+ }, [onSelect]);
131
+ const handleKeyDown = (e) => {
132
+ const visible = flattenVisible(items, expanded);
133
+ if (visible.length === 0) return;
134
+ const currentId = selectedId ?? visible[0]?.id;
135
+ const currentIdx = visible.findIndex((v) => v.id === currentId);
136
+ const current = visible[currentIdx];
137
+ const focusItem = (id) => {
138
+ handleSelect(id);
139
+ document.getElementById(`${baseId}-${id}`)?.querySelector("button")?.focus();
140
+ };
141
+ switch (e.key) {
142
+ case "ArrowDown": {
143
+ e.preventDefault();
144
+ const next = visible[currentIdx + 1];
145
+ if (next && !next.disabled) focusItem(next.id);
146
+ break;
147
+ }
148
+ case "ArrowUp": {
149
+ e.preventDefault();
150
+ let prev = visible[currentIdx - 1];
151
+ while (prev && prev.disabled) prev = visible[visible.indexOf(prev) - 1];
152
+ if (prev) focusItem(prev.id);
153
+ break;
154
+ }
155
+ case "ArrowRight":
156
+ e.preventDefault();
157
+ if (current?.children?.length && !expanded.has(current.id)) toggleExpand(current.id);
158
+ else {
159
+ const child = current?.children?.[0];
160
+ if (child && !child.disabled) focusItem(child.id);
161
+ }
162
+ break;
163
+ case "ArrowLeft":
164
+ e.preventDefault();
165
+ if (current?.children?.length && expanded.has(current.id)) toggleExpand(current.id);
166
+ else {
167
+ const parent = findParent(items, current?.id ?? "");
168
+ if (parent) focusItem(parent.id);
169
+ }
170
+ break;
171
+ case "Home": {
172
+ e.preventDefault();
173
+ const first = visible.find((v) => !v.disabled);
174
+ if (first) focusItem(first.id);
175
+ break;
176
+ }
177
+ case "End": {
178
+ e.preventDefault();
179
+ const last = [...visible].reverse().find((v) => !v.disabled);
180
+ if (last) focusItem(last.id);
181
+ break;
182
+ }
183
+ case "Enter":
184
+ case " ":
185
+ e.preventDefault();
186
+ if (current && !current.disabled) {
187
+ if (current.children?.length) toggleExpand(current.id);
188
+ handleSelect(current.id);
189
+ }
190
+ }
191
+ };
192
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
193
+ ref: treeRef,
194
+ role: "tree",
195
+ "aria-label": ariaLabel,
196
+ onKeyDown: handleKeyDown,
197
+ className: `flex flex-col gap-0.5 outline-none ${className}`,
198
+ tabIndex: 0,
199
+ children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNode, {
200
+ item,
201
+ depth: 1,
202
+ selectedId,
203
+ expanded,
204
+ toggleExpand,
205
+ onSelect: handleSelect,
206
+ baseId
207
+ }, item.id))
208
+ });
209
+ }
210
+ //#endregion
211
+ exports.TreeView = TreeView;
212
+
213
+ //# sourceMappingURL=index62.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index62.cjs","names":[],"sources":["../src/ui-library/gallery/tree-view/TreeView.tsx"],"sourcesContent":["/**\n * TreeView — Design System Component\n *\n * Hierarchical tree with expand/collapse, selection, and keyboard navigation.\n *\n * WAI-ARIA Tree pattern:\n * role=\"tree\" on the root <ul>\n * role=\"treeitem\" + aria-expanded + aria-selected on each item\n * role=\"group\" on nested <ul> children\n *\n * Keyboard (WAI-ARIA authoring guide):\n * ArrowDown — next visible item\n * ArrowUp — previous visible item\n * ArrowRight — expand collapsed node, or move to first child\n * ArrowLeft — collapse expanded node, or move to parent\n * Home — first item in tree\n * End — last visible item in tree\n * Enter — select item\n * Space — select item\n *\n * Data model:\n * const items: TreeItem[] = [\n * { id: '1', label: 'Parent', children: [\n * { id: '1.1', label: 'Child' },\n * ]},\n * ];\n *\n * <TreeView\n * items={items}\n * selectedId={selected}\n * onSelect={setSelected}\n * />\n */\n\nimport { useCallback, useId, useRef, useState, type ReactNode } from 'react';\nimport { ChevronRight } from 'lucide-react';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface TreeItem {\n id: string;\n label: string;\n icon?: ReactNode;\n disabled?: boolean;\n children?: TreeItem[];\n}\n\nexport interface TreeViewProps {\n items: TreeItem[];\n /** Currently selected item id */\n selectedId?: string;\n /** Called when an item is selected */\n onSelect?: (id: string) => void;\n /** Optional accessible label for the tree */\n 'aria-label'?: string;\n className?: string;\n}\n\n// ── Flatten visible items for keyboard navigation ─────────────────────────────\n\nfunction flattenVisible(items: TreeItem[], expanded: Set<string>): TreeItem[] {\n const result: TreeItem[] = [];\n for (const item of items) {\n result.push(item);\n if (item.children && expanded.has(item.id)) {\n result.push(...flattenVisible(item.children, expanded));\n }\n }\n return result;\n}\n\n// ── Find parent of a given id ─────────────────────────────────────────────────\n\nfunction findParent(items: TreeItem[], id: string): TreeItem | null {\n for (const item of items) {\n if (item.children) {\n if (item.children.some((c) => c.id === id)) return item;\n const found = findParent(item.children, id);\n if (found) return found;\n }\n }\n return null;\n}\n\n// ── TreeNode (recursive) ──────────────────────────────────────────────────────\n\ninterface TreeNodeProps {\n item: TreeItem;\n depth: number;\n selectedId?: string;\n expanded: Set<string>;\n toggleExpand: (id: string) => void;\n onSelect: (id: string) => void;\n baseId: string;\n}\n\nfunction TreeNode({ item, depth, selectedId, expanded, toggleExpand, onSelect, baseId }: TreeNodeProps) {\n const hasChildren = Boolean(item.children?.length);\n const isExpanded = expanded.has(item.id);\n const isSelected = item.id === selectedId;\n\n const handleClick = () => {\n if (item.disabled) return;\n if (hasChildren) toggleExpand(item.id);\n onSelect(item.id);\n };\n\n return (\n <li\n id={`${baseId}-${item.id}`}\n role=\"treeitem\"\n aria-expanded={hasChildren ? isExpanded : undefined}\n aria-selected={isSelected}\n aria-disabled={item.disabled || undefined}\n aria-level={depth}\n >\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={item.disabled}\n style={{ paddingLeft: `${(depth - 1) * 16 + 8}px` }}\n className={[\n 'flex w-full items-center gap-1.5 rounded-lg py-1.5 pr-3 text-sm font-medium transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n isSelected\n ? 'bg-blue-50 font-semibold text-brand-blue'\n : item.disabled\n ? 'cursor-not-allowed text-slate-300'\n : 'text-slate-700 hover:bg-slate-100',\n ].filter(Boolean).join(' ')}\n >\n {/* Expand/collapse chevron */}\n <span\n className={`flex h-4 w-4 shrink-0 items-center justify-center transition-transform ${\n hasChildren ? '' : 'invisible'\n } ${isExpanded ? 'rotate-90' : ''}`}\n aria-hidden=\"true\"\n >\n <ChevronRight size={12} />\n </span>\n\n {/* Icon */}\n {item.icon && (\n <span className=\"shrink-0 text-slate-400\" aria-hidden=\"true\">\n {item.icon}\n </span>\n )}\n\n {/* Label */}\n <span className=\"truncate\">{item.label}</span>\n </button>\n\n {/* Children */}\n {hasChildren && isExpanded && (\n <ul role=\"group\">\n {item.children!.map((child) => (\n <TreeNode\n key={child.id}\n item={child}\n depth={depth + 1}\n selectedId={selectedId}\n expanded={expanded}\n toggleExpand={toggleExpand}\n onSelect={onSelect}\n baseId={baseId}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\n// ── TreeView (root) ───────────────────────────────────────────────────────────\n\nexport function TreeView({\n items,\n selectedId,\n onSelect,\n 'aria-label': ariaLabel,\n className = '',\n}: TreeViewProps) {\n const baseId = useId().replace(/:/g, '');\n const [expanded, setExpanded] = useState<Set<string>>(() => {\n // Start with top-level nodes expanded\n const s = new Set<string>();\n items.forEach((i) => { if (i.children?.length) s.add(i.id); });\n return s;\n });\n const treeRef = useRef<HTMLUListElement>(null);\n\n const toggleExpand = useCallback((id: string) => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id); else next.add(id);\n return next;\n });\n }, []);\n\n const handleSelect = useCallback((id: string) => {\n onSelect?.(id);\n }, [onSelect]);\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLUListElement>) => {\n const visible = flattenVisible(items, expanded);\n if (visible.length === 0) return;\n\n const currentId = selectedId ?? visible[0]?.id;\n const currentIdx = visible.findIndex((v) => v.id === currentId);\n const current = visible[currentIdx];\n\n const focusItem = (id: string) => {\n handleSelect(id);\n document.getElementById(`${baseId}-${id}`)?.querySelector('button')?.focus();\n };\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const next = visible[currentIdx + 1];\n if (next && !next.disabled) focusItem(next.id);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n let prev = visible[currentIdx - 1];\n while (prev && prev.disabled) {\n const idx = visible.indexOf(prev);\n prev = visible[idx - 1];\n }\n if (prev) focusItem(prev.id);\n break;\n }\n case 'ArrowRight': {\n e.preventDefault();\n if (current?.children?.length && !expanded.has(current.id)) {\n toggleExpand(current.id);\n } else {\n const child = current?.children?.[0];\n if (child && !child.disabled) focusItem(child.id);\n }\n break;\n }\n case 'ArrowLeft': {\n e.preventDefault();\n if (current?.children?.length && expanded.has(current.id)) {\n toggleExpand(current.id);\n } else {\n const parent = findParent(items, current?.id ?? '');\n if (parent) focusItem(parent.id);\n }\n break;\n }\n case 'Home': {\n e.preventDefault();\n const first = visible.find((v) => !v.disabled);\n if (first) focusItem(first.id);\n break;\n }\n case 'End': {\n e.preventDefault();\n const last = [...visible].reverse().find((v) => !v.disabled);\n if (last) focusItem(last.id);\n break;\n }\n case 'Enter':\n case ' ': {\n e.preventDefault();\n if (current && !current.disabled) {\n if (current.children?.length) toggleExpand(current.id);\n handleSelect(current.id);\n }\n break;\n }\n }\n };\n\n return (\n <ul\n ref={treeRef}\n role=\"tree\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={`flex flex-col gap-0.5 outline-none ${className}`}\n tabIndex={0}\n >\n {items.map((item) => (\n <TreeNode\n key={item.id}\n item={item}\n depth={1}\n selectedId={selectedId}\n expanded={expanded}\n toggleExpand={toggleExpand}\n onSelect={handleSelect}\n baseId={baseId}\n />\n ))}\n </ul>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,213 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useId, useRef, useState } from "react";
3
+ import { ChevronRight } from "lucide-react";
4
+ //#region src/ui-library/gallery/tree-view/TreeView.tsx
5
+ /**
6
+ * TreeView — Design System Component
7
+ *
8
+ * Hierarchical tree with expand/collapse, selection, and keyboard navigation.
9
+ *
10
+ * WAI-ARIA Tree pattern:
11
+ * role="tree" on the root <ul>
12
+ * role="treeitem" + aria-expanded + aria-selected on each item
13
+ * role="group" on nested <ul> children
14
+ *
15
+ * Keyboard (WAI-ARIA authoring guide):
16
+ * ArrowDown — next visible item
17
+ * ArrowUp — previous visible item
18
+ * ArrowRight — expand collapsed node, or move to first child
19
+ * ArrowLeft — collapse expanded node, or move to parent
20
+ * Home — first item in tree
21
+ * End — last visible item in tree
22
+ * Enter — select item
23
+ * Space — select item
24
+ *
25
+ * Data model:
26
+ * const items: TreeItem[] = [
27
+ * { id: '1', label: 'Parent', children: [
28
+ * { id: '1.1', label: 'Child' },
29
+ * ]},
30
+ * ];
31
+ *
32
+ * <TreeView
33
+ * items={items}
34
+ * selectedId={selected}
35
+ * onSelect={setSelected}
36
+ * />
37
+ */
38
+ function flattenVisible(items, expanded) {
39
+ const result = [];
40
+ for (const item of items) {
41
+ result.push(item);
42
+ if (item.children && expanded.has(item.id)) result.push(...flattenVisible(item.children, expanded));
43
+ }
44
+ return result;
45
+ }
46
+ function findParent(items, id) {
47
+ for (const item of items) if (item.children) {
48
+ if (item.children.some((c) => c.id === id)) return item;
49
+ const found = findParent(item.children, id);
50
+ if (found) return found;
51
+ }
52
+ return null;
53
+ }
54
+ function TreeNode({ item, depth, selectedId, expanded, toggleExpand, onSelect, baseId }) {
55
+ const hasChildren = Boolean(item.children?.length);
56
+ const isExpanded = expanded.has(item.id);
57
+ const isSelected = item.id === selectedId;
58
+ const handleClick = () => {
59
+ if (item.disabled) return;
60
+ if (hasChildren) toggleExpand(item.id);
61
+ onSelect(item.id);
62
+ };
63
+ return /* @__PURE__ */ jsxs("li", {
64
+ id: `${baseId}-${item.id}`,
65
+ role: "treeitem",
66
+ "aria-expanded": hasChildren ? isExpanded : void 0,
67
+ "aria-selected": isSelected,
68
+ "aria-disabled": item.disabled || void 0,
69
+ "aria-level": depth,
70
+ children: [/* @__PURE__ */ jsxs("button", {
71
+ type: "button",
72
+ onClick: handleClick,
73
+ disabled: item.disabled,
74
+ style: { paddingLeft: `${(depth - 1) * 16 + 8}px` },
75
+ className: [
76
+ "flex w-full items-center gap-1.5 rounded-lg py-1.5 pr-3 text-sm font-medium transition",
77
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
78
+ isSelected ? "bg-blue-50 font-semibold text-brand-blue" : item.disabled ? "cursor-not-allowed text-slate-300" : "text-slate-700 hover:bg-slate-100"
79
+ ].filter(Boolean).join(" "),
80
+ children: [
81
+ /* @__PURE__ */ jsx("span", {
82
+ className: `flex h-4 w-4 shrink-0 items-center justify-center transition-transform ${hasChildren ? "" : "invisible"} ${isExpanded ? "rotate-90" : ""}`,
83
+ "aria-hidden": "true",
84
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 12 })
85
+ }),
86
+ item.icon && /* @__PURE__ */ jsx("span", {
87
+ className: "shrink-0 text-slate-400",
88
+ "aria-hidden": "true",
89
+ children: item.icon
90
+ }),
91
+ /* @__PURE__ */ jsx("span", {
92
+ className: "truncate",
93
+ children: item.label
94
+ })
95
+ ]
96
+ }), hasChildren && isExpanded && /* @__PURE__ */ jsx("ul", {
97
+ role: "group",
98
+ children: item.children.map((child) => /* @__PURE__ */ jsx(TreeNode, {
99
+ item: child,
100
+ depth: depth + 1,
101
+ selectedId,
102
+ expanded,
103
+ toggleExpand,
104
+ onSelect,
105
+ baseId
106
+ }, child.id))
107
+ })]
108
+ });
109
+ }
110
+ function TreeView({ items, selectedId, onSelect, "aria-label": ariaLabel, className = "" }) {
111
+ const baseId = useId().replace(/:/g, "");
112
+ const [expanded, setExpanded] = useState(() => {
113
+ const s = /* @__PURE__ */ new Set();
114
+ items.forEach((i) => {
115
+ if (i.children?.length) s.add(i.id);
116
+ });
117
+ return s;
118
+ });
119
+ const treeRef = useRef(null);
120
+ const toggleExpand = useCallback((id) => {
121
+ setExpanded((prev) => {
122
+ const next = new Set(prev);
123
+ if (next.has(id)) next.delete(id);
124
+ else next.add(id);
125
+ return next;
126
+ });
127
+ }, []);
128
+ const handleSelect = useCallback((id) => {
129
+ onSelect?.(id);
130
+ }, [onSelect]);
131
+ const handleKeyDown = (e) => {
132
+ const visible = flattenVisible(items, expanded);
133
+ if (visible.length === 0) return;
134
+ const currentId = selectedId ?? visible[0]?.id;
135
+ const currentIdx = visible.findIndex((v) => v.id === currentId);
136
+ const current = visible[currentIdx];
137
+ const focusItem = (id) => {
138
+ handleSelect(id);
139
+ document.getElementById(`${baseId}-${id}`)?.querySelector("button")?.focus();
140
+ };
141
+ switch (e.key) {
142
+ case "ArrowDown": {
143
+ e.preventDefault();
144
+ const next = visible[currentIdx + 1];
145
+ if (next && !next.disabled) focusItem(next.id);
146
+ break;
147
+ }
148
+ case "ArrowUp": {
149
+ e.preventDefault();
150
+ let prev = visible[currentIdx - 1];
151
+ while (prev && prev.disabled) prev = visible[visible.indexOf(prev) - 1];
152
+ if (prev) focusItem(prev.id);
153
+ break;
154
+ }
155
+ case "ArrowRight":
156
+ e.preventDefault();
157
+ if (current?.children?.length && !expanded.has(current.id)) toggleExpand(current.id);
158
+ else {
159
+ const child = current?.children?.[0];
160
+ if (child && !child.disabled) focusItem(child.id);
161
+ }
162
+ break;
163
+ case "ArrowLeft":
164
+ e.preventDefault();
165
+ if (current?.children?.length && expanded.has(current.id)) toggleExpand(current.id);
166
+ else {
167
+ const parent = findParent(items, current?.id ?? "");
168
+ if (parent) focusItem(parent.id);
169
+ }
170
+ break;
171
+ case "Home": {
172
+ e.preventDefault();
173
+ const first = visible.find((v) => !v.disabled);
174
+ if (first) focusItem(first.id);
175
+ break;
176
+ }
177
+ case "End": {
178
+ e.preventDefault();
179
+ const last = [...visible].reverse().find((v) => !v.disabled);
180
+ if (last) focusItem(last.id);
181
+ break;
182
+ }
183
+ case "Enter":
184
+ case " ":
185
+ e.preventDefault();
186
+ if (current && !current.disabled) {
187
+ if (current.children?.length) toggleExpand(current.id);
188
+ handleSelect(current.id);
189
+ }
190
+ }
191
+ };
192
+ return /* @__PURE__ */ jsx("ul", {
193
+ ref: treeRef,
194
+ role: "tree",
195
+ "aria-label": ariaLabel,
196
+ onKeyDown: handleKeyDown,
197
+ className: `flex flex-col gap-0.5 outline-none ${className}`,
198
+ tabIndex: 0,
199
+ children: items.map((item) => /* @__PURE__ */ jsx(TreeNode, {
200
+ item,
201
+ depth: 1,
202
+ selectedId,
203
+ expanded,
204
+ toggleExpand,
205
+ onSelect: handleSelect,
206
+ baseId
207
+ }, item.id))
208
+ });
209
+ }
210
+ //#endregion
211
+ export { TreeView };
212
+
213
+ //# sourceMappingURL=index62.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index62.mjs","names":[],"sources":["../src/ui-library/gallery/tree-view/TreeView.tsx"],"sourcesContent":["/**\n * TreeView — Design System Component\n *\n * Hierarchical tree with expand/collapse, selection, and keyboard navigation.\n *\n * WAI-ARIA Tree pattern:\n * role=\"tree\" on the root <ul>\n * role=\"treeitem\" + aria-expanded + aria-selected on each item\n * role=\"group\" on nested <ul> children\n *\n * Keyboard (WAI-ARIA authoring guide):\n * ArrowDown — next visible item\n * ArrowUp — previous visible item\n * ArrowRight — expand collapsed node, or move to first child\n * ArrowLeft — collapse expanded node, or move to parent\n * Home — first item in tree\n * End — last visible item in tree\n * Enter — select item\n * Space — select item\n *\n * Data model:\n * const items: TreeItem[] = [\n * { id: '1', label: 'Parent', children: [\n * { id: '1.1', label: 'Child' },\n * ]},\n * ];\n *\n * <TreeView\n * items={items}\n * selectedId={selected}\n * onSelect={setSelected}\n * />\n */\n\nimport { useCallback, useId, useRef, useState, type ReactNode } from 'react';\nimport { ChevronRight } from 'lucide-react';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface TreeItem {\n id: string;\n label: string;\n icon?: ReactNode;\n disabled?: boolean;\n children?: TreeItem[];\n}\n\nexport interface TreeViewProps {\n items: TreeItem[];\n /** Currently selected item id */\n selectedId?: string;\n /** Called when an item is selected */\n onSelect?: (id: string) => void;\n /** Optional accessible label for the tree */\n 'aria-label'?: string;\n className?: string;\n}\n\n// ── Flatten visible items for keyboard navigation ─────────────────────────────\n\nfunction flattenVisible(items: TreeItem[], expanded: Set<string>): TreeItem[] {\n const result: TreeItem[] = [];\n for (const item of items) {\n result.push(item);\n if (item.children && expanded.has(item.id)) {\n result.push(...flattenVisible(item.children, expanded));\n }\n }\n return result;\n}\n\n// ── Find parent of a given id ─────────────────────────────────────────────────\n\nfunction findParent(items: TreeItem[], id: string): TreeItem | null {\n for (const item of items) {\n if (item.children) {\n if (item.children.some((c) => c.id === id)) return item;\n const found = findParent(item.children, id);\n if (found) return found;\n }\n }\n return null;\n}\n\n// ── TreeNode (recursive) ──────────────────────────────────────────────────────\n\ninterface TreeNodeProps {\n item: TreeItem;\n depth: number;\n selectedId?: string;\n expanded: Set<string>;\n toggleExpand: (id: string) => void;\n onSelect: (id: string) => void;\n baseId: string;\n}\n\nfunction TreeNode({ item, depth, selectedId, expanded, toggleExpand, onSelect, baseId }: TreeNodeProps) {\n const hasChildren = Boolean(item.children?.length);\n const isExpanded = expanded.has(item.id);\n const isSelected = item.id === selectedId;\n\n const handleClick = () => {\n if (item.disabled) return;\n if (hasChildren) toggleExpand(item.id);\n onSelect(item.id);\n };\n\n return (\n <li\n id={`${baseId}-${item.id}`}\n role=\"treeitem\"\n aria-expanded={hasChildren ? isExpanded : undefined}\n aria-selected={isSelected}\n aria-disabled={item.disabled || undefined}\n aria-level={depth}\n >\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={item.disabled}\n style={{ paddingLeft: `${(depth - 1) * 16 + 8}px` }}\n className={[\n 'flex w-full items-center gap-1.5 rounded-lg py-1.5 pr-3 text-sm font-medium transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n isSelected\n ? 'bg-blue-50 font-semibold text-brand-blue'\n : item.disabled\n ? 'cursor-not-allowed text-slate-300'\n : 'text-slate-700 hover:bg-slate-100',\n ].filter(Boolean).join(' ')}\n >\n {/* Expand/collapse chevron */}\n <span\n className={`flex h-4 w-4 shrink-0 items-center justify-center transition-transform ${\n hasChildren ? '' : 'invisible'\n } ${isExpanded ? 'rotate-90' : ''}`}\n aria-hidden=\"true\"\n >\n <ChevronRight size={12} />\n </span>\n\n {/* Icon */}\n {item.icon && (\n <span className=\"shrink-0 text-slate-400\" aria-hidden=\"true\">\n {item.icon}\n </span>\n )}\n\n {/* Label */}\n <span className=\"truncate\">{item.label}</span>\n </button>\n\n {/* Children */}\n {hasChildren && isExpanded && (\n <ul role=\"group\">\n {item.children!.map((child) => (\n <TreeNode\n key={child.id}\n item={child}\n depth={depth + 1}\n selectedId={selectedId}\n expanded={expanded}\n toggleExpand={toggleExpand}\n onSelect={onSelect}\n baseId={baseId}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\n// ── TreeView (root) ───────────────────────────────────────────────────────────\n\nexport function TreeView({\n items,\n selectedId,\n onSelect,\n 'aria-label': ariaLabel,\n className = '',\n}: TreeViewProps) {\n const baseId = useId().replace(/:/g, '');\n const [expanded, setExpanded] = useState<Set<string>>(() => {\n // Start with top-level nodes expanded\n const s = new Set<string>();\n items.forEach((i) => { if (i.children?.length) s.add(i.id); });\n return s;\n });\n const treeRef = useRef<HTMLUListElement>(null);\n\n const toggleExpand = useCallback((id: string) => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id); else next.add(id);\n return next;\n });\n }, []);\n\n const handleSelect = useCallback((id: string) => {\n onSelect?.(id);\n }, [onSelect]);\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLUListElement>) => {\n const visible = flattenVisible(items, expanded);\n if (visible.length === 0) return;\n\n const currentId = selectedId ?? visible[0]?.id;\n const currentIdx = visible.findIndex((v) => v.id === currentId);\n const current = visible[currentIdx];\n\n const focusItem = (id: string) => {\n handleSelect(id);\n document.getElementById(`${baseId}-${id}`)?.querySelector('button')?.focus();\n };\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const next = visible[currentIdx + 1];\n if (next && !next.disabled) focusItem(next.id);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n let prev = visible[currentIdx - 1];\n while (prev && prev.disabled) {\n const idx = visible.indexOf(prev);\n prev = visible[idx - 1];\n }\n if (prev) focusItem(prev.id);\n break;\n }\n case 'ArrowRight': {\n e.preventDefault();\n if (current?.children?.length && !expanded.has(current.id)) {\n toggleExpand(current.id);\n } else {\n const child = current?.children?.[0];\n if (child && !child.disabled) focusItem(child.id);\n }\n break;\n }\n case 'ArrowLeft': {\n e.preventDefault();\n if (current?.children?.length && expanded.has(current.id)) {\n toggleExpand(current.id);\n } else {\n const parent = findParent(items, current?.id ?? '');\n if (parent) focusItem(parent.id);\n }\n break;\n }\n case 'Home': {\n e.preventDefault();\n const first = visible.find((v) => !v.disabled);\n if (first) focusItem(first.id);\n break;\n }\n case 'End': {\n e.preventDefault();\n const last = [...visible].reverse().find((v) => !v.disabled);\n if (last) focusItem(last.id);\n break;\n }\n case 'Enter':\n case ' ': {\n e.preventDefault();\n if (current && !current.disabled) {\n if (current.children?.length) toggleExpand(current.id);\n handleSelect(current.id);\n }\n break;\n }\n }\n };\n\n return (\n <ul\n ref={treeRef}\n role=\"tree\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={`flex flex-col gap-0.5 outline-none ${className}`}\n tabIndex={0}\n >\n {items.map((item) => (\n <TreeNode\n key={item.id}\n item={item}\n depth={1}\n selectedId={selectedId}\n expanded={expanded}\n toggleExpand={toggleExpand}\n onSelect={handleSelect}\n baseId={baseId}\n />\n ))}\n </ul>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAS,eAAe,OAAmB,UAAmC;CAC5E,MAAM,SAAqB,CAAC;CAC5B,KAAK,MAAM,QAAQ,OAAO;EACxB,OAAO,KAAK,IAAI;EAChB,IAAI,KAAK,YAAY,SAAS,IAAI,KAAK,EAAE,GACvC,OAAO,KAAK,GAAG,eAAe,KAAK,UAAU,QAAQ,CAAC;CAE1D;CACA,OAAO;AACT;AAIA,SAAS,WAAW,OAAmB,IAA6B;CAClE,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,UAAU;EACjB,IAAI,KAAK,SAAS,MAAM,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO;EACnD,MAAM,QAAQ,WAAW,KAAK,UAAU,EAAE;EAC1C,IAAI,OAAO,OAAO;CACpB;CAEF,OAAO;AACT;AAcA,SAAS,SAAS,EAAE,MAAM,OAAO,YAAY,UAAU,cAAc,UAAU,UAAyB;CACtG,MAAM,cAAc,QAAQ,KAAK,UAAU,MAAM;CACjD,MAAM,aAAa,SAAS,IAAI,KAAK,EAAE;CACvC,MAAM,aAAa,KAAK,OAAO;CAE/B,MAAM,oBAAoB;EACxB,IAAI,KAAK,UAAU;EACnB,IAAI,aAAa,aAAa,KAAK,EAAE;EACrC,SAAS,KAAK,EAAE;CAClB;CAEA,OACE,qBAAC,MAAD;EACE,IAAI,GAAG,OAAO,GAAG,KAAK;EACtB,MAAK;EACL,iBAAe,cAAc,aAAa,KAAA;EAC1C,iBAAe;EACf,iBAAe,KAAK,YAAY,KAAA;EAChC,cAAY;EANd,UAAA,CAQE,qBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,UAAU,KAAK;GACf,OAAO,EAAE,aAAa,IAAI,QAAQ,KAAK,KAAK,EAAE,IAAI;GAClD,WAAW;IACT;IACA;IACA,aACI,6CACA,KAAK,WACH,sCACA;GACR,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;GAb5B,UAAA;IAgBE,oBAAC,QAAD;KACE,WAAW,0EACT,cAAc,KAAK,YACpB,GAAG,aAAa,cAAc;KAC/B,eAAY;KAEZ,UAAA,oBAAC,cAAD,EAAc,MAAM,GAAK,CAAA;IACrB,CAAA;IAGL,KAAK,QACJ,oBAAC,QAAD;KAAM,WAAU;KAA0B,eAAY;KACnD,UAAA,KAAK;IACF,CAAA;IAIR,oBAAC,QAAD;KAAM,WAAU;KAAY,UAAA,KAAK;IAAY,CAAA;GACvC;EAGP,CAAA,GAAA,eAAe,cACd,oBAAC,MAAD;GAAI,MAAK;GACN,UAAA,KAAK,SAAU,KAAK,UACnB,oBAAC,UAAD;IAEE,MAAM;IACN,OAAO,QAAQ;IACH;IACF;IACI;IACJ;IACF;GACT,GARM,MAAM,EAQZ,CACF;EACC,CAAA,CAEJ;;AAER;AAIA,SAAgB,SAAS,EACvB,OACA,YACA,UACA,cAAc,WACd,YAAY,MACI;CAChB,MAAM,SAAS,MAAM,CAAC,CAAC,QAAQ,MAAM,EAAE;CACvC,MAAM,CAAC,UAAU,eAAe,eAA4B;EAE1D,MAAM,oBAAI,IAAI,IAAY;EAC1B,MAAM,SAAS,MAAM;GAAE,IAAI,EAAE,UAAU,QAAQ,EAAE,IAAI,EAAE,EAAE;EAAG,CAAC;EAC7D,OAAO;CACT,CAAC;CACD,MAAM,UAAU,OAAyB,IAAI;CAE7C,MAAM,eAAe,aAAa,OAAe;EAC/C,aAAa,SAAS;GACpB,MAAM,OAAO,IAAI,IAAI,IAAI;GACzB,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,EAAE;QAAQ,KAAK,IAAI,EAAE;GACnD,OAAO;EACT,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,MAAM,eAAe,aAAa,OAAe;EAC/C,WAAW,EAAE;CACf,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,iBAAiB,MAA6C;EAClE,MAAM,UAAU,eAAe,OAAO,QAAQ;EAC9C,IAAI,QAAQ,WAAW,GAAG;EAE1B,MAAM,YAAY,cAAc,QAAQ,EAAE,EAAE;EAC5C,MAAM,aAAa,QAAQ,WAAW,MAAM,EAAE,OAAO,SAAS;EAC9D,MAAM,UAAU,QAAQ;EAExB,MAAM,aAAa,OAAe;GAChC,aAAa,EAAE;GACf,SAAS,eAAe,GAAG,OAAO,GAAG,IAAI,CAAC,EAAE,cAAc,QAAQ,CAAC,EAAE,MAAM;EAC7E;EAEA,QAAQ,EAAE,KAAV;GACE,KAAK,aAAa;IAChB,EAAE,eAAe;IACjB,MAAM,OAAO,QAAQ,aAAa;IAClC,IAAI,QAAQ,CAAC,KAAK,UAAU,UAAU,KAAK,EAAE;IAC7C;GACF;GACA,KAAK,WAAW;IACd,EAAE,eAAe;IACjB,IAAI,OAAO,QAAQ,aAAa;IAChC,OAAO,QAAQ,KAAK,UAElB,OAAO,QADK,QAAQ,QAAQ,IACb,IAAM;IAEvB,IAAI,MAAM,UAAU,KAAK,EAAE;IAC3B;GACF;GACA,KAAK;IACH,EAAE,eAAe;IACjB,IAAI,SAAS,UAAU,UAAU,CAAC,SAAS,IAAI,QAAQ,EAAE,GACvD,aAAa,QAAQ,EAAE;SAClB;KACL,MAAM,QAAQ,SAAS,WAAW;KAClC,IAAI,SAAS,CAAC,MAAM,UAAU,UAAU,MAAM,EAAE;IAClD;IACA;GAEF,KAAK;IACH,EAAE,eAAe;IACjB,IAAI,SAAS,UAAU,UAAU,SAAS,IAAI,QAAQ,EAAE,GACtD,aAAa,QAAQ,EAAE;SAClB;KACL,MAAM,SAAS,WAAW,OAAO,SAAS,MAAM,EAAE;KAClD,IAAI,QAAQ,UAAU,OAAO,EAAE;IACjC;IACA;GAEF,KAAK,QAAQ;IACX,EAAE,eAAe;IACjB,MAAM,QAAQ,QAAQ,MAAM,MAAM,CAAC,EAAE,QAAQ;IAC7C,IAAI,OAAO,UAAU,MAAM,EAAE;IAC7B;GACF;GACA,KAAK,OAAO;IACV,EAAE,eAAe;IACjB,MAAM,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,MAAM,CAAC,EAAE,QAAQ;IAC3D,IAAI,MAAM,UAAU,KAAK,EAAE;IAC3B;GACF;GACA,KAAK;GACL,KAAK;IACH,EAAE,eAAe;IACjB,IAAI,WAAW,CAAC,QAAQ,UAAU;KAChC,IAAI,QAAQ,UAAU,QAAQ,aAAa,QAAQ,EAAE;KACrD,aAAa,QAAQ,EAAE;IACzB;EAGJ;CACF;CAEA,OACE,oBAAC,MAAD;EACE,KAAK;EACL,MAAK;EACL,cAAY;EACZ,WAAW;EACX,WAAW,sCAAsC;EACjD,UAAU;EAET,UAAA,MAAM,KAAK,SACV,oBAAC,UAAD;GAEQ;GACN,OAAO;GACK;GACF;GACI;GACd,UAAU;GACF;EACT,GARM,KAAK,EAQX,CACF;CACC,CAAA;AAER"}
@@ -0,0 +1,42 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ //#region src/ui-library/gallery/kanban/Kanban.tsx
3
+ function Kanban({ columns, className = "" }) {
4
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5
+ className: `w-full flex gap-4 overflow-x-auto pb-2 ${className}`,
6
+ children: columns.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
7
+ "aria-label": `${col.label} column, ${col.cards.length} cards`,
8
+ className: "flex w-64 shrink-0 flex-col rounded-2xl border border-slate-200 bg-slate-50/60 overflow-hidden",
9
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10
+ className: "flex items-center justify-between px-4 py-3",
11
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12
+ className: "flex items-center gap-2",
13
+ children: [col.accentClass && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
14
+ className: `h-2.5 w-2.5 rounded-full ${col.accentClass}`,
15
+ "aria-hidden": "true"
16
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
17
+ className: "text-xs font-bold uppercase tracking-widest text-slate-600",
18
+ children: col.label
19
+ })]
20
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21
+ "aria-label": `${col.cards.length} cards`,
22
+ className: "rounded-full bg-slate-200 px-2 py-0.5 text-[10px] font-bold text-slate-500",
23
+ children: col.cards.length
24
+ })]
25
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
26
+ className: "flex flex-col gap-2 px-3 pb-3",
27
+ "aria-label": `${col.label} cards`,
28
+ children: [col.cards.map((card) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
29
+ className: "rounded-xl border border-slate-200 bg-white p-3 shadow-sm",
30
+ children: card.content
31
+ }) }, card.id)), col.cards.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32
+ className: "flex items-center justify-center rounded-xl border-2 border-dashed border-slate-200 p-4 text-xs font-medium text-slate-400",
33
+ children: "No cards"
34
+ }) })]
35
+ })]
36
+ }, col.id))
37
+ });
38
+ }
39
+ //#endregion
40
+ exports.Kanban = Kanban;
41
+
42
+ //# sourceMappingURL=index63.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index63.cjs","names":[],"sources":["../src/ui-library/gallery/kanban/Kanban.tsx"],"sourcesContent":["/**\n * Kanban — Design System Component\n *\n * A static column-based board for visualizing workflow states.\n * Cards are rendered by the caller; this component provides the\n * column layout and card slot structure.\n *\n * No drag-and-drop: this is a gallery demonstration component.\n * For drag-and-drop in production, add @dnd-kit or similar.\n *\n * Accessibility:\n * - Each column is a <section> with aria-label\n * - Card count announced via aria-label on the count badge\n * - Card slots render as <li> items in a <ul>\n */\n\nimport type { ReactNode } from 'react';\n\nexport interface KanbanColumn {\n id: string;\n label: string;\n /** Color class for the column header accent. E.g. \"bg-amber-400\" */\n accentClass?: string;\n cards: KanbanCard[];\n}\n\nexport interface KanbanCard {\n id: string;\n content: ReactNode;\n}\n\nexport interface KanbanProps {\n columns: KanbanColumn[];\n /** Additional class on the root element. */\n className?: string;\n}\n\nexport function Kanban({ columns, className = '' }: KanbanProps) {\n return (\n <div className={`w-full flex gap-4 overflow-x-auto pb-2 ${className}`}>\n {columns.map((col) => (\n <section\n key={col.id}\n aria-label={`${col.label} column, ${col.cards.length} cards`}\n className=\"flex w-64 shrink-0 flex-col rounded-2xl border border-slate-200 bg-slate-50/60 overflow-hidden\"\n >\n {/* Column header */}\n <div className=\"flex items-center justify-between px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n {col.accentClass && (\n <span className={`h-2.5 w-2.5 rounded-full ${col.accentClass}`} aria-hidden=\"true\" />\n )}\n <h3 className=\"text-xs font-bold uppercase tracking-widest text-slate-600\">\n {col.label}\n </h3>\n </div>\n <span\n aria-label={`${col.cards.length} cards`}\n className=\"rounded-full bg-slate-200 px-2 py-0.5 text-[10px] font-bold text-slate-500\"\n >\n {col.cards.length}\n </span>\n </div>\n\n {/* Cards */}\n <ul className=\"flex flex-col gap-2 px-3 pb-3\" aria-label={`${col.label} cards`}>\n {col.cards.map((card) => (\n <li key={card.id}>\n <div className=\"rounded-xl border border-slate-200 bg-white p-3 shadow-sm\">\n {card.content}\n </div>\n </li>\n ))}\n {col.cards.length === 0 && (\n <li>\n <div className=\"flex items-center justify-center rounded-xl border-2 border-dashed border-slate-200 p-4 text-xs font-medium text-slate-400\">\n No cards\n </div>\n </li>\n )}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":";;AAqCA,SAAgB,OAAO,EAAE,SAAS,YAAY,MAAmB;CAC/D,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,WAAW,0CAA0C;EACvD,UAAA,QAAQ,KAAK,QACZ,iBAAA,GAAA,kBAAA,KAAA,CAAC,WAAD;GAEE,cAAY,GAAG,IAAI,MAAM,WAAW,IAAI,MAAM,OAAO;GACrD,WAAU;GAHZ,UAAA,CAME,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,IAAI,eACH,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAW,4BAA4B,IAAI;MAAe,eAAY;KAAQ,CAAA,GAEtF,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;MAAI,WAAU;MACX,UAAA,IAAI;KACH,CAAA,CACD;IACL,CAAA,GAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;KACE,cAAY,GAAG,IAAI,MAAM,OAAO;KAChC,WAAU;KAET,UAAA,IAAI,MAAM;IACP,CAAA,CACH;GAGL,CAAA,GAAA,iBAAA,GAAA,kBAAA,KAAA,CAAC,MAAD;IAAI,WAAU;IAAgC,cAAY,GAAG,IAAI,MAAM;IAAvE,UAAA,CACG,IAAI,MAAM,KAAK,SACd,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;KAAK,WAAU;KACZ,UAAA,KAAK;IACH,CAAA,EACH,GAJK,KAAK,EAIV,CACL,GACA,IAAI,MAAM,WAAW,KACpB,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;KAAK,WAAU;KAA6H,UAAA;IAEvI,CAAA,EACH,CAAA,CAEJ;GACG,CAAA,CAAA;EAvCF,GAAA,IAAI,EAuCF,CACV;CACE,CAAA;AAET"}
@@ -0,0 +1,42 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ //#region src/ui-library/gallery/kanban/Kanban.tsx
3
+ function Kanban({ columns, className = "" }) {
4
+ return /* @__PURE__ */ jsx("div", {
5
+ className: `w-full flex gap-4 overflow-x-auto pb-2 ${className}`,
6
+ children: columns.map((col) => /* @__PURE__ */ jsxs("section", {
7
+ "aria-label": `${col.label} column, ${col.cards.length} cards`,
8
+ className: "flex w-64 shrink-0 flex-col rounded-2xl border border-slate-200 bg-slate-50/60 overflow-hidden",
9
+ children: [/* @__PURE__ */ jsxs("div", {
10
+ className: "flex items-center justify-between px-4 py-3",
11
+ children: [/* @__PURE__ */ jsxs("div", {
12
+ className: "flex items-center gap-2",
13
+ children: [col.accentClass && /* @__PURE__ */ jsx("span", {
14
+ className: `h-2.5 w-2.5 rounded-full ${col.accentClass}`,
15
+ "aria-hidden": "true"
16
+ }), /* @__PURE__ */ jsx("h3", {
17
+ className: "text-xs font-bold uppercase tracking-widest text-slate-600",
18
+ children: col.label
19
+ })]
20
+ }), /* @__PURE__ */ jsx("span", {
21
+ "aria-label": `${col.cards.length} cards`,
22
+ className: "rounded-full bg-slate-200 px-2 py-0.5 text-[10px] font-bold text-slate-500",
23
+ children: col.cards.length
24
+ })]
25
+ }), /* @__PURE__ */ jsxs("ul", {
26
+ className: "flex flex-col gap-2 px-3 pb-3",
27
+ "aria-label": `${col.label} cards`,
28
+ children: [col.cards.map((card) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("div", {
29
+ className: "rounded-xl border border-slate-200 bg-white p-3 shadow-sm",
30
+ children: card.content
31
+ }) }, card.id)), col.cards.length === 0 && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("div", {
32
+ className: "flex items-center justify-center rounded-xl border-2 border-dashed border-slate-200 p-4 text-xs font-medium text-slate-400",
33
+ children: "No cards"
34
+ }) })]
35
+ })]
36
+ }, col.id))
37
+ });
38
+ }
39
+ //#endregion
40
+ export { Kanban };
41
+
42
+ //# sourceMappingURL=index63.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index63.mjs","names":[],"sources":["../src/ui-library/gallery/kanban/Kanban.tsx"],"sourcesContent":["/**\n * Kanban — Design System Component\n *\n * A static column-based board for visualizing workflow states.\n * Cards are rendered by the caller; this component provides the\n * column layout and card slot structure.\n *\n * No drag-and-drop: this is a gallery demonstration component.\n * For drag-and-drop in production, add @dnd-kit or similar.\n *\n * Accessibility:\n * - Each column is a <section> with aria-label\n * - Card count announced via aria-label on the count badge\n * - Card slots render as <li> items in a <ul>\n */\n\nimport type { ReactNode } from 'react';\n\nexport interface KanbanColumn {\n id: string;\n label: string;\n /** Color class for the column header accent. E.g. \"bg-amber-400\" */\n accentClass?: string;\n cards: KanbanCard[];\n}\n\nexport interface KanbanCard {\n id: string;\n content: ReactNode;\n}\n\nexport interface KanbanProps {\n columns: KanbanColumn[];\n /** Additional class on the root element. */\n className?: string;\n}\n\nexport function Kanban({ columns, className = '' }: KanbanProps) {\n return (\n <div className={`w-full flex gap-4 overflow-x-auto pb-2 ${className}`}>\n {columns.map((col) => (\n <section\n key={col.id}\n aria-label={`${col.label} column, ${col.cards.length} cards`}\n className=\"flex w-64 shrink-0 flex-col rounded-2xl border border-slate-200 bg-slate-50/60 overflow-hidden\"\n >\n {/* Column header */}\n <div className=\"flex items-center justify-between px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n {col.accentClass && (\n <span className={`h-2.5 w-2.5 rounded-full ${col.accentClass}`} aria-hidden=\"true\" />\n )}\n <h3 className=\"text-xs font-bold uppercase tracking-widest text-slate-600\">\n {col.label}\n </h3>\n </div>\n <span\n aria-label={`${col.cards.length} cards`}\n className=\"rounded-full bg-slate-200 px-2 py-0.5 text-[10px] font-bold text-slate-500\"\n >\n {col.cards.length}\n </span>\n </div>\n\n {/* Cards */}\n <ul className=\"flex flex-col gap-2 px-3 pb-3\" aria-label={`${col.label} cards`}>\n {col.cards.map((card) => (\n <li key={card.id}>\n <div className=\"rounded-xl border border-slate-200 bg-white p-3 shadow-sm\">\n {card.content}\n </div>\n </li>\n ))}\n {col.cards.length === 0 && (\n <li>\n <div className=\"flex items-center justify-center rounded-xl border-2 border-dashed border-slate-200 p-4 text-xs font-medium text-slate-400\">\n No cards\n </div>\n </li>\n )}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":";;AAqCA,SAAgB,OAAO,EAAE,SAAS,YAAY,MAAmB;CAC/D,OACE,oBAAC,OAAD;EAAK,WAAW,0CAA0C;EACvD,UAAA,QAAQ,KAAK,QACZ,qBAAC,WAAD;GAEE,cAAY,GAAG,IAAI,MAAM,WAAW,IAAI,MAAM,OAAO;GACrD,WAAU;GAHZ,UAAA,CAME,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,IAAI,eACH,oBAAC,QAAD;MAAM,WAAW,4BAA4B,IAAI;MAAe,eAAY;KAAQ,CAAA,GAEtF,oBAAC,MAAD;MAAI,WAAU;MACX,UAAA,IAAI;KACH,CAAA,CACD;IACL,CAAA,GAAA,oBAAC,QAAD;KACE,cAAY,GAAG,IAAI,MAAM,OAAO;KAChC,WAAU;KAET,UAAA,IAAI,MAAM;IACP,CAAA,CACH;GAGL,CAAA,GAAA,qBAAC,MAAD;IAAI,WAAU;IAAgC,cAAY,GAAG,IAAI,MAAM;IAAvE,UAAA,CACG,IAAI,MAAM,KAAK,SACd,oBAAC,MAAD,EAAA,UACE,oBAAC,OAAD;KAAK,WAAU;KACZ,UAAA,KAAK;IACH,CAAA,EACH,GAJK,KAAK,EAIV,CACL,GACA,IAAI,MAAM,WAAW,KACpB,oBAAC,MAAD,EAAA,UACE,oBAAC,OAAD;KAAK,WAAU;KAA6H,UAAA;IAEvI,CAAA,EACH,CAAA,CAEJ;GACG,CAAA,CAAA;EAvCF,GAAA,IAAI,EAuCF,CACV;CACE,CAAA;AAET"}