@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,100 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Check } from "lucide-react";
3
+ //#region src/ui-library/gallery/stepper/Stepper.tsx
4
+ function resolveState(id, currentStep, completedSteps, disabledSteps) {
5
+ if (disabledSteps.includes(id)) return "disabled";
6
+ if (completedSteps.includes(id)) return "completed";
7
+ if (id === currentStep) return "current";
8
+ return "upcoming";
9
+ }
10
+ function StepCircle({ state, number, icon }) {
11
+ return /* @__PURE__ */ jsx("div", {
12
+ className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold transition-all ${{
13
+ completed: "border-brand-blue bg-brand-blue text-white",
14
+ current: "border-brand-blue bg-white text-brand-blue shadow-md shadow-brand-blue/15",
15
+ upcoming: "border-slate-300 bg-white text-slate-400",
16
+ disabled: "border-slate-200 bg-slate-50 text-slate-300"
17
+ }[state]}`,
18
+ "aria-hidden": "true",
19
+ children: state === "completed" ? icon ?? /* @__PURE__ */ jsx(Check, {
20
+ size: 16,
21
+ strokeWidth: 3
22
+ }) : icon ?? /* @__PURE__ */ jsx("span", { children: number })
23
+ });
24
+ }
25
+ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [], orientation = "horizontal", className = "" }) {
26
+ const isHorizontal = orientation === "horizontal";
27
+ return /* @__PURE__ */ jsx("div", {
28
+ "aria-label": "Progress",
29
+ "aria-orientation": orientation,
30
+ className: [isHorizontal ? "flex items-start" : "flex flex-col", className].filter(Boolean).join(" "),
31
+ children: /* @__PURE__ */ jsx("ol", {
32
+ className: [isHorizontal ? "flex w-full items-start" : "flex flex-col"].filter(Boolean).join(" "),
33
+ children: steps.map((step, idx) => {
34
+ const state = resolveState(step.id, currentStep, completedSteps, disabledSteps);
35
+ const isCurrent = state === "current";
36
+ const isLast = idx === steps.length - 1;
37
+ const labelColor = {
38
+ completed: "text-brand-blue",
39
+ current: "text-brand-navy font-bold",
40
+ upcoming: "text-slate-500",
41
+ disabled: "text-slate-300"
42
+ };
43
+ return /* @__PURE__ */ jsxs("li", {
44
+ "aria-current": isCurrent ? "step" : void 0,
45
+ className: isHorizontal ? "flex flex-1 items-start" : "flex items-start gap-4 pb-8 last:pb-0",
46
+ children: [
47
+ !isHorizontal && /* @__PURE__ */ jsxs("div", {
48
+ className: "relative flex flex-col items-center",
49
+ children: [/* @__PURE__ */ jsx(StepCircle, {
50
+ state,
51
+ number: idx + 1,
52
+ icon: step.icon
53
+ }), !isLast && /* @__PURE__ */ jsx("div", {
54
+ className: `mt-1 w-0.5 flex-1 min-h-[32px] ${state === "completed" ? "bg-brand-blue/50" : "bg-slate-200"}`,
55
+ "aria-hidden": "true"
56
+ })]
57
+ }),
58
+ isHorizontal && /* @__PURE__ */ jsxs("div", {
59
+ className: "flex flex-1 flex-col items-center",
60
+ children: [/* @__PURE__ */ jsxs("div", {
61
+ className: "flex w-full items-center",
62
+ children: [/* @__PURE__ */ jsx(StepCircle, {
63
+ state,
64
+ number: idx + 1,
65
+ icon: step.icon
66
+ }), !isLast && /* @__PURE__ */ jsx("div", {
67
+ className: `h-0.5 flex-1 ${state === "completed" ? "bg-brand-blue/50" : "bg-slate-200"}`,
68
+ "aria-hidden": "true"
69
+ })]
70
+ }), /* @__PURE__ */ jsxs("div", {
71
+ className: "mt-2 text-center px-1",
72
+ children: [/* @__PURE__ */ jsx("p", {
73
+ className: `text-sm leading-tight ${labelColor[state]}`,
74
+ children: step.label
75
+ }), step.description && /* @__PURE__ */ jsx("p", {
76
+ className: "mt-0.5 text-xs text-slate-400 leading-tight",
77
+ children: step.description
78
+ })]
79
+ })]
80
+ }),
81
+ !isHorizontal && /* @__PURE__ */ jsxs("div", {
82
+ className: "min-w-0 flex-1 pt-1",
83
+ children: [/* @__PURE__ */ jsx("p", {
84
+ className: `text-sm leading-tight ${labelColor[state]}`,
85
+ children: step.label
86
+ }), step.description && /* @__PURE__ */ jsx("p", {
87
+ className: "mt-0.5 text-xs text-slate-400 leading-tight",
88
+ children: step.description
89
+ })]
90
+ })
91
+ ]
92
+ }, step.id);
93
+ })
94
+ })
95
+ });
96
+ }
97
+ //#endregion
98
+ export { Stepper };
99
+
100
+ //# sourceMappingURL=index50.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index50.mjs","names":[],"sources":["../src/ui-library/gallery/stepper/Stepper.tsx"],"sourcesContent":["/**\n * Stepper — Design System Component\n *\n * A step progress indicator for multi-step workflows.\n *\n * Step states: completed | current | upcoming | disabled\n *\n * Accessibility:\n * - The current step receives aria-current=\"step\".\n * - Completed steps have aria-label with a \"Completed\" suffix when no icon.\n * - The step list is an ordered list (<ol>) — each step is an <li>.\n * - Orientation is exposed via aria-orientation on the container.\n *\n * API:\n * <Stepper\n * steps={[\n * { id: '1', label: 'Personal Details', description: 'Name and contact' },\n * { id: '2', label: 'Employment', description: 'IDs and dates' },\n * ]}\n * currentStep=\"2\"\n * completedSteps={['1']}\n * />\n */\n\nimport type { ReactNode } from 'react';\nimport { Check } from 'lucide-react';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type StepState = 'completed' | 'current' | 'upcoming' | 'disabled';\n\nexport interface StepDef {\n id: string;\n label: string;\n description?: string;\n icon?: ReactNode;\n}\n\nexport interface StepperProps {\n steps: StepDef[];\n /** The id of the currently active step */\n currentStep: string;\n /** IDs of steps that are completed */\n completedSteps?: string[];\n /** IDs of steps that are disabled */\n disabledSteps?: string[];\n orientation?: 'horizontal' | 'vertical';\n className?: string;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction resolveState(\n id: string,\n currentStep: string,\n completedSteps: string[],\n disabledSteps: string[],\n): StepState {\n if (disabledSteps.includes(id)) return 'disabled';\n if (completedSteps.includes(id)) return 'completed';\n if (id === currentStep) return 'current';\n return 'upcoming';\n}\n\n// ── Step indicator circle ─────────────────────────────────────────────────────\n\nfunction StepCircle({ state, number, icon }: { state: StepState; number: number; icon?: ReactNode }) {\n const base = 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold transition-all';\n\n const styles: Record<StepState, string> = {\n completed: 'border-brand-blue bg-brand-blue text-white',\n current: 'border-brand-blue bg-white text-brand-blue shadow-md shadow-brand-blue/15',\n upcoming: 'border-slate-300 bg-white text-slate-400',\n disabled: 'border-slate-200 bg-slate-50 text-slate-300',\n };\n\n return (\n <div className={`${base} ${styles[state]}`} aria-hidden=\"true\">\n {state === 'completed' ? (\n icon ?? <Check size={16} strokeWidth={3} />\n ) : (\n icon ?? <span>{number}</span>\n )}\n </div>\n );\n}\n\n// ── Stepper ───────────────────────────────────────────────────────────────────\n\nexport function Stepper({\n steps,\n currentStep,\n completedSteps = [],\n disabledSteps = [],\n orientation = 'horizontal',\n className = '',\n}: StepperProps) {\n const isHorizontal = orientation === 'horizontal';\n\n return (\n <div\n aria-label=\"Progress\"\n aria-orientation={orientation}\n className={[\n isHorizontal\n ? 'flex items-start'\n : 'flex flex-col',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <ol\n className={[\n isHorizontal ? 'flex w-full items-start' : 'flex flex-col',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {steps.map((step, idx) => {\n const state = resolveState(step.id, currentStep, completedSteps, disabledSteps);\n const isCurrent = state === 'current';\n const isLast = idx === steps.length - 1;\n\n const labelColor: Record<StepState, string> = {\n completed: 'text-brand-blue',\n current: 'text-brand-navy font-bold',\n upcoming: 'text-slate-500',\n disabled: 'text-slate-300',\n };\n\n return (\n <li\n key={step.id}\n aria-current={isCurrent ? 'step' : undefined}\n className={isHorizontal ? 'flex flex-1 items-start' : 'flex items-start gap-4 pb-8 last:pb-0'}\n >\n {/* Vertical connector + circle column */}\n {!isHorizontal && (\n <div className=\"relative flex flex-col items-center\">\n <StepCircle state={state} number={idx + 1} icon={step.icon} />\n {!isLast && (\n <div\n className={`mt-1 w-0.5 flex-1 min-h-[32px] ${\n state === 'completed' ? 'bg-brand-blue/50' : 'bg-slate-200'\n }`}\n aria-hidden=\"true\"\n />\n )}\n </div>\n )}\n\n {/* Horizontal: circle + connector */}\n {isHorizontal && (\n <div className=\"flex flex-1 flex-col items-center\">\n <div className=\"flex w-full items-center\">\n <StepCircle state={state} number={idx + 1} icon={step.icon} />\n {!isLast && (\n <div\n className={`h-0.5 flex-1 ${\n state === 'completed' ? 'bg-brand-blue/50' : 'bg-slate-200'\n }`}\n aria-hidden=\"true\"\n />\n )}\n </div>\n {/* Label below circle */}\n <div className=\"mt-2 text-center px-1\">\n <p className={`text-sm leading-tight ${labelColor[state]}`}>{step.label}</p>\n {step.description && (\n <p className=\"mt-0.5 text-xs text-slate-400 leading-tight\">{step.description}</p>\n )}\n </div>\n </div>\n )}\n\n {/* Vertical: label to the right */}\n {!isHorizontal && (\n <div className=\"min-w-0 flex-1 pt-1\">\n <p className={`text-sm leading-tight ${labelColor[state]}`}>{step.label}</p>\n {step.description && (\n <p className=\"mt-0.5 text-xs text-slate-400 leading-tight\">{step.description}</p>\n )}\n </div>\n )}\n </li>\n );\n })}\n </ol>\n </div>\n );\n}\n"],"mappings":";;;AAoDA,SAAS,aACP,IACA,aACA,gBACA,eACW;CACX,IAAI,cAAc,SAAS,EAAE,GAAG,OAAO;CACvC,IAAI,eAAe,SAAS,EAAE,GAAG,OAAO;CACxC,IAAI,OAAO,aAAa,OAAO;CAC/B,OAAO;AACT;AAIA,SAAS,WAAW,EAAE,OAAO,QAAQ,QAAgE;CAUnG,OACE,oBAAC,OAAD;EAAK,WAAW,4GAAW;GAP3B,WAAW;GACX,SAAW;GACX,UAAW;GACX,UAAW;EAIgB,EAAO;EAAU,eAAY;EACrD,UAAA,UAAU,cACT,QAAQ,oBAAC,OAAD;GAAO,MAAM;GAAI,aAAa;EAAI,CAAA,IAE1C,QAAQ,oBAAC,QAAD,EAAA,UAAO,OAAa,CAAA;CAE3B,CAAA;AAET;AAIA,SAAgB,QAAQ,EACtB,OACA,aACA,iBAAiB,CAAC,GAClB,gBAAiB,CAAC,GAClB,cAAc,cACd,YAAY,MACG;CACf,MAAM,eAAe,gBAAgB;CAErC,OACE,oBAAC,OAAD;EACE,cAAW;EACX,oBAAkB;EAClB,WAAW,CACT,eACI,qBACA,iBACJ,SACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EAEX,UAAA,oBAAC,MAAD;GACE,WAAW,CACT,eAAe,4BAA4B,eAC7C,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV,UAAA,MAAM,KAAK,MAAM,QAAQ;IACxB,MAAM,QAAQ,aAAa,KAAK,IAAI,aAAa,gBAAgB,aAAa;IAC9E,MAAM,YAAY,UAAU;IAC5B,MAAM,SAAS,QAAQ,MAAM,SAAS;IAEtC,MAAM,aAAwC;KAC5C,WAAW;KACX,SAAW;KACX,UAAW;KACX,UAAW;IACb;IAEA,OACE,qBAAC,MAAD;KAEE,gBAAc,YAAY,SAAS,KAAA;KACnC,WAAW,eAAe,4BAA4B;KAHxD,UAAA;MAMG,CAAC,gBACA,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,YAAD;QAAmB;QAAO,QAAQ,MAAM;QAAG,MAAM,KAAK;OAAO,CAAA,GAC5D,CAAC,UACA,oBAAC,OAAD;QACE,WAAW,kCACT,UAAU,cAAc,qBAAqB;QAE/C,eAAY;OACb,CAAA,CAEA;;MAIN,gBACC,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,qBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,oBAAC,YAAD;SAAmB;SAAO,QAAQ,MAAM;SAAG,MAAM,KAAK;QAAO,CAAA,GAC5D,CAAC,UACA,oBAAC,OAAD;SACE,WAAW,gBACT,UAAU,cAAc,qBAAqB;SAE/C,eAAY;QACb,CAAA,CAEA;OAEL,CAAA,GAAA,qBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,oBAAC,KAAD;SAAG,WAAW,yBAAyB,WAAW;SAAW,UAAA,KAAK;QAAS,CAAA,GAC1E,KAAK,eACJ,oBAAC,KAAD;SAAG,WAAU;SAA+C,UAAA,KAAK;QAAe,CAAA,CAE/E;OACF,CAAA,CAAA;;MAIN,CAAC,gBACA,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,KAAD;QAAG,WAAW,yBAAyB,WAAW;QAAW,UAAA,KAAK;OAAS,CAAA,GAC1E,KAAK,eACJ,oBAAC,KAAD;QAAG,WAAU;QAA+C,UAAA,KAAK;OAAe,CAAA,CAE/E;;KAEL;IApDG,GAAA,KAAK,EAoDR;GAER,CAAC;EACC,CAAA;CACD,CAAA;AAET"}
@@ -0,0 +1,116 @@
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/sidebar/Sidebar.tsx
5
+ /**
6
+ * Sidebar — Design System Component
7
+ *
8
+ * A vertical navigation panel. Supports grouped navigation sections,
9
+ * collapsible groups, active item state, icons, and badges.
10
+ *
11
+ * Accessibility:
12
+ * - Renders a <nav> landmark with aria-label.
13
+ * - Active item receives aria-current="page".
14
+ * - Collapsible group buttons have aria-expanded.
15
+ * - Collapsed content is hidden with aria-hidden + display:none equivalent.
16
+ *
17
+ * Composition:
18
+ * <SidebarNav aria-label="Main navigation">
19
+ * <SidebarSection label="Workspace">
20
+ * <SidebarItem href="/dashboard" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>
21
+ * <SidebarItem href="/employees" icon={<Users />}>Employees</SidebarItem>
22
+ * <SidebarItem href="/reports" icon={<BarChart2 />} badge="3">Reports</SidebarItem>
23
+ * </SidebarSection>
24
+ * <SidebarGroup label="Settings" collapsible>
25
+ * <SidebarItem href="/settings/profile">Profile</SidebarItem>
26
+ * </SidebarGroup>
27
+ * </SidebarNav>
28
+ */
29
+ function SidebarNav({ "aria-label": ariaLabel, children, className = "" }) {
30
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
31
+ "aria-label": ariaLabel,
32
+ className: `flex w-full flex-col gap-1 ${className}`,
33
+ children
34
+ });
35
+ }
36
+ function SidebarSection({ label, children, className = "" }) {
37
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
38
+ className: `mb-3 ${className}`,
39
+ children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
40
+ className: "mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400",
41
+ children: label
42
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
43
+ className: "flex flex-col gap-0.5",
44
+ role: "list",
45
+ children
46
+ })]
47
+ });
48
+ }
49
+ function SidebarGroup({ label, defaultExpanded = true, children, className = "" }) {
50
+ const [expanded, setExpanded] = (0, react.useState)(defaultExpanded);
51
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
52
+ className: `mb-2 ${className}`,
53
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
54
+ type: "button",
55
+ "aria-expanded": expanded,
56
+ onClick: () => setExpanded(!expanded),
57
+ className: "flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest 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",
58
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
59
+ size: 12,
60
+ "aria-hidden": "true",
61
+ className: `transition-transform duration-200 ${expanded ? "" : "-rotate-90"}`
62
+ })]
63
+ }), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
64
+ className: "flex flex-col gap-0.5 mt-0.5",
65
+ role: "list",
66
+ children
67
+ })]
68
+ });
69
+ }
70
+ function SidebarItem({ href, onClick, active = false, icon, badge, disabled = false, children, className = "" }) {
71
+ const baseClass = [
72
+ "flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition",
73
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
74
+ active ? "bg-blue-50 text-brand-blue" : disabled ? "cursor-not-allowed text-slate-300" : "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
75
+ className
76
+ ].filter(Boolean).join(" ");
77
+ const content = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
78
+ icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
79
+ className: `shrink-0 ${active ? "text-brand-blue" : "text-slate-400"}`,
80
+ "aria-hidden": "true",
81
+ children: icon
82
+ }),
83
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
84
+ className: "flex-1 truncate",
85
+ children
86
+ }),
87
+ badge !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
88
+ className: `ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${active ? "bg-blue-50 text-brand-blue" : "bg-slate-100 text-slate-500"}`,
89
+ "aria-label": `${badge} items`,
90
+ children: badge
91
+ })
92
+ ] });
93
+ if (href && !disabled) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
94
+ href,
95
+ "aria-current": active ? "page" : void 0,
96
+ className: baseClass,
97
+ onClick,
98
+ children: content
99
+ }) });
100
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
101
+ type: "button",
102
+ "aria-current": active ? "page" : void 0,
103
+ "aria-disabled": disabled || void 0,
104
+ disabled,
105
+ onClick: !disabled ? onClick : void 0,
106
+ className: baseClass,
107
+ children: content
108
+ }) });
109
+ }
110
+ //#endregion
111
+ exports.SidebarGroup = SidebarGroup;
112
+ exports.SidebarItem = SidebarItem;
113
+ exports.SidebarNav = SidebarNav;
114
+ exports.SidebarSection = SidebarSection;
115
+
116
+ //# sourceMappingURL=index51.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index51.cjs","names":[],"sources":["../src/ui-library/gallery/sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * Sidebar — Design System Component\n *\n * A vertical navigation panel. Supports grouped navigation sections,\n * collapsible groups, active item state, icons, and badges.\n *\n * Accessibility:\n * - Renders a <nav> landmark with aria-label.\n * - Active item receives aria-current=\"page\".\n * - Collapsible group buttons have aria-expanded.\n * - Collapsed content is hidden with aria-hidden + display:none equivalent.\n *\n * Composition:\n * <SidebarNav aria-label=\"Main navigation\">\n * <SidebarSection label=\"Workspace\">\n * <SidebarItem href=\"/dashboard\" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>\n * <SidebarItem href=\"/employees\" icon={<Users />}>Employees</SidebarItem>\n * <SidebarItem href=\"/reports\" icon={<BarChart2 />} badge=\"3\">Reports</SidebarItem>\n * </SidebarSection>\n * <SidebarGroup label=\"Settings\" collapsible>\n * <SidebarItem href=\"/settings/profile\">Profile</SidebarItem>\n * </SidebarGroup>\n * </SidebarNav>\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { ChevronDown } from 'lucide-react';\n\n// ── SidebarNav ────────────────────────────────────────────────────────────────\n\nexport interface SidebarNavProps {\n 'aria-label': string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarNav({ 'aria-label': ariaLabel, children, className = '' }: SidebarNavProps) {\n return (\n <nav\n aria-label={ariaLabel}\n className={`flex w-full flex-col gap-1 ${className}`}\n >\n {children}\n </nav>\n );\n}\n\n// ── SidebarSection ────────────────────────────────────────────────────────────\n// A non-collapsible labeled group.\n\nexport interface SidebarSectionProps {\n label?: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarSection({ label, children, className = '' }: SidebarSectionProps) {\n return (\n <div className={`mb-3 ${className}`}>\n {label && (\n <p className=\"mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400\">\n {label}\n </p>\n )}\n <ul className=\"flex flex-col gap-0.5\" role=\"list\">\n {children}\n </ul>\n </div>\n );\n}\n\n// ── SidebarGroup ──────────────────────────────────────────────────────────────\n// A collapsible labeled group.\n\nexport interface SidebarGroupProps {\n label: string;\n defaultExpanded?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarGroup({ label, defaultExpanded = true, children, className = '' }: SidebarGroupProps) {\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n return (\n <div className={`mb-2 ${className}`}>\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded(!expanded)}\n className=\"flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest 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 <span>{label}</span>\n <ChevronDown\n size={12}\n aria-hidden=\"true\"\n className={`transition-transform duration-200 ${expanded ? '' : '-rotate-90'}`}\n />\n </button>\n {expanded && (\n <ul className=\"flex flex-col gap-0.5 mt-0.5\" role=\"list\">\n {children}\n </ul>\n )}\n </div>\n );\n}\n\n// ── SidebarItem ───────────────────────────────────────────────────────────────\n\nexport interface SidebarItemProps {\n href?: string;\n onClick?: () => void;\n active?: boolean;\n icon?: ReactNode;\n badge?: string | number;\n disabled?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarItem({\n href,\n onClick,\n active = false,\n icon,\n badge,\n disabled = false,\n children,\n className = '',\n}: SidebarItemProps) {\n const baseClass = [\n 'flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n active\n ? 'bg-blue-50 text-brand-blue'\n : disabled\n ? 'cursor-not-allowed text-slate-300'\n : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const content = (\n <>\n {icon && (\n <span className={`shrink-0 ${active ? 'text-brand-blue' : 'text-slate-400'}`} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <span className=\"flex-1 truncate\">{children}</span>\n {badge !== undefined && (\n <span\n className={`ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${\n active ? 'bg-blue-50 text-brand-blue' : 'bg-slate-100 text-slate-500'\n }`}\n aria-label={`${badge} items`}\n >\n {badge}\n </span>\n )}\n </>\n );\n\n if (href && !disabled) {\n return (\n <li>\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className={baseClass}\n onClick={onClick}\n >\n {content}\n </a>\n </li>\n );\n }\n\n return (\n <li>\n <button\n type=\"button\"\n aria-current={active ? 'page' : undefined}\n aria-disabled={disabled || undefined}\n disabled={disabled}\n onClick={!disabled ? onClick : undefined}\n className={baseClass}\n >\n {content}\n </button>\n </li>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,WAAW,EAAE,cAAc,WAAW,UAAU,YAAY,MAAuB;CACjG,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EACE,cAAY;EACZ,WAAW,8BAA8B;EAExC;CACE,CAAA;AAET;AAWA,SAAgB,eAAe,EAAE,OAAO,UAAU,YAAY,MAA2B;CACvF,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACG,SACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;GAAG,WAAU;GACV,UAAA;EACA,CAAA,GAEL,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;GAAI,WAAU;GAAwB,MAAK;GACxC;EACC,CAAA,CACD;;AAET;AAYA,SAAgB,aAAa,EAAE,OAAO,kBAAkB,MAAM,UAAU,YAAY,MAAyB;CAC3G,MAAM,CAAC,UAAU,gBAAA,GAAe,MAAA,SAAA,CAAS,eAAe;CAExD,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,UAAD;GACE,MAAK;GACL,iBAAe;GACf,eAAe,YAAY,CAAC,QAAQ;GACpC,WAAU;GAJZ,UAAA,CAME,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD,EAAA,UAAO,MAAY,CAAA,GACnB,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,aAAD;IACE,MAAM;IACN,eAAY;IACZ,WAAW,qCAAqC,WAAW,KAAK;GACjE,CAAA,CACK;EACP,CAAA,GAAA,YACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;GAAI,WAAU;GAA+B,MAAK;GAC/C;EACC,CAAA,CAEH;;AAET;AAeA,SAAgB,YAAY,EAC1B,MACA,SACA,SAAS,OACT,MACA,OACA,WAAW,OACX,UACA,YAAY,MACO;CACnB,MAAM,YAAY;EAChB;EACA;EACA,SACI,+BACA,WACE,sCACA;EACN;CACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;CAEX,MAAM,UACJ,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACG,QACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,WAAW,YAAY,SAAS,oBAAoB;GAAoB,eAAY;GACvF,UAAA;EACG,CAAA;EAER,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,WAAU;GAAmB;EAAe,CAAA;EACjD,UAAU,KAAA,KACT,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GACE,WAAW,qEACT,SAAS,+BAA+B;GAE1C,cAAY,GAAG,MAAM;GAEpB,UAAA;EACG,CAAA;CAER,EAAA,CAAA;CAGJ,IAAI,QAAQ,CAAC,UACX,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;EACQ;EACN,gBAAc,SAAS,SAAS,KAAA;EAChC,WAAW;EACF;EAER,UAAA;CACA,CAAA,EACD,CAAA;CAIR,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;EACE,MAAK;EACL,gBAAc,SAAS,SAAS,KAAA;EAChC,iBAAe,YAAY,KAAA;EACjB;EACV,SAAS,CAAC,WAAW,UAAU,KAAA;EAC/B,WAAW;EAEV,UAAA;CACK,CAAA,EACN,CAAA;AAER"}
@@ -0,0 +1,113 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ChevronDown } from "lucide-react";
4
+ //#region src/ui-library/gallery/sidebar/Sidebar.tsx
5
+ /**
6
+ * Sidebar — Design System Component
7
+ *
8
+ * A vertical navigation panel. Supports grouped navigation sections,
9
+ * collapsible groups, active item state, icons, and badges.
10
+ *
11
+ * Accessibility:
12
+ * - Renders a <nav> landmark with aria-label.
13
+ * - Active item receives aria-current="page".
14
+ * - Collapsible group buttons have aria-expanded.
15
+ * - Collapsed content is hidden with aria-hidden + display:none equivalent.
16
+ *
17
+ * Composition:
18
+ * <SidebarNav aria-label="Main navigation">
19
+ * <SidebarSection label="Workspace">
20
+ * <SidebarItem href="/dashboard" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>
21
+ * <SidebarItem href="/employees" icon={<Users />}>Employees</SidebarItem>
22
+ * <SidebarItem href="/reports" icon={<BarChart2 />} badge="3">Reports</SidebarItem>
23
+ * </SidebarSection>
24
+ * <SidebarGroup label="Settings" collapsible>
25
+ * <SidebarItem href="/settings/profile">Profile</SidebarItem>
26
+ * </SidebarGroup>
27
+ * </SidebarNav>
28
+ */
29
+ function SidebarNav({ "aria-label": ariaLabel, children, className = "" }) {
30
+ return /* @__PURE__ */ jsx("nav", {
31
+ "aria-label": ariaLabel,
32
+ className: `flex w-full flex-col gap-1 ${className}`,
33
+ children
34
+ });
35
+ }
36
+ function SidebarSection({ label, children, className = "" }) {
37
+ return /* @__PURE__ */ jsxs("div", {
38
+ className: `mb-3 ${className}`,
39
+ children: [label && /* @__PURE__ */ jsx("p", {
40
+ className: "mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400",
41
+ children: label
42
+ }), /* @__PURE__ */ jsx("ul", {
43
+ className: "flex flex-col gap-0.5",
44
+ role: "list",
45
+ children
46
+ })]
47
+ });
48
+ }
49
+ function SidebarGroup({ label, defaultExpanded = true, children, className = "" }) {
50
+ const [expanded, setExpanded] = useState(defaultExpanded);
51
+ return /* @__PURE__ */ jsxs("div", {
52
+ className: `mb-2 ${className}`,
53
+ children: [/* @__PURE__ */ jsxs("button", {
54
+ type: "button",
55
+ "aria-expanded": expanded,
56
+ onClick: () => setExpanded(!expanded),
57
+ className: "flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest 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",
58
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx(ChevronDown, {
59
+ size: 12,
60
+ "aria-hidden": "true",
61
+ className: `transition-transform duration-200 ${expanded ? "" : "-rotate-90"}`
62
+ })]
63
+ }), expanded && /* @__PURE__ */ jsx("ul", {
64
+ className: "flex flex-col gap-0.5 mt-0.5",
65
+ role: "list",
66
+ children
67
+ })]
68
+ });
69
+ }
70
+ function SidebarItem({ href, onClick, active = false, icon, badge, disabled = false, children, className = "" }) {
71
+ const baseClass = [
72
+ "flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition",
73
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
74
+ active ? "bg-blue-50 text-brand-blue" : disabled ? "cursor-not-allowed text-slate-300" : "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
75
+ className
76
+ ].filter(Boolean).join(" ");
77
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
78
+ icon && /* @__PURE__ */ jsx("span", {
79
+ className: `shrink-0 ${active ? "text-brand-blue" : "text-slate-400"}`,
80
+ "aria-hidden": "true",
81
+ children: icon
82
+ }),
83
+ /* @__PURE__ */ jsx("span", {
84
+ className: "flex-1 truncate",
85
+ children
86
+ }),
87
+ badge !== void 0 && /* @__PURE__ */ jsx("span", {
88
+ className: `ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${active ? "bg-blue-50 text-brand-blue" : "bg-slate-100 text-slate-500"}`,
89
+ "aria-label": `${badge} items`,
90
+ children: badge
91
+ })
92
+ ] });
93
+ if (href && !disabled) return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
94
+ href,
95
+ "aria-current": active ? "page" : void 0,
96
+ className: baseClass,
97
+ onClick,
98
+ children: content
99
+ }) });
100
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
101
+ type: "button",
102
+ "aria-current": active ? "page" : void 0,
103
+ "aria-disabled": disabled || void 0,
104
+ disabled,
105
+ onClick: !disabled ? onClick : void 0,
106
+ className: baseClass,
107
+ children: content
108
+ }) });
109
+ }
110
+ //#endregion
111
+ export { SidebarGroup, SidebarItem, SidebarNav, SidebarSection };
112
+
113
+ //# sourceMappingURL=index51.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index51.mjs","names":[],"sources":["../src/ui-library/gallery/sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * Sidebar — Design System Component\n *\n * A vertical navigation panel. Supports grouped navigation sections,\n * collapsible groups, active item state, icons, and badges.\n *\n * Accessibility:\n * - Renders a <nav> landmark with aria-label.\n * - Active item receives aria-current=\"page\".\n * - Collapsible group buttons have aria-expanded.\n * - Collapsed content is hidden with aria-hidden + display:none equivalent.\n *\n * Composition:\n * <SidebarNav aria-label=\"Main navigation\">\n * <SidebarSection label=\"Workspace\">\n * <SidebarItem href=\"/dashboard\" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>\n * <SidebarItem href=\"/employees\" icon={<Users />}>Employees</SidebarItem>\n * <SidebarItem href=\"/reports\" icon={<BarChart2 />} badge=\"3\">Reports</SidebarItem>\n * </SidebarSection>\n * <SidebarGroup label=\"Settings\" collapsible>\n * <SidebarItem href=\"/settings/profile\">Profile</SidebarItem>\n * </SidebarGroup>\n * </SidebarNav>\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { ChevronDown } from 'lucide-react';\n\n// ── SidebarNav ────────────────────────────────────────────────────────────────\n\nexport interface SidebarNavProps {\n 'aria-label': string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarNav({ 'aria-label': ariaLabel, children, className = '' }: SidebarNavProps) {\n return (\n <nav\n aria-label={ariaLabel}\n className={`flex w-full flex-col gap-1 ${className}`}\n >\n {children}\n </nav>\n );\n}\n\n// ── SidebarSection ────────────────────────────────────────────────────────────\n// A non-collapsible labeled group.\n\nexport interface SidebarSectionProps {\n label?: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarSection({ label, children, className = '' }: SidebarSectionProps) {\n return (\n <div className={`mb-3 ${className}`}>\n {label && (\n <p className=\"mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400\">\n {label}\n </p>\n )}\n <ul className=\"flex flex-col gap-0.5\" role=\"list\">\n {children}\n </ul>\n </div>\n );\n}\n\n// ── SidebarGroup ──────────────────────────────────────────────────────────────\n// A collapsible labeled group.\n\nexport interface SidebarGroupProps {\n label: string;\n defaultExpanded?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarGroup({ label, defaultExpanded = true, children, className = '' }: SidebarGroupProps) {\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n return (\n <div className={`mb-2 ${className}`}>\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded(!expanded)}\n className=\"flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest 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 <span>{label}</span>\n <ChevronDown\n size={12}\n aria-hidden=\"true\"\n className={`transition-transform duration-200 ${expanded ? '' : '-rotate-90'}`}\n />\n </button>\n {expanded && (\n <ul className=\"flex flex-col gap-0.5 mt-0.5\" role=\"list\">\n {children}\n </ul>\n )}\n </div>\n );\n}\n\n// ── SidebarItem ───────────────────────────────────────────────────────────────\n\nexport interface SidebarItemProps {\n href?: string;\n onClick?: () => void;\n active?: boolean;\n icon?: ReactNode;\n badge?: string | number;\n disabled?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarItem({\n href,\n onClick,\n active = false,\n icon,\n badge,\n disabled = false,\n children,\n className = '',\n}: SidebarItemProps) {\n const baseClass = [\n 'flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n active\n ? 'bg-blue-50 text-brand-blue'\n : disabled\n ? 'cursor-not-allowed text-slate-300'\n : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const content = (\n <>\n {icon && (\n <span className={`shrink-0 ${active ? 'text-brand-blue' : 'text-slate-400'}`} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <span className=\"flex-1 truncate\">{children}</span>\n {badge !== undefined && (\n <span\n className={`ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${\n active ? 'bg-blue-50 text-brand-blue' : 'bg-slate-100 text-slate-500'\n }`}\n aria-label={`${badge} items`}\n >\n {badge}\n </span>\n )}\n </>\n );\n\n if (href && !disabled) {\n return (\n <li>\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className={baseClass}\n onClick={onClick}\n >\n {content}\n </a>\n </li>\n );\n }\n\n return (\n <li>\n <button\n type=\"button\"\n aria-current={active ? 'page' : undefined}\n aria-disabled={disabled || undefined}\n disabled={disabled}\n onClick={!disabled ? onClick : undefined}\n className={baseClass}\n >\n {content}\n </button>\n </li>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,WAAW,EAAE,cAAc,WAAW,UAAU,YAAY,MAAuB;CACjG,OACE,oBAAC,OAAD;EACE,cAAY;EACZ,WAAW,8BAA8B;EAExC;CACE,CAAA;AAET;AAWA,SAAgB,eAAe,EAAE,OAAO,UAAU,YAAY,MAA2B;CACvF,OACE,qBAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACG,SACC,oBAAC,KAAD;GAAG,WAAU;GACV,UAAA;EACA,CAAA,GAEL,oBAAC,MAAD;GAAI,WAAU;GAAwB,MAAK;GACxC;EACC,CAAA,CACD;;AAET;AAYA,SAAgB,aAAa,EAAE,OAAO,kBAAkB,MAAM,UAAU,YAAY,MAAyB;CAC3G,MAAM,CAAC,UAAU,eAAe,SAAS,eAAe;CAExD,OACE,qBAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACE,qBAAC,UAAD;GACE,MAAK;GACL,iBAAe;GACf,eAAe,YAAY,CAAC,QAAQ;GACpC,WAAU;GAJZ,UAAA,CAME,oBAAC,QAAD,EAAA,UAAO,MAAY,CAAA,GACnB,oBAAC,aAAD;IACE,MAAM;IACN,eAAY;IACZ,WAAW,qCAAqC,WAAW,KAAK;GACjE,CAAA,CACK;EACP,CAAA,GAAA,YACC,oBAAC,MAAD;GAAI,WAAU;GAA+B,MAAK;GAC/C;EACC,CAAA,CAEH;;AAET;AAeA,SAAgB,YAAY,EAC1B,MACA,SACA,SAAS,OACT,MACA,OACA,WAAW,OACX,UACA,YAAY,MACO;CACnB,MAAM,YAAY;EAChB;EACA;EACA,SACI,+BACA,WACE,sCACA;EACN;CACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;CAEX,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA;EACG,QACC,oBAAC,QAAD;GAAM,WAAW,YAAY,SAAS,oBAAoB;GAAoB,eAAY;GACvF,UAAA;EACG,CAAA;EAER,oBAAC,QAAD;GAAM,WAAU;GAAmB;EAAe,CAAA;EACjD,UAAU,KAAA,KACT,oBAAC,QAAD;GACE,WAAW,qEACT,SAAS,+BAA+B;GAE1C,cAAY,GAAG,MAAM;GAEpB,UAAA;EACG,CAAA;CAER,EAAA,CAAA;CAGJ,IAAI,QAAQ,CAAC,UACX,OACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;EACQ;EACN,gBAAc,SAAS,SAAS,KAAA;EAChC,WAAW;EACF;EAER,UAAA;CACA,CAAA,EACD,CAAA;CAIR,OACE,oBAAC,MAAD,EAAA,UACE,oBAAC,UAAD;EACE,MAAK;EACL,gBAAc,SAAS,SAAS,KAAA;EAChC,iBAAe,YAAY,KAAA;EACjB;EACV,SAAS,CAAC,WAAW,UAAU,KAAA;EAC/B,WAAW;EAEV,UAAA;CACK,CAAA,EACN,CAAA;AAER"}
@@ -0,0 +1,93 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ let react = require("react");
3
+ //#region src/ui-library/gallery/dropdown/Dropdown.tsx
4
+ /**
5
+ * Dropdown — Design System Component
6
+ *
7
+ * A controlled floating panel anchored to a trigger button.
8
+ * The consumer owns the open state, trigger content, and panel content.
9
+ *
10
+ * Responsibilities:
11
+ * - Trigger button with aria-expanded, aria-haspopup, aria-controls
12
+ * - Floating panel anchored below the trigger
13
+ * - Close on outside click (mousedown)
14
+ * - Close on Escape key
15
+ * - Stable panel ID per instance
16
+ *
17
+ * This component is intentionally presentation-only. It does not:
18
+ * - Manage selected options
19
+ * - Impose role="listbox" or role="option" on children
20
+ * - Handle async data
21
+ *
22
+ * Consumers are responsible for the trigger layout, option semantics,
23
+ * and any keyboard navigation within the panel.
24
+ *
25
+ * Usage:
26
+ * <Dropdown
27
+ * trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}
28
+ * isOpen={open}
29
+ * onToggle={() => setOpen(!open)}
30
+ * onClose={() => setOpen(false)}
31
+ * >
32
+ * <button onClick={() => select('all')}>All</button>
33
+ * <button onClick={() => select('active')}>Active</button>
34
+ * </Dropdown>
35
+ */
36
+ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassName = "", panelClassName = "" }) {
37
+ const ref = (0, react.useRef)(null);
38
+ const triggerRef = (0, react.useRef)(null);
39
+ const panelId = `${(0, react.useId)().replace(/:/g, "")}-panel`;
40
+ (0, react.useEffect)(() => {
41
+ if (!isOpen) return;
42
+ const handlePointerDown = (e) => {
43
+ if (ref.current && !ref.current.contains(e.target)) {
44
+ onClose();
45
+ triggerRef.current?.focus();
46
+ }
47
+ };
48
+ document.addEventListener("mousedown", handlePointerDown);
49
+ return () => document.removeEventListener("mousedown", handlePointerDown);
50
+ }, [isOpen, onClose]);
51
+ (0, react.useEffect)(() => {
52
+ if (!isOpen) return;
53
+ const handleKeyDown = (e) => {
54
+ if (e.key === "Escape") {
55
+ e.preventDefault();
56
+ onClose();
57
+ triggerRef.current?.focus();
58
+ }
59
+ };
60
+ document.addEventListener("keydown", handleKeyDown);
61
+ return () => document.removeEventListener("keydown", handleKeyDown);
62
+ }, [isOpen, onClose]);
63
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
64
+ className: "relative inline-block",
65
+ ref,
66
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
67
+ ref: triggerRef,
68
+ type: "button",
69
+ onClick: onToggle,
70
+ "aria-expanded": isOpen,
71
+ "aria-haspopup": "true",
72
+ "aria-controls": isOpen ? panelId : void 0,
73
+ className: [
74
+ "inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all",
75
+ "hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
76
+ triggerClassName
77
+ ].filter(Boolean).join(" "),
78
+ children: trigger
79
+ }), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
80
+ id: panelId,
81
+ className: [
82
+ "absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto",
83
+ "rounded-xl border border-slate-200 bg-white shadow-lg",
84
+ panelClassName
85
+ ].filter(Boolean).join(" "),
86
+ children
87
+ })]
88
+ });
89
+ }
90
+ //#endregion
91
+ exports.Dropdown = Dropdown;
92
+
93
+ //# sourceMappingURL=index52.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index52.cjs","names":[],"sources":["../src/ui-library/gallery/dropdown/Dropdown.tsx"],"sourcesContent":["/**\n * Dropdown — Design System Component\n *\n * A controlled floating panel anchored to a trigger button.\n * The consumer owns the open state, trigger content, and panel content.\n *\n * Responsibilities:\n * - Trigger button with aria-expanded, aria-haspopup, aria-controls\n * - Floating panel anchored below the trigger\n * - Close on outside click (mousedown)\n * - Close on Escape key\n * - Stable panel ID per instance\n *\n * This component is intentionally presentation-only. It does not:\n * - Manage selected options\n * - Impose role=\"listbox\" or role=\"option\" on children\n * - Handle async data\n *\n * Consumers are responsible for the trigger layout, option semantics,\n * and any keyboard navigation within the panel.\n *\n * Usage:\n * <Dropdown\n * trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}\n * isOpen={open}\n * onToggle={() => setOpen(!open)}\n * onClose={() => setOpen(false)}\n * >\n * <button onClick={() => select('all')}>All</button>\n * <button onClick={() => select('active')}>Active</button>\n * </Dropdown>\n */\n\nimport { useEffect, useId, useRef, type ReactNode } from 'react';\n\nexport interface DropdownProps {\n /** Complete trigger content — icons, label, chevron — owned by consumer. */\n trigger: ReactNode;\n /** Whether the panel is open. Owned by the consumer. */\n isOpen: boolean;\n /** Called when the trigger button is clicked. Consumer toggles isOpen. */\n onToggle: () => void;\n /** Called on outside click or Escape press. Consumer sets isOpen to false. */\n onClose: () => void;\n /** Panel content — option buttons or any children. */\n children: ReactNode;\n /** Optional className on the trigger button */\n triggerClassName?: string;\n /** Optional className on the floating panel */\n panelClassName?: string;\n}\n\nexport function Dropdown({\n trigger,\n isOpen,\n onToggle,\n onClose,\n children,\n triggerClassName = '',\n panelClassName = '',\n}: DropdownProps) {\n const ref = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const uid = useId();\n const panelId = `${uid.replace(/:/g, '')}-panel`;\n\n // Close on outside mousedown — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handlePointerDown = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('mousedown', handlePointerDown);\n return () => document.removeEventListener('mousedown', handlePointerDown);\n }, [isOpen, onClose]);\n\n // Close on Escape — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, onClose]);\n\n return (\n <div className=\"relative inline-block\" ref={ref}>\n <button\n ref={triggerRef}\n type=\"button\"\n onClick={onToggle}\n aria-expanded={isOpen}\n aria-haspopup=\"true\"\n aria-controls={isOpen ? panelId : undefined}\n className={[\n 'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all',\n 'hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n triggerClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {trigger}\n </button>\n\n {isOpen && (\n <div\n id={panelId}\n className={[\n 'absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto',\n 'rounded-xl border border-slate-200 bg-white shadow-lg',\n panelClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {children}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,SACA,QACA,UACA,SACA,UACA,mBAAmB,IACnB,iBAAiB,MACD;CAChB,MAAM,OAAA,GAAM,MAAA,OAAA,CAAuB,IAAI;CACvC,MAAM,cAAA,GAAa,MAAA,OAAA,CAA0B,IAAI;CAEjD,MAAM,UAAU,IAAA,GADJ,MAAA,MAAA,CACO,CAAA,CAAI,QAAQ,MAAM,EAAE,EAAE;CAGzC,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,qBAAqB,MAAkB;GAC3C,IAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,EAAE,MAAc,GAAG;IAC1D,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,aAAa,iBAAiB;EACxD,aAAa,SAAS,oBAAoB,aAAa,iBAAiB;CAC1E,GAAG,CAAC,QAAQ,OAAO,CAAC;CAGpB,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,UAAU;IACtB,EAAE,eAAe;IACjB,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa,SAAS,oBAAoB,WAAW,aAAa;CACpE,GAAG,CAAC,QAAQ,OAAO,CAAC;CAEpB,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAA6B;EAA5C,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,KAAK;GACL,MAAK;GACL,SAAS;GACT,iBAAe;GACf,iBAAc;GACd,iBAAe,SAAS,UAAU,KAAA;GAClC,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV,UAAA;EACK,CAAA,GAEP,UACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;GACE,IAAI;GACJ,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV;EACE,CAAA,CAEJ;;AAET"}
@@ -0,0 +1,93 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef } from "react";
3
+ //#region src/ui-library/gallery/dropdown/Dropdown.tsx
4
+ /**
5
+ * Dropdown — Design System Component
6
+ *
7
+ * A controlled floating panel anchored to a trigger button.
8
+ * The consumer owns the open state, trigger content, and panel content.
9
+ *
10
+ * Responsibilities:
11
+ * - Trigger button with aria-expanded, aria-haspopup, aria-controls
12
+ * - Floating panel anchored below the trigger
13
+ * - Close on outside click (mousedown)
14
+ * - Close on Escape key
15
+ * - Stable panel ID per instance
16
+ *
17
+ * This component is intentionally presentation-only. It does not:
18
+ * - Manage selected options
19
+ * - Impose role="listbox" or role="option" on children
20
+ * - Handle async data
21
+ *
22
+ * Consumers are responsible for the trigger layout, option semantics,
23
+ * and any keyboard navigation within the panel.
24
+ *
25
+ * Usage:
26
+ * <Dropdown
27
+ * trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}
28
+ * isOpen={open}
29
+ * onToggle={() => setOpen(!open)}
30
+ * onClose={() => setOpen(false)}
31
+ * >
32
+ * <button onClick={() => select('all')}>All</button>
33
+ * <button onClick={() => select('active')}>Active</button>
34
+ * </Dropdown>
35
+ */
36
+ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassName = "", panelClassName = "" }) {
37
+ const ref = useRef(null);
38
+ const triggerRef = useRef(null);
39
+ const panelId = `${useId().replace(/:/g, "")}-panel`;
40
+ useEffect(() => {
41
+ if (!isOpen) return;
42
+ const handlePointerDown = (e) => {
43
+ if (ref.current && !ref.current.contains(e.target)) {
44
+ onClose();
45
+ triggerRef.current?.focus();
46
+ }
47
+ };
48
+ document.addEventListener("mousedown", handlePointerDown);
49
+ return () => document.removeEventListener("mousedown", handlePointerDown);
50
+ }, [isOpen, onClose]);
51
+ useEffect(() => {
52
+ if (!isOpen) return;
53
+ const handleKeyDown = (e) => {
54
+ if (e.key === "Escape") {
55
+ e.preventDefault();
56
+ onClose();
57
+ triggerRef.current?.focus();
58
+ }
59
+ };
60
+ document.addEventListener("keydown", handleKeyDown);
61
+ return () => document.removeEventListener("keydown", handleKeyDown);
62
+ }, [isOpen, onClose]);
63
+ return /* @__PURE__ */ jsxs("div", {
64
+ className: "relative inline-block",
65
+ ref,
66
+ children: [/* @__PURE__ */ jsx("button", {
67
+ ref: triggerRef,
68
+ type: "button",
69
+ onClick: onToggle,
70
+ "aria-expanded": isOpen,
71
+ "aria-haspopup": "true",
72
+ "aria-controls": isOpen ? panelId : void 0,
73
+ className: [
74
+ "inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all",
75
+ "hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
76
+ triggerClassName
77
+ ].filter(Boolean).join(" "),
78
+ children: trigger
79
+ }), isOpen && /* @__PURE__ */ jsx("div", {
80
+ id: panelId,
81
+ className: [
82
+ "absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto",
83
+ "rounded-xl border border-slate-200 bg-white shadow-lg",
84
+ panelClassName
85
+ ].filter(Boolean).join(" "),
86
+ children
87
+ })]
88
+ });
89
+ }
90
+ //#endregion
91
+ export { Dropdown };
92
+
93
+ //# sourceMappingURL=index52.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index52.mjs","names":[],"sources":["../src/ui-library/gallery/dropdown/Dropdown.tsx"],"sourcesContent":["/**\n * Dropdown — Design System Component\n *\n * A controlled floating panel anchored to a trigger button.\n * The consumer owns the open state, trigger content, and panel content.\n *\n * Responsibilities:\n * - Trigger button with aria-expanded, aria-haspopup, aria-controls\n * - Floating panel anchored below the trigger\n * - Close on outside click (mousedown)\n * - Close on Escape key\n * - Stable panel ID per instance\n *\n * This component is intentionally presentation-only. It does not:\n * - Manage selected options\n * - Impose role=\"listbox\" or role=\"option\" on children\n * - Handle async data\n *\n * Consumers are responsible for the trigger layout, option semantics,\n * and any keyboard navigation within the panel.\n *\n * Usage:\n * <Dropdown\n * trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}\n * isOpen={open}\n * onToggle={() => setOpen(!open)}\n * onClose={() => setOpen(false)}\n * >\n * <button onClick={() => select('all')}>All</button>\n * <button onClick={() => select('active')}>Active</button>\n * </Dropdown>\n */\n\nimport { useEffect, useId, useRef, type ReactNode } from 'react';\n\nexport interface DropdownProps {\n /** Complete trigger content — icons, label, chevron — owned by consumer. */\n trigger: ReactNode;\n /** Whether the panel is open. Owned by the consumer. */\n isOpen: boolean;\n /** Called when the trigger button is clicked. Consumer toggles isOpen. */\n onToggle: () => void;\n /** Called on outside click or Escape press. Consumer sets isOpen to false. */\n onClose: () => void;\n /** Panel content — option buttons or any children. */\n children: ReactNode;\n /** Optional className on the trigger button */\n triggerClassName?: string;\n /** Optional className on the floating panel */\n panelClassName?: string;\n}\n\nexport function Dropdown({\n trigger,\n isOpen,\n onToggle,\n onClose,\n children,\n triggerClassName = '',\n panelClassName = '',\n}: DropdownProps) {\n const ref = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const uid = useId();\n const panelId = `${uid.replace(/:/g, '')}-panel`;\n\n // Close on outside mousedown — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handlePointerDown = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('mousedown', handlePointerDown);\n return () => document.removeEventListener('mousedown', handlePointerDown);\n }, [isOpen, onClose]);\n\n // Close on Escape — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, onClose]);\n\n return (\n <div className=\"relative inline-block\" ref={ref}>\n <button\n ref={triggerRef}\n type=\"button\"\n onClick={onToggle}\n aria-expanded={isOpen}\n aria-haspopup=\"true\"\n aria-controls={isOpen ? panelId : undefined}\n className={[\n 'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all',\n 'hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n triggerClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {trigger}\n </button>\n\n {isOpen && (\n <div\n id={panelId}\n className={[\n 'absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto',\n 'rounded-xl border border-slate-200 bg-white shadow-lg',\n panelClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {children}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,SACA,QACA,UACA,SACA,UACA,mBAAmB,IACnB,iBAAiB,MACD;CAChB,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,aAAa,OAA0B,IAAI;CAEjD,MAAM,UAAU,GADJ,MACO,CAAA,CAAI,QAAQ,MAAM,EAAE,EAAE;CAGzC,gBAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,qBAAqB,MAAkB;GAC3C,IAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,EAAE,MAAc,GAAG;IAC1D,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,aAAa,iBAAiB;EACxD,aAAa,SAAS,oBAAoB,aAAa,iBAAiB;CAC1E,GAAG,CAAC,QAAQ,OAAO,CAAC;CAGpB,gBAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,UAAU;IACtB,EAAE,eAAe;IACjB,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa,SAAS,oBAAoB,WAAW,aAAa;CACpE,GAAG,CAAC,QAAQ,OAAO,CAAC;CAEpB,OACE,qBAAC,OAAD;EAAK,WAAU;EAA6B;EAA5C,UAAA,CACE,oBAAC,UAAD;GACE,KAAK;GACL,MAAK;GACL,SAAS;GACT,iBAAe;GACf,iBAAc;GACd,iBAAe,SAAS,UAAU,KAAA;GAClC,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV,UAAA;EACK,CAAA,GAEP,UACC,oBAAC,OAAD;GACE,IAAI;GACJ,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV;EACE,CAAA,CAEJ;;AAET"}