@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,161 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useId, useRef, useState } from "react";
3
+ //#region src/ui-library/gallery/tabs/Tabs.tsx
4
+ /**
5
+ * Tabs — Design System Component
6
+ *
7
+ * Composable tab interface with full WAI-ARIA Tabs pattern compliance.
8
+ *
9
+ * Responsibilities:
10
+ * - role="tablist" on the tab strip
11
+ * - role="tab" with aria-selected, aria-controls, aria-disabled on each tab
12
+ * - role="tabpanel" with aria-labelledby on each panel
13
+ * - Stable generated IDs for tab/panel cross-referencing
14
+ * - ArrowRight / ArrowLeft / Home / End keyboard navigation
15
+ * - Controlled and uncontrolled usage
16
+ * - Disabled tab support (skipped during keyboard navigation)
17
+ *
18
+ * Composition:
19
+ * <Tabs defaultValue="overview">
20
+ * <TabList aria-label="Section navigation">
21
+ * <Tab value="overview">Overview</Tab>
22
+ * <Tab value="details">Details</Tab>
23
+ * <Tab value="history" disabled>History</Tab>
24
+ * </TabList>
25
+ * <TabPanels>
26
+ * <TabPanel value="overview">…</TabPanel>
27
+ * <TabPanel value="details">…</TabPanel>
28
+ * <TabPanel value="history">…</TabPanel>
29
+ * </TabPanels>
30
+ * </Tabs>
31
+ *
32
+ * Keyboard model: "manual activation" — ArrowKey moves focus but the user
33
+ * must press Space/Enter to activate a new tab. This is the WAI-ARIA
34
+ * recommended default when tab panels contain complex content (forms, tables)
35
+ * because "automatic activation" can trigger expensive content fetches on
36
+ * every arrow press.
37
+ *
38
+ * To switch to "automatic activation" (Arrow key also activates),
39
+ * set activateOnFocus={true} on <Tabs>.
40
+ */
41
+ var TabsContext = createContext(null);
42
+ function useTabsContext() {
43
+ const ctx = useContext(TabsContext);
44
+ if (!ctx) throw new Error("Tabs sub-component used outside <Tabs>");
45
+ return ctx;
46
+ }
47
+ function tabId(baseId, value) {
48
+ return `${baseId}-tab-${value}`;
49
+ }
50
+ function panelId(baseId, value) {
51
+ return `${baseId}-panel-${value}`;
52
+ }
53
+ function Tabs({ value: controlledValue, onValueChange, defaultValue = "", activateOnFocus = false, children, className = "" }) {
54
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
55
+ const baseId = useId();
56
+ const tabValues = useRef([]);
57
+ const isControlled = controlledValue !== void 0;
58
+ const activeValue = isControlled ? controlledValue : uncontrolledValue;
59
+ const disabledValues = useRef(/* @__PURE__ */ new Set()).current;
60
+ const activate = (value) => {
61
+ if (disabledValues.has(value)) return;
62
+ if (!isControlled) setUncontrolledValue(value);
63
+ onValueChange?.(value);
64
+ };
65
+ return /* @__PURE__ */ jsx(TabsContext.Provider, {
66
+ value: {
67
+ activeValue,
68
+ activate,
69
+ baseId,
70
+ tabValues,
71
+ disabledValues,
72
+ activateOnFocus
73
+ },
74
+ children: /* @__PURE__ */ jsx("div", {
75
+ className,
76
+ children
77
+ })
78
+ });
79
+ }
80
+ function TabList({ "aria-label": ariaLabel, children, className = "" }) {
81
+ const { baseId, tabValues, disabledValues, activate, activateOnFocus } = useTabsContext();
82
+ const handleKeyDown = (e) => {
83
+ const enabled = tabValues.current.filter((v) => !disabledValues.has(v));
84
+ if (enabled.length === 0) return;
85
+ const currentFocused = document.activeElement;
86
+ const currentIdx = tabValues.current.findIndex((v) => document.getElementById(tabId(baseId, v)) === currentFocused);
87
+ const currentEnabledIdx = enabled.indexOf(tabValues.current[currentIdx] ?? "");
88
+ let nextValue;
89
+ if (e.key === "ArrowRight") {
90
+ e.preventDefault();
91
+ nextValue = enabled[(currentEnabledIdx + 1) % enabled.length];
92
+ } else if (e.key === "ArrowLeft") {
93
+ e.preventDefault();
94
+ nextValue = enabled[(currentEnabledIdx - 1 + enabled.length) % enabled.length];
95
+ } else if (e.key === "Home") {
96
+ e.preventDefault();
97
+ nextValue = enabled[0];
98
+ } else if (e.key === "End") {
99
+ e.preventDefault();
100
+ nextValue = enabled[enabled.length - 1];
101
+ }
102
+ if (nextValue !== void 0) {
103
+ document.getElementById(tabId(baseId, nextValue))?.focus();
104
+ if (activateOnFocus) activate(nextValue);
105
+ }
106
+ };
107
+ return /* @__PURE__ */ jsx("div", {
108
+ role: "tablist",
109
+ "aria-label": ariaLabel,
110
+ onKeyDown: handleKeyDown,
111
+ className: `flex border-b-2 border-slate-200 ${className}`,
112
+ children
113
+ });
114
+ }
115
+ function Tab({ value, disabled = false, children, className = "" }) {
116
+ const { activeValue, activate, baseId, tabValues, disabledValues } = useTabsContext();
117
+ if (!tabValues.current.includes(value)) tabValues.current.push(value);
118
+ if (disabled) disabledValues.add(value);
119
+ else disabledValues.delete(value);
120
+ const isActive = activeValue === value;
121
+ return /* @__PURE__ */ jsx("button", {
122
+ id: tabId(baseId, value),
123
+ type: "button",
124
+ role: "tab",
125
+ "aria-selected": isActive,
126
+ "aria-controls": panelId(baseId, value),
127
+ "aria-disabled": disabled || void 0,
128
+ disabled,
129
+ tabIndex: isActive ? 0 : -1,
130
+ onClick: () => activate(value),
131
+ className: [
132
+ "relative px-5 py-3 text-sm font-semibold font-heading border-b-4 transition-colors",
133
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30",
134
+ isActive ? "border-brand-blue text-brand-blue" : disabled ? "border-transparent text-slate-300 cursor-not-allowed" : "border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300",
135
+ className
136
+ ].filter(Boolean).join(" "),
137
+ children
138
+ });
139
+ }
140
+ function TabPanels({ children, className = "" }) {
141
+ return /* @__PURE__ */ jsx("div", {
142
+ className,
143
+ children
144
+ });
145
+ }
146
+ function TabPanel({ value, children, className = "" }) {
147
+ const { activeValue, baseId } = useTabsContext();
148
+ if (activeValue !== value) return null;
149
+ return /* @__PURE__ */ jsx("div", {
150
+ id: panelId(baseId, value),
151
+ role: "tabpanel",
152
+ "aria-labelledby": tabId(baseId, value),
153
+ tabIndex: 0,
154
+ className: ["focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30 rounded-sm", className].filter(Boolean).join(" "),
155
+ children
156
+ });
157
+ }
158
+ //#endregion
159
+ export { Tab, TabList, TabPanel, TabPanels, Tabs };
160
+
161
+ //# sourceMappingURL=index48.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index48.mjs","names":[],"sources":["../src/ui-library/gallery/tabs/Tabs.tsx"],"sourcesContent":["/**\n * Tabs — Design System Component\n *\n * Composable tab interface with full WAI-ARIA Tabs pattern compliance.\n *\n * Responsibilities:\n * - role=\"tablist\" on the tab strip\n * - role=\"tab\" with aria-selected, aria-controls, aria-disabled on each tab\n * - role=\"tabpanel\" with aria-labelledby on each panel\n * - Stable generated IDs for tab/panel cross-referencing\n * - ArrowRight / ArrowLeft / Home / End keyboard navigation\n * - Controlled and uncontrolled usage\n * - Disabled tab support (skipped during keyboard navigation)\n *\n * Composition:\n * <Tabs defaultValue=\"overview\">\n * <TabList aria-label=\"Section navigation\">\n * <Tab value=\"overview\">Overview</Tab>\n * <Tab value=\"details\">Details</Tab>\n * <Tab value=\"history\" disabled>History</Tab>\n * </TabList>\n * <TabPanels>\n * <TabPanel value=\"overview\">…</TabPanel>\n * <TabPanel value=\"details\">…</TabPanel>\n * <TabPanel value=\"history\">…</TabPanel>\n * </TabPanels>\n * </Tabs>\n *\n * Keyboard model: \"manual activation\" — ArrowKey moves focus but the user\n * must press Space/Enter to activate a new tab. This is the WAI-ARIA\n * recommended default when tab panels contain complex content (forms, tables)\n * because \"automatic activation\" can trigger expensive content fetches on\n * every arrow press.\n *\n * To switch to \"automatic activation\" (Arrow key also activates),\n * set activateOnFocus={true} on <Tabs>.\n */\n\nimport {\n createContext,\n useContext,\n useId,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactNode,\n} from 'react';\n\n// ── Context ───────────────────────────────────────────────────────────────────\n\ninterface TabsContextValue {\n /** Currently active tab value */\n activeValue: string;\n /** Activate a tab (does nothing if disabled) */\n activate: (value: string) => void;\n /** Base id prefix shared by all tabs and panels in this Tabs instance */\n baseId: string;\n /** Registered tab values in DOM order (built up by Tab children) */\n tabValues: React.MutableRefObject<string[]>;\n /** Set of disabled tab values */\n disabledValues: Set<string>;\n /** Whether arrow keys also activate (true) or only move focus (false) */\n activateOnFocus: boolean;\n}\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\n\nfunction useTabsContext(): TabsContextValue {\n const ctx = useContext(TabsContext);\n if (!ctx) throw new Error('Tabs sub-component used outside <Tabs>');\n return ctx;\n}\n\n// ── Stable ID helpers ─────────────────────────────────────────────────────────\n\nfunction tabId(baseId: string, value: string) {\n return `${baseId}-tab-${value}`;\n}\n\nfunction panelId(baseId: string, value: string) {\n return `${baseId}-panel-${value}`;\n}\n\n// ── Tabs (root) ───────────────────────────────────────────────────────────────\n\nexport interface TabsProps {\n /** Controlled: current active tab value */\n value?: string;\n /** Controlled: called when a tab is activated */\n onValueChange?: (value: string) => void;\n /** Uncontrolled: initial active tab value */\n defaultValue?: string;\n /** Whether ArrowKey navigation also activates the tab immediately */\n activateOnFocus?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function Tabs({\n value: controlledValue,\n onValueChange,\n defaultValue = '',\n activateOnFocus = false,\n children,\n className = '',\n}: TabsProps) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const baseId = useId();\n const tabValues = useRef<string[]>([]);\n\n const isControlled = controlledValue !== undefined;\n const activeValue = isControlled ? controlledValue : uncontrolledValue;\n\n // Collect disabled values from child Tab props via context\n // eslint-disable-next-line react-hooks/refs\n const disabledValues = useRef(new Set<string>()).current;\n\n const activate = (value: string) => {\n if (disabledValues.has(value)) return;\n if (!isControlled) setUncontrolledValue(value);\n onValueChange?.(value);\n };\n\n return (\n <TabsContext.Provider\n value={{ activeValue, activate, baseId, tabValues, disabledValues, activateOnFocus }}\n >\n <div className={className}>{children}</div>\n </TabsContext.Provider>\n );\n}\n\n// ── TabList ───────────────────────────────────────────────────────────────────\n\nexport interface TabListProps {\n /** Accessible label for the tablist landmark */\n 'aria-label'?: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function TabList({\n 'aria-label': ariaLabel,\n children,\n className = '',\n}: TabListProps) {\n const { baseId, tabValues, disabledValues, activate, activateOnFocus } =\n useTabsContext();\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n const enabled = tabValues.current.filter((v) => !disabledValues.has(v));\n if (enabled.length === 0) return;\n\n const currentFocused = document.activeElement;\n const currentIdx = tabValues.current.findIndex(\n (v) => document.getElementById(tabId(baseId, v)) === currentFocused,\n );\n const currentEnabledIdx = enabled.indexOf(tabValues.current[currentIdx] ?? '');\n\n let nextValue: string | undefined;\n\n if (e.key === 'ArrowRight') {\n e.preventDefault();\n nextValue = enabled[(currentEnabledIdx + 1) % enabled.length];\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n nextValue = enabled[(currentEnabledIdx - 1 + enabled.length) % enabled.length];\n } else if (e.key === 'Home') {\n e.preventDefault();\n nextValue = enabled[0];\n } else if (e.key === 'End') {\n e.preventDefault();\n nextValue = enabled[enabled.length - 1];\n }\n\n if (nextValue !== undefined) {\n // Always move focus to the tab element\n document.getElementById(tabId(baseId, nextValue))?.focus();\n // Activate immediately if activateOnFocus, otherwise wait for Space/Enter\n if (activateOnFocus) activate(nextValue);\n }\n };\n\n return (\n <div\n role=\"tablist\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={`flex border-b-2 border-slate-200 ${className}`}\n >\n {children}\n </div>\n );\n}\n\n// ── Tab ───────────────────────────────────────────────────────────────────────\n\nexport interface TabProps {\n /** Must match the value used in <TabPanel value=\"…\"> */\n value: string;\n disabled?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function Tab({ value, disabled = false, children, className = '' }: TabProps) {\n const { activeValue, activate, baseId, tabValues, disabledValues } = useTabsContext();\n\n // Register this tab's value in DOM order on first render.\n // Using a ref-mutation here avoids needing a reducer and is safe because\n // the tab list is stable (static children).\n if (!tabValues.current.includes(value)) {\n tabValues.current.push(value);\n }\n if (disabled) {\n disabledValues.add(value);\n } else {\n disabledValues.delete(value);\n }\n\n const isActive = activeValue === value;\n\n return (\n <button\n id={tabId(baseId, value)}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={panelId(baseId, value)}\n aria-disabled={disabled || undefined}\n disabled={disabled}\n tabIndex={isActive ? 0 : -1}\n onClick={() => activate(value)}\n className={[\n 'relative px-5 py-3 text-sm font-semibold font-heading border-b-4 transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30',\n isActive\n ? 'border-brand-blue text-brand-blue'\n : disabled\n ? 'border-transparent text-slate-300 cursor-not-allowed'\n : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {children}\n </button>\n );\n}\n\n// ── TabPanels ─────────────────────────────────────────────────────────────────\n\nexport interface TabPanelsProps {\n children: ReactNode;\n className?: string;\n}\n\nexport function TabPanels({ children, className = '' }: TabPanelsProps) {\n return <div className={className}>{children}</div>;\n}\n\n// ── TabPanel ──────────────────────────────────────────────────────────────────\n\nexport interface TabPanelProps {\n /** Must match the value used in <Tab value=\"…\"> */\n value: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function TabPanel({ value, children, className = '' }: TabPanelProps) {\n const { activeValue, baseId } = useTabsContext();\n\n if (activeValue !== value) return null;\n\n return (\n <div\n id={panelId(baseId, value)}\n role=\"tabpanel\"\n aria-labelledby={tabId(baseId, value)}\n tabIndex={0}\n className={[\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30 rounded-sm',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {children}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ let lucide_react = require("lucide-react");
3
+ //#region src/ui-library/gallery/pagination/Pagination.tsx
4
+ /**
5
+ * Pagination — Design System Component
6
+ *
7
+ * Renders previous/next navigation and page number pills.
8
+ * Presentation-only — the consuming feature owns page state.
9
+ *
10
+ * Page numbers collapse intelligently with ellipsis when there are
11
+ * more pages than can fit (> 7 pages visible at once).
12
+ *
13
+ * Usage:
14
+ * <Pagination
15
+ * page={page}
16
+ * totalPages={totalPages}
17
+ * onPageChange={setPage}
18
+ * />
19
+ */
20
+ function getPageNumbers(current, total) {
21
+ if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
22
+ const pages = [1];
23
+ if (current > 3) pages.push("ellipsis");
24
+ const start = Math.max(2, current - 1);
25
+ const end = Math.min(total - 1, current + 1);
26
+ for (let i = start; i <= end; i++) pages.push(i);
27
+ if (current < total - 2) pages.push("ellipsis");
28
+ pages.push(total);
29
+ return pages;
30
+ }
31
+ function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination" }) {
32
+ if (totalPages <= 1) return null;
33
+ const pageNumbers = getPageNumbers(page, totalPages);
34
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
35
+ "aria-label": ariaLabel,
36
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
37
+ className: "list-none flex items-center gap-1",
38
+ role: "list",
39
+ children: [
40
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
41
+ type: "button",
42
+ onClick: () => onPageChange(page - 1),
43
+ disabled: page <= 1,
44
+ "aria-label": "Previous page",
45
+ className: "flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40",
46
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, {
47
+ size: 16,
48
+ "aria-hidden": "true"
49
+ })
50
+ }) }),
51
+ pageNumbers.map((n, idx) => n === "ellipsis" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
52
+ "aria-hidden": "true",
53
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
54
+ className: "flex h-9 w-9 items-center justify-center text-xs font-bold text-slate-300",
55
+ children: "…"
56
+ })
57
+ }, `ellipsis-${idx}`) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
58
+ type: "button",
59
+ onClick: () => onPageChange(n),
60
+ "aria-label": `Page ${n}`,
61
+ "aria-current": n === page ? "page" : void 0,
62
+ className: ["flex h-9 w-9 items-center justify-center rounded-md text-xs font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30", n === page ? "bg-brand-blue text-white shadow-md shadow-brand-blue/20" : "border border-slate-200 bg-white text-slate-500 hover:border-slate-300 hover:text-slate-800"].filter(Boolean).join(" "),
63
+ children: n
64
+ }) }, n)),
65
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
66
+ type: "button",
67
+ onClick: () => onPageChange(page + 1),
68
+ disabled: page >= totalPages,
69
+ "aria-label": "Next page",
70
+ className: "flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40",
71
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
72
+ size: 16,
73
+ "aria-hidden": "true"
74
+ })
75
+ }) })
76
+ ]
77
+ })
78
+ });
79
+ }
80
+ //#endregion
81
+ exports.Pagination = Pagination;
82
+
83
+ //# sourceMappingURL=index49.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index49.cjs","names":[],"sources":["../src/ui-library/gallery/pagination/Pagination.tsx"],"sourcesContent":["/**\n * Pagination — Design System Component\n *\n * Renders previous/next navigation and page number pills.\n * Presentation-only — the consuming feature owns page state.\n *\n * Page numbers collapse intelligently with ellipsis when there are\n * more pages than can fit (> 7 pages visible at once).\n *\n * Usage:\n * <Pagination\n * page={page}\n * totalPages={totalPages}\n * onPageChange={setPage}\n * />\n */\n\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\nexport interface PaginationProps {\n /** Current page number (1-indexed) */\n page: number;\n /** Total number of pages */\n totalPages: number;\n /** Called when the user selects a different page */\n onPageChange: (page: number) => void;\n /** Accessible label for the navigation landmark (default: \"Pagination\") */\n ariaLabel?: string;\n}\n\n// ── Page number generation ─────────────────────────────────────────────────\n\nfunction getPageNumbers(current: number, total: number): (number | 'ellipsis')[] {\n if (total <= 7) {\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const pages: (number | 'ellipsis')[] = [1];\n\n if (current > 3) pages.push('ellipsis');\n\n const start = Math.max(2, current - 1);\n const end = Math.min(total - 1, current + 1);\n for (let i = start; i <= end; i++) pages.push(i);\n\n if (current < total - 2) pages.push('ellipsis');\n\n pages.push(total);\n return pages;\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function Pagination({\n page,\n totalPages,\n onPageChange,\n ariaLabel = 'Pagination',\n}: PaginationProps) {\n if (totalPages <= 1) return null;\n\n const pageNumbers = getPageNumbers(page, totalPages);\n\n return (\n <nav aria-label={ariaLabel}>\n <ol className=\"list-none flex items-center gap-1\" role=\"list\">\n {/* Previous */}\n <li>\n <button\n type=\"button\"\n onClick={() => onPageChange(page - 1)}\n disabled={page <= 1}\n aria-label=\"Previous page\"\n className=\"flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft size={16} aria-hidden=\"true\" />\n </button>\n </li>\n\n {/* Page numbers */}\n {pageNumbers.map((n, idx) =>\n n === 'ellipsis' ? (\n <li key={`ellipsis-${idx}`} aria-hidden=\"true\">\n <span className=\"flex h-9 w-9 items-center justify-center text-xs font-bold text-slate-300\">\n …\n </span>\n </li>\n ) : (\n <li key={n}>\n <button\n type=\"button\"\n onClick={() => onPageChange(n)}\n aria-label={`Page ${n}`}\n aria-current={n === page ? 'page' : undefined}\n className={[\n 'flex h-9 w-9 items-center justify-center rounded-md text-xs font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n n === page\n ? 'bg-brand-blue text-white shadow-md shadow-brand-blue/20'\n : 'border border-slate-200 bg-white text-slate-500 hover:border-slate-300 hover:text-slate-800',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {n}\n </button>\n </li>\n ),\n )}\n\n {/* Next */}\n <li>\n <button\n type=\"button\"\n onClick={() => onPageChange(page + 1)}\n disabled={page >= totalPages}\n aria-label=\"Next page\"\n className=\"flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronRight size={16} aria-hidden=\"true\" />\n </button>\n </li>\n </ol>\n </nav>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,SAAS,eAAe,SAAiB,OAAwC;CAC/E,IAAI,SAAS,GACX,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAGtD,MAAM,QAAiC,CAAC,CAAC;CAEzC,IAAI,UAAU,GAAG,MAAM,KAAK,UAAU;CAEtC,MAAM,QAAQ,KAAK,IAAI,GAAG,UAAU,CAAC;CACrC,MAAM,MAAQ,KAAK,IAAI,QAAQ,GAAG,UAAU,CAAC;CAC7C,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,KAAK,MAAM,KAAK,CAAC;CAE/C,IAAI,UAAU,QAAQ,GAAG,MAAM,KAAK,UAAU;CAE9C,MAAM,KAAK,KAAK;CAChB,OAAO;AACT;AAIA,SAAgB,WAAW,EACzB,MACA,YACA,cACA,YAAY,gBACM;CAClB,IAAI,cAAc,GAAG,OAAO;CAE5B,MAAM,cAAc,eAAe,MAAM,UAAU;CAEnD,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,cAAY;EACf,UAAA,iBAAA,GAAA,kBAAA,KAAA,CAAC,MAAD;GAAI,WAAU;GAAoC,MAAK;GAAvD,UAAA;IAEE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;KACE,MAAK;KACL,eAAe,aAAa,OAAO,CAAC;KACpC,UAAU,QAAQ;KAClB,cAAW;KACX,WAAU;KAEV,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,aAAD;MAAa,MAAM;MAAI,eAAY;KAAQ,CAAA;IACrC,CAAA,EACN,CAAA;IAGH,YAAY,KAAK,GAAG,QACnB,MAAM,aACJ,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;KAA4B,eAAY;KACtC,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAU;MAA4E,UAAA;KAEtF,CAAA;IACJ,GAJK,YAAY,KAIjB,IAEJ,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;KACE,MAAK;KACL,eAAe,aAAa,CAAC;KAC7B,cAAY,QAAQ;KACpB,gBAAc,MAAM,OAAO,SAAS,KAAA;KACpC,WAAW,CACT,qKACA,MAAM,OACF,4DACA,6FACN,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;KAEV,UAAA;IACK,CAAA,EACN,GAjBK,CAiBL,CAER;IAGA,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;KACE,MAAK;KACL,eAAe,aAAa,OAAO,CAAC;KACpC,UAAU,QAAQ;KAClB,cAAW;KACX,WAAU;KAEV,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,cAAD;MAAc,MAAM;MAAI,eAAY;KAAQ,CAAA;IACtC,CAAA,EACN,CAAA;GACF;;CACD,CAAA;AAET"}
@@ -0,0 +1,83 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+ //#region src/ui-library/gallery/pagination/Pagination.tsx
4
+ /**
5
+ * Pagination — Design System Component
6
+ *
7
+ * Renders previous/next navigation and page number pills.
8
+ * Presentation-only — the consuming feature owns page state.
9
+ *
10
+ * Page numbers collapse intelligently with ellipsis when there are
11
+ * more pages than can fit (> 7 pages visible at once).
12
+ *
13
+ * Usage:
14
+ * <Pagination
15
+ * page={page}
16
+ * totalPages={totalPages}
17
+ * onPageChange={setPage}
18
+ * />
19
+ */
20
+ function getPageNumbers(current, total) {
21
+ if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
22
+ const pages = [1];
23
+ if (current > 3) pages.push("ellipsis");
24
+ const start = Math.max(2, current - 1);
25
+ const end = Math.min(total - 1, current + 1);
26
+ for (let i = start; i <= end; i++) pages.push(i);
27
+ if (current < total - 2) pages.push("ellipsis");
28
+ pages.push(total);
29
+ return pages;
30
+ }
31
+ function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination" }) {
32
+ if (totalPages <= 1) return null;
33
+ const pageNumbers = getPageNumbers(page, totalPages);
34
+ return /* @__PURE__ */ jsx("nav", {
35
+ "aria-label": ariaLabel,
36
+ children: /* @__PURE__ */ jsxs("ol", {
37
+ className: "list-none flex items-center gap-1",
38
+ role: "list",
39
+ children: [
40
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
41
+ type: "button",
42
+ onClick: () => onPageChange(page - 1),
43
+ disabled: page <= 1,
44
+ "aria-label": "Previous page",
45
+ className: "flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40",
46
+ children: /* @__PURE__ */ jsx(ChevronLeft, {
47
+ size: 16,
48
+ "aria-hidden": "true"
49
+ })
50
+ }) }),
51
+ pageNumbers.map((n, idx) => n === "ellipsis" ? /* @__PURE__ */ jsx("li", {
52
+ "aria-hidden": "true",
53
+ children: /* @__PURE__ */ jsx("span", {
54
+ className: "flex h-9 w-9 items-center justify-center text-xs font-bold text-slate-300",
55
+ children: "…"
56
+ })
57
+ }, `ellipsis-${idx}`) : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
58
+ type: "button",
59
+ onClick: () => onPageChange(n),
60
+ "aria-label": `Page ${n}`,
61
+ "aria-current": n === page ? "page" : void 0,
62
+ className: ["flex h-9 w-9 items-center justify-center rounded-md text-xs font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30", n === page ? "bg-brand-blue text-white shadow-md shadow-brand-blue/20" : "border border-slate-200 bg-white text-slate-500 hover:border-slate-300 hover:text-slate-800"].filter(Boolean).join(" "),
63
+ children: n
64
+ }) }, n)),
65
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
66
+ type: "button",
67
+ onClick: () => onPageChange(page + 1),
68
+ disabled: page >= totalPages,
69
+ "aria-label": "Next page",
70
+ className: "flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40",
71
+ children: /* @__PURE__ */ jsx(ChevronRight, {
72
+ size: 16,
73
+ "aria-hidden": "true"
74
+ })
75
+ }) })
76
+ ]
77
+ })
78
+ });
79
+ }
80
+ //#endregion
81
+ export { Pagination };
82
+
83
+ //# sourceMappingURL=index49.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index49.mjs","names":[],"sources":["../src/ui-library/gallery/pagination/Pagination.tsx"],"sourcesContent":["/**\n * Pagination — Design System Component\n *\n * Renders previous/next navigation and page number pills.\n * Presentation-only — the consuming feature owns page state.\n *\n * Page numbers collapse intelligently with ellipsis when there are\n * more pages than can fit (> 7 pages visible at once).\n *\n * Usage:\n * <Pagination\n * page={page}\n * totalPages={totalPages}\n * onPageChange={setPage}\n * />\n */\n\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\nexport interface PaginationProps {\n /** Current page number (1-indexed) */\n page: number;\n /** Total number of pages */\n totalPages: number;\n /** Called when the user selects a different page */\n onPageChange: (page: number) => void;\n /** Accessible label for the navigation landmark (default: \"Pagination\") */\n ariaLabel?: string;\n}\n\n// ── Page number generation ─────────────────────────────────────────────────\n\nfunction getPageNumbers(current: number, total: number): (number | 'ellipsis')[] {\n if (total <= 7) {\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const pages: (number | 'ellipsis')[] = [1];\n\n if (current > 3) pages.push('ellipsis');\n\n const start = Math.max(2, current - 1);\n const end = Math.min(total - 1, current + 1);\n for (let i = start; i <= end; i++) pages.push(i);\n\n if (current < total - 2) pages.push('ellipsis');\n\n pages.push(total);\n return pages;\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function Pagination({\n page,\n totalPages,\n onPageChange,\n ariaLabel = 'Pagination',\n}: PaginationProps) {\n if (totalPages <= 1) return null;\n\n const pageNumbers = getPageNumbers(page, totalPages);\n\n return (\n <nav aria-label={ariaLabel}>\n <ol className=\"list-none flex items-center gap-1\" role=\"list\">\n {/* Previous */}\n <li>\n <button\n type=\"button\"\n onClick={() => onPageChange(page - 1)}\n disabled={page <= 1}\n aria-label=\"Previous page\"\n className=\"flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft size={16} aria-hidden=\"true\" />\n </button>\n </li>\n\n {/* Page numbers */}\n {pageNumbers.map((n, idx) =>\n n === 'ellipsis' ? (\n <li key={`ellipsis-${idx}`} aria-hidden=\"true\">\n <span className=\"flex h-9 w-9 items-center justify-center text-xs font-bold text-slate-300\">\n …\n </span>\n </li>\n ) : (\n <li key={n}>\n <button\n type=\"button\"\n onClick={() => onPageChange(n)}\n aria-label={`Page ${n}`}\n aria-current={n === page ? 'page' : undefined}\n className={[\n 'flex h-9 w-9 items-center justify-center rounded-md text-xs font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n n === page\n ? 'bg-brand-blue text-white shadow-md shadow-brand-blue/20'\n : 'border border-slate-200 bg-white text-slate-500 hover:border-slate-300 hover:text-slate-800',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {n}\n </button>\n </li>\n ),\n )}\n\n {/* Next */}\n <li>\n <button\n type=\"button\"\n onClick={() => onPageChange(page + 1)}\n disabled={page >= totalPages}\n aria-label=\"Next page\"\n className=\"flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronRight size={16} aria-hidden=\"true\" />\n </button>\n </li>\n </ol>\n </nav>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,SAAS,eAAe,SAAiB,OAAwC;CAC/E,IAAI,SAAS,GACX,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAGtD,MAAM,QAAiC,CAAC,CAAC;CAEzC,IAAI,UAAU,GAAG,MAAM,KAAK,UAAU;CAEtC,MAAM,QAAQ,KAAK,IAAI,GAAG,UAAU,CAAC;CACrC,MAAM,MAAQ,KAAK,IAAI,QAAQ,GAAG,UAAU,CAAC;CAC7C,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,KAAK,MAAM,KAAK,CAAC;CAE/C,IAAI,UAAU,QAAQ,GAAG,MAAM,KAAK,UAAU;CAE9C,MAAM,KAAK,KAAK;CAChB,OAAO;AACT;AAIA,SAAgB,WAAW,EACzB,MACA,YACA,cACA,YAAY,gBACM;CAClB,IAAI,cAAc,GAAG,OAAO;CAE5B,MAAM,cAAc,eAAe,MAAM,UAAU;CAEnD,OACE,oBAAC,OAAD;EAAK,cAAY;EACf,UAAA,qBAAC,MAAD;GAAI,WAAU;GAAoC,MAAK;GAAvD,UAAA;IAEE,oBAAC,MAAD,EAAA,UACE,oBAAC,UAAD;KACE,MAAK;KACL,eAAe,aAAa,OAAO,CAAC;KACpC,UAAU,QAAQ;KAClB,cAAW;KACX,WAAU;KAEV,UAAA,oBAAC,aAAD;MAAa,MAAM;MAAI,eAAY;KAAQ,CAAA;IACrC,CAAA,EACN,CAAA;IAGH,YAAY,KAAK,GAAG,QACnB,MAAM,aACJ,oBAAC,MAAD;KAA4B,eAAY;KACtC,UAAA,oBAAC,QAAD;MAAM,WAAU;MAA4E,UAAA;KAEtF,CAAA;IACJ,GAJK,YAAY,KAIjB,IAEJ,oBAAC,MAAD,EAAA,UACE,oBAAC,UAAD;KACE,MAAK;KACL,eAAe,aAAa,CAAC;KAC7B,cAAY,QAAQ;KACpB,gBAAc,MAAM,OAAO,SAAS,KAAA;KACpC,WAAW,CACT,qKACA,MAAM,OACF,4DACA,6FACN,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;KAEV,UAAA;IACK,CAAA,EACN,GAjBK,CAiBL,CAER;IAGA,oBAAC,MAAD,EAAA,UACE,oBAAC,UAAD;KACE,MAAK;KACL,eAAe,aAAa,OAAO,CAAC;KACpC,UAAU,QAAQ;KAClB,cAAW;KACX,WAAU;KAEV,UAAA,oBAAC,cAAD;MAAc,MAAM;MAAI,eAAY;KAAQ,CAAA;IACtC,CAAA,EACN,CAAA;GACF;;CACD,CAAA;AAET"}
package/lib/index5.cjs ADDED
@@ -0,0 +1,39 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ //#region src/ui-library/gallery/badge/Badge.tsx
3
+ var TONE_STYLES = {
4
+ neutral: "border-slate-200 bg-slate-100 text-slate-700",
5
+ primary: "border-brand-blue/20 bg-blue-50 text-brand-blue",
6
+ success: "border-emerald-200 bg-emerald-50 text-emerald-700",
7
+ warning: "border-amber-200 bg-amber-50 text-amber-700",
8
+ danger: "border-rose-200 bg-rose-50 text-rose-700",
9
+ info: "border-sky-200 bg-sky-50 text-sky-700",
10
+ subtle: "border-transparent bg-slate-100 text-slate-500",
11
+ outline: "border-slate-300 bg-transparent text-slate-700"
12
+ };
13
+ var STATUS_TONES = /* @__PURE__ */ new Set([
14
+ "success",
15
+ "warning",
16
+ "danger",
17
+ "info"
18
+ ]);
19
+ var SIZE_STYLES = {
20
+ sm: "gap-1 px-2 py-0.5 text-[10px]",
21
+ md: "gap-1.5 px-2.5 py-0.5 text-xs",
22
+ lg: "gap-1.5 px-3 py-1 text-sm"
23
+ };
24
+ function Badge({ tone = "neutral", size = "md", icon, children, className = "" }) {
25
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
26
+ className: [
27
+ "inline-flex items-center border font-semibold uppercase tracking-wide",
28
+ STATUS_TONES.has(tone) ? "rounded" : "rounded-full",
29
+ TONE_STYLES[tone],
30
+ SIZE_STYLES[size],
31
+ className
32
+ ].filter(Boolean).join(" "),
33
+ children: [icon, children]
34
+ });
35
+ }
36
+ //#endregion
37
+ exports.Badge = Badge;
38
+
39
+ //# sourceMappingURL=index5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index5.cjs","names":[],"sources":["../src/ui-library/gallery/badge/Badge.tsx"],"sourcesContent":["/**\n * Badge — Design System Component\n *\n * A small, pill-shaped label for conveying short status, category, or\n * count information. Purely presentational — no business logic.\n *\n * Tones: neutral | primary | success | warning | danger | info | subtle | outline\n * Sizes: sm | md | lg\n */\n\nimport type { ReactNode } from 'react';\n\nexport type BadgeTone =\n | 'neutral'\n | 'primary'\n | 'success'\n | 'warning'\n | 'danger'\n | 'info'\n | 'subtle'\n | 'outline';\n\nexport type BadgeSize = 'sm' | 'md' | 'lg';\n\nexport interface BadgeProps {\n tone?: BadgeTone;\n size?: BadgeSize;\n /** Leading icon node. Should be aria-hidden=\"true\". */\n icon?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\n// ── Style maps ────────────────────────────────────────────────────────────────\n\nconst TONE_STYLES: Record<BadgeTone, string> = {\n neutral: 'border-slate-200 bg-slate-100 text-slate-700',\n primary: 'border-brand-blue/20 bg-blue-50 text-brand-blue',\n success: 'border-emerald-200 bg-emerald-50 text-emerald-700',\n warning: 'border-amber-200 bg-amber-50 text-amber-700',\n danger: 'border-rose-200 bg-rose-50 text-rose-700',\n info: 'border-sky-200 bg-sky-50 text-sky-700',\n subtle: 'border-transparent bg-slate-100 text-slate-500',\n outline: 'border-slate-300 bg-transparent text-slate-700',\n};\n\n// Status tones carry formal/institutional meaning — use a tight rectangular\n// radius. Category/identity tones use a pill for visual distinction.\nconst STATUS_TONES = new Set<BadgeTone>(['success', 'warning', 'danger', 'info']);\n\nconst SIZE_STYLES: Record<BadgeSize, string> = {\n sm: 'gap-1 px-2 py-0.5 text-[10px]',\n md: 'gap-1.5 px-2.5 py-0.5 text-xs',\n lg: 'gap-1.5 px-3 py-1 text-sm',\n};\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function Badge({\n tone = 'neutral',\n size = 'md',\n icon,\n children,\n className = '',\n}: BadgeProps) {\n return (\n <span\n className={[\n 'inline-flex items-center border font-semibold uppercase tracking-wide',\n STATUS_TONES.has(tone) ? 'rounded' : 'rounded-full',\n TONE_STYLES[tone],\n SIZE_STYLES[size],\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {icon}\n {children}\n </span>\n );\n}\n"],"mappings":";;AAmCA,IAAM,cAAyC;CAC7C,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,QAAS;CACT,MAAS;CACT,QAAS;CACT,SAAS;AACX;AAIA,IAAM,+BAAe,IAAI,IAAe;CAAC;CAAW;CAAW;CAAU;AAAM,CAAC;AAEhF,IAAM,cAAyC;CAC7C,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAIA,SAAgB,MAAM,EACpB,OAAO,WACP,OAAO,MACP,MACA,UACA,YAAY,MACC;CACb,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;EACE,WAAW;GACT;GACA,aAAa,IAAI,IAAI,IAAI,YAAY;GACrC,YAAY;GACZ,YAAY;GACZ;EACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EATb,UAAA,CAWG,MACA,QACG;;AAEV"}
package/lib/index5.mjs ADDED
@@ -0,0 +1,39 @@
1
+ import { jsxs } from "react/jsx-runtime";
2
+ //#region src/ui-library/gallery/badge/Badge.tsx
3
+ var TONE_STYLES = {
4
+ neutral: "border-slate-200 bg-slate-100 text-slate-700",
5
+ primary: "border-brand-blue/20 bg-blue-50 text-brand-blue",
6
+ success: "border-emerald-200 bg-emerald-50 text-emerald-700",
7
+ warning: "border-amber-200 bg-amber-50 text-amber-700",
8
+ danger: "border-rose-200 bg-rose-50 text-rose-700",
9
+ info: "border-sky-200 bg-sky-50 text-sky-700",
10
+ subtle: "border-transparent bg-slate-100 text-slate-500",
11
+ outline: "border-slate-300 bg-transparent text-slate-700"
12
+ };
13
+ var STATUS_TONES = /* @__PURE__ */ new Set([
14
+ "success",
15
+ "warning",
16
+ "danger",
17
+ "info"
18
+ ]);
19
+ var SIZE_STYLES = {
20
+ sm: "gap-1 px-2 py-0.5 text-[10px]",
21
+ md: "gap-1.5 px-2.5 py-0.5 text-xs",
22
+ lg: "gap-1.5 px-3 py-1 text-sm"
23
+ };
24
+ function Badge({ tone = "neutral", size = "md", icon, children, className = "" }) {
25
+ return /* @__PURE__ */ jsxs("span", {
26
+ className: [
27
+ "inline-flex items-center border font-semibold uppercase tracking-wide",
28
+ STATUS_TONES.has(tone) ? "rounded" : "rounded-full",
29
+ TONE_STYLES[tone],
30
+ SIZE_STYLES[size],
31
+ className
32
+ ].filter(Boolean).join(" "),
33
+ children: [icon, children]
34
+ });
35
+ }
36
+ //#endregion
37
+ export { Badge };
38
+
39
+ //# sourceMappingURL=index5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index5.mjs","names":[],"sources":["../src/ui-library/gallery/badge/Badge.tsx"],"sourcesContent":["/**\n * Badge — Design System Component\n *\n * A small, pill-shaped label for conveying short status, category, or\n * count information. Purely presentational — no business logic.\n *\n * Tones: neutral | primary | success | warning | danger | info | subtle | outline\n * Sizes: sm | md | lg\n */\n\nimport type { ReactNode } from 'react';\n\nexport type BadgeTone =\n | 'neutral'\n | 'primary'\n | 'success'\n | 'warning'\n | 'danger'\n | 'info'\n | 'subtle'\n | 'outline';\n\nexport type BadgeSize = 'sm' | 'md' | 'lg';\n\nexport interface BadgeProps {\n tone?: BadgeTone;\n size?: BadgeSize;\n /** Leading icon node. Should be aria-hidden=\"true\". */\n icon?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\n// ── Style maps ────────────────────────────────────────────────────────────────\n\nconst TONE_STYLES: Record<BadgeTone, string> = {\n neutral: 'border-slate-200 bg-slate-100 text-slate-700',\n primary: 'border-brand-blue/20 bg-blue-50 text-brand-blue',\n success: 'border-emerald-200 bg-emerald-50 text-emerald-700',\n warning: 'border-amber-200 bg-amber-50 text-amber-700',\n danger: 'border-rose-200 bg-rose-50 text-rose-700',\n info: 'border-sky-200 bg-sky-50 text-sky-700',\n subtle: 'border-transparent bg-slate-100 text-slate-500',\n outline: 'border-slate-300 bg-transparent text-slate-700',\n};\n\n// Status tones carry formal/institutional meaning — use a tight rectangular\n// radius. Category/identity tones use a pill for visual distinction.\nconst STATUS_TONES = new Set<BadgeTone>(['success', 'warning', 'danger', 'info']);\n\nconst SIZE_STYLES: Record<BadgeSize, string> = {\n sm: 'gap-1 px-2 py-0.5 text-[10px]',\n md: 'gap-1.5 px-2.5 py-0.5 text-xs',\n lg: 'gap-1.5 px-3 py-1 text-sm',\n};\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function Badge({\n tone = 'neutral',\n size = 'md',\n icon,\n children,\n className = '',\n}: BadgeProps) {\n return (\n <span\n className={[\n 'inline-flex items-center border font-semibold uppercase tracking-wide',\n STATUS_TONES.has(tone) ? 'rounded' : 'rounded-full',\n TONE_STYLES[tone],\n SIZE_STYLES[size],\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {icon}\n {children}\n </span>\n );\n}\n"],"mappings":";;AAmCA,IAAM,cAAyC;CAC7C,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,QAAS;CACT,MAAS;CACT,QAAS;CACT,SAAS;AACX;AAIA,IAAM,+BAAe,IAAI,IAAe;CAAC;CAAW;CAAW;CAAU;AAAM,CAAC;AAEhF,IAAM,cAAyC;CAC7C,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAIA,SAAgB,MAAM,EACpB,OAAO,WACP,OAAO,MACP,MACA,UACA,YAAY,MACC;CACb,OACE,qBAAC,QAAD;EACE,WAAW;GACT;GACA,aAAa,IAAI,IAAI,IAAI,YAAY;GACrC,YAAY;GACZ,YAAY;GACZ;EACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EATb,UAAA,CAWG,MACA,QACG;;AAEV"}
@@ -0,0 +1,100 @@
1
+ let react_jsx_runtime = require("react/jsx-runtime");
2
+ let lucide_react = require("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__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {
20
+ size: 16,
21
+ strokeWidth: 3
22
+ }) : icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: number })
23
+ });
24
+ }
25
+ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [], orientation = "horizontal", className = "" }) {
26
+ const isHorizontal = orientation === "horizontal";
27
+ return /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsxs)("div", {
48
+ className: "relative flex flex-col items-center",
49
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
50
+ state,
51
+ number: idx + 1,
52
+ icon: step.icon
53
+ }), !isLast && /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsxs)("div", {
59
+ className: "flex flex-1 flex-col items-center",
60
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
61
+ className: "flex w-full items-center",
62
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
63
+ state,
64
+ number: idx + 1,
65
+ icon: step.icon
66
+ }), !isLast && /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsxs)("div", {
71
+ className: "mt-2 text-center px-1",
72
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
73
+ className: `text-sm leading-tight ${labelColor[state]}`,
74
+ children: step.label
75
+ }), step.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
76
+ className: "mt-0.5 text-xs text-slate-400 leading-tight",
77
+ children: step.description
78
+ })]
79
+ })]
80
+ }),
81
+ !isHorizontal && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
82
+ className: "min-w-0 flex-1 pt-1",
83
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
84
+ className: `text-sm leading-tight ${labelColor[state]}`,
85
+ children: step.label
86
+ }), step.description && /* @__PURE__ */ (0, react_jsx_runtime.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
+ exports.Stepper = Stepper;
99
+
100
+ //# sourceMappingURL=index50.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index50.cjs","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":"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"}