@marktiderman/genesis-ui 1.3.0 → 2.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 (280) hide show
  1. package/dist/AppShell-BJarYZzz.d.ts +198 -0
  2. package/dist/{chunk-NJMO3VPA.js → chunk-22XB3Q6H.js} +8 -8
  3. package/dist/chunk-22XB3Q6H.js.map +1 -0
  4. package/dist/{chunk-2MXMWBDV.js → chunk-27ELYTGE.js} +6 -6
  5. package/dist/chunk-27ELYTGE.js.map +1 -0
  6. package/dist/chunk-46TZGMFW.js +159 -0
  7. package/dist/chunk-46TZGMFW.js.map +1 -0
  8. package/dist/chunk-4CQHPWJD.js +117 -0
  9. package/dist/chunk-4CQHPWJD.js.map +1 -0
  10. package/dist/{chunk-ZDAT5JDH.js → chunk-65SPT7EA.js} +24 -6
  11. package/dist/chunk-65SPT7EA.js.map +1 -0
  12. package/dist/chunk-6KHY647C.js +67 -0
  13. package/dist/chunk-6KHY647C.js.map +1 -0
  14. package/dist/chunk-6M4NHHKG.js +51 -0
  15. package/dist/chunk-6M4NHHKG.js.map +1 -0
  16. package/dist/chunk-6Q6CLLJ4.js +9 -0
  17. package/dist/chunk-6Q6CLLJ4.js.map +1 -0
  18. package/dist/{chunk-UGHQFQOE.js → chunk-6TVMQH6M.js} +28 -28
  19. package/dist/chunk-6TVMQH6M.js.map +1 -0
  20. package/dist/{chunk-6VVVGYKS.js → chunk-6XPMU65K.js} +5 -5
  21. package/dist/{chunk-6VVVGYKS.js.map → chunk-6XPMU65K.js.map} +1 -1
  22. package/dist/{chunk-YZEA5TGK.js → chunk-7XMZVISM.js} +16 -16
  23. package/dist/chunk-7XMZVISM.js.map +1 -0
  24. package/dist/chunk-A7AHVDG7.js +52 -0
  25. package/dist/chunk-A7AHVDG7.js.map +1 -0
  26. package/dist/chunk-AFZ76Q26.js +84 -0
  27. package/dist/chunk-AFZ76Q26.js.map +1 -0
  28. package/dist/{chunk-4V4H2ZM7.js → chunk-AL5TMEZQ.js} +31 -31
  29. package/dist/chunk-AL5TMEZQ.js.map +1 -0
  30. package/dist/{chunk-AUECDWP2.js → chunk-AOZGQM6W.js} +3 -3
  31. package/dist/chunk-AOZGQM6W.js.map +1 -0
  32. package/dist/chunk-ARJI2JUV.js +122 -0
  33. package/dist/chunk-ARJI2JUV.js.map +1 -0
  34. package/dist/chunk-AVFZ4P4B.js +25 -0
  35. package/dist/chunk-AVFZ4P4B.js.map +1 -0
  36. package/dist/chunk-B2GIBUQD.js +11 -0
  37. package/dist/chunk-B2GIBUQD.js.map +1 -0
  38. package/dist/{chunk-JXI2MYL3.js → chunk-C46ITXE4.js} +5 -5
  39. package/dist/chunk-C46ITXE4.js.map +1 -0
  40. package/dist/{chunk-A2OXCNNY.js → chunk-CNTWQSUT.js} +3 -3
  41. package/dist/{chunk-A2OXCNNY.js.map → chunk-CNTWQSUT.js.map} +1 -1
  42. package/dist/chunk-D2LIXA2Y.js +65 -0
  43. package/dist/chunk-D2LIXA2Y.js.map +1 -0
  44. package/dist/{chunk-T5VX5DYO.js → chunk-F47U5KNW.js} +5 -5
  45. package/dist/chunk-F47U5KNW.js.map +1 -0
  46. package/dist/chunk-FEJAZFP3.js +1100 -0
  47. package/dist/chunk-FEJAZFP3.js.map +1 -0
  48. package/dist/{chunk-2B3MFHBQ.js → chunk-FK42ZGGV.js} +21 -21
  49. package/dist/chunk-FK42ZGGV.js.map +1 -0
  50. package/dist/{chunk-XISSLIMH.js → chunk-FTTNGGLA.js} +2 -2
  51. package/dist/{chunk-XISSLIMH.js.map → chunk-FTTNGGLA.js.map} +1 -1
  52. package/dist/chunk-G5YKBPBF.js +74 -0
  53. package/dist/chunk-G5YKBPBF.js.map +1 -0
  54. package/dist/{chunk-KB5T5LWM.js → chunk-IJCT5IJD.js} +3 -3
  55. package/dist/{chunk-KB5T5LWM.js.map → chunk-IJCT5IJD.js.map} +1 -1
  56. package/dist/{chunk-44TULYGG.js → chunk-J6FBA7PH.js} +3 -3
  57. package/dist/{chunk-44TULYGG.js.map → chunk-J6FBA7PH.js.map} +1 -1
  58. package/dist/chunk-JAMLFMDS.js +12 -0
  59. package/dist/chunk-JAMLFMDS.js.map +1 -0
  60. package/dist/{chunk-C6H7AMO4.js → chunk-LNFF2RBD.js} +9 -9
  61. package/dist/chunk-LNFF2RBD.js.map +1 -0
  62. package/dist/{chunk-QBZMY2AF.js → chunk-M62CHO3N.js} +18 -18
  63. package/dist/chunk-M62CHO3N.js.map +1 -0
  64. package/dist/{chunk-5Q4FABZQ.js → chunk-NUXW6NPP.js} +8 -8
  65. package/dist/chunk-NUXW6NPP.js.map +1 -0
  66. package/dist/{chunk-ZJFSZFPI.js → chunk-NWSCYLPT.js} +77 -18
  67. package/dist/chunk-NWSCYLPT.js.map +1 -0
  68. package/dist/{chunk-63FW5ZUA.js → chunk-P3ZL6FGR.js} +7 -7
  69. package/dist/chunk-P3ZL6FGR.js.map +1 -0
  70. package/dist/chunk-P64MFQXZ.js +191 -0
  71. package/dist/chunk-P64MFQXZ.js.map +1 -0
  72. package/dist/chunk-PLIYF6KI.js +52 -0
  73. package/dist/chunk-PLIYF6KI.js.map +1 -0
  74. package/dist/chunk-PSR47JRA.js +26 -0
  75. package/dist/chunk-PSR47JRA.js.map +1 -0
  76. package/dist/{chunk-QLCDAFS7.js → chunk-Q4I4SFBD.js} +3 -3
  77. package/dist/chunk-Q4I4SFBD.js.map +1 -0
  78. package/dist/{chunk-SBJV3K4G.js → chunk-QJ6P2DW6.js} +28 -28
  79. package/dist/chunk-QJ6P2DW6.js.map +1 -0
  80. package/dist/{chunk-IP6TMT3D.js → chunk-R7QFP6EL.js} +3 -3
  81. package/dist/chunk-R7QFP6EL.js.map +1 -0
  82. package/dist/{chunk-WZWR2ELB.js → chunk-SEBU6JCK.js} +6 -6
  83. package/dist/chunk-SEBU6JCK.js.map +1 -0
  84. package/dist/{chunk-7OBL3H5P.js → chunk-SEFK4NZV.js} +3 -3
  85. package/dist/chunk-SEFK4NZV.js.map +1 -0
  86. package/dist/chunk-SRIT4NFQ.js +67 -0
  87. package/dist/chunk-SRIT4NFQ.js.map +1 -0
  88. package/dist/chunk-SY62XJFA.js +19 -0
  89. package/dist/chunk-SY62XJFA.js.map +1 -0
  90. package/dist/{chunk-SVXDWPLO.js → chunk-THD3YOOZ.js} +17 -17
  91. package/dist/chunk-THD3YOOZ.js.map +1 -0
  92. package/dist/chunk-VXFYETRK.js +58 -0
  93. package/dist/chunk-VXFYETRK.js.map +1 -0
  94. package/dist/chunk-XBX6QMR5.js +58 -0
  95. package/dist/chunk-XBX6QMR5.js.map +1 -0
  96. package/dist/{chunk-SCUT2ZCD.js → chunk-XCGZPFT7.js} +17 -17
  97. package/dist/chunk-XCGZPFT7.js.map +1 -0
  98. package/dist/chunk-XMK3T4TA.js +73 -0
  99. package/dist/chunk-XMK3T4TA.js.map +1 -0
  100. package/dist/chunk-YJ6MPS6U.js +226 -0
  101. package/dist/chunk-YJ6MPS6U.js.map +1 -0
  102. package/dist/chunk-YRYQXU3G.js +38 -0
  103. package/dist/chunk-YRYQXU3G.js.map +1 -0
  104. package/dist/{chunk-5J7BP5DI.js → chunk-YWFPMND7.js} +10 -10
  105. package/dist/chunk-YWFPMND7.js.map +1 -0
  106. package/dist/chunk-YYEGAFUE.js +94 -0
  107. package/dist/chunk-YYEGAFUE.js.map +1 -0
  108. package/dist/{chunk-TKA4LOMT.js → chunk-ZZ7H7VVX.js} +8 -8
  109. package/dist/chunk-ZZ7H7VVX.js.map +1 -0
  110. package/dist/components/data/index.d.ts +194 -211
  111. package/dist/components/data/index.js +362 -1381
  112. package/dist/components/data/index.js.map +1 -1
  113. package/dist/components/layout/dashboard-page.d.ts +4 -0
  114. package/dist/components/layout/dashboard-page.js +5 -0
  115. package/dist/components/layout/dashboard-page.js.map +1 -0
  116. package/dist/components/layout/detail-page.d.ts +91 -0
  117. package/dist/components/layout/detail-page.js +6 -0
  118. package/dist/components/layout/detail-page.js.map +1 -0
  119. package/dist/components/layout/form-page.d.ts +82 -0
  120. package/dist/components/layout/form-page.js +5 -0
  121. package/dist/components/layout/form-page.js.map +1 -0
  122. package/dist/components/layout/index.d.ts +15 -0
  123. package/dist/components/layout/index.js +21 -0
  124. package/dist/components/layout/index.js.map +1 -0
  125. package/dist/components/layout/settings-page.d.ts +114 -0
  126. package/dist/components/layout/settings-page.js +6 -0
  127. package/dist/components/layout/settings-page.js.map +1 -0
  128. package/dist/components/patterns/empty-state.d.ts +74 -0
  129. package/dist/components/{ui → patterns}/empty-state.js +2 -1
  130. package/dist/components/{ui → patterns}/form-field.d.ts +2 -2
  131. package/dist/components/{ui → patterns}/form-field.js +2 -2
  132. package/dist/components/patterns/index.d.ts +18 -0
  133. package/dist/components/patterns/index.js +18 -0
  134. package/dist/components/patterns/index.js.map +1 -0
  135. package/dist/components/patterns/page-loading.d.ts +25 -0
  136. package/dist/components/patterns/page-loading.js +5 -0
  137. package/dist/components/patterns/page-loading.js.map +1 -0
  138. package/dist/components/patterns/settings-row.d.ts +42 -0
  139. package/dist/components/patterns/settings-row.js +6 -0
  140. package/dist/components/patterns/settings-row.js.map +1 -0
  141. package/dist/components/patterns/status-badge.d.ts +38 -0
  142. package/dist/components/patterns/status-badge.js +4 -0
  143. package/dist/components/patterns/status-badge.js.map +1 -0
  144. package/dist/components/patterns/toggle-row.d.ts +36 -0
  145. package/dist/components/patterns/toggle-row.js +8 -0
  146. package/dist/components/patterns/toggle-row.js.map +1 -0
  147. package/dist/components/patterns/user-avatar.d.ts +34 -0
  148. package/dist/components/patterns/user-avatar.js +5 -0
  149. package/dist/components/patterns/user-avatar.js.map +1 -0
  150. package/dist/components/ui/accessible-icon.d.ts +48 -0
  151. package/dist/components/ui/accessible-icon.js +3 -0
  152. package/dist/components/ui/accessible-icon.js.map +1 -0
  153. package/dist/components/ui/accordion.d.ts +5 -5
  154. package/dist/components/ui/accordion.js +1 -1
  155. package/dist/components/ui/alert-dialog.d.ts +10 -10
  156. package/dist/components/ui/alert-dialog.js +1 -1
  157. package/dist/components/ui/aspect-ratio.d.ts +2 -2
  158. package/dist/components/ui/aspect-ratio.js +1 -1
  159. package/dist/components/ui/avatar.d.ts +4 -4
  160. package/dist/components/ui/avatar.js +1 -1
  161. package/dist/components/ui/button.d.ts +73 -8
  162. package/dist/components/ui/button.js +1 -1
  163. package/dist/components/ui/calendar.js +2 -2
  164. package/dist/components/ui/carousel.d.ts +4 -3
  165. package/dist/components/ui/carousel.js +2 -2
  166. package/dist/components/ui/checkbox.d.ts +2 -2
  167. package/dist/components/ui/checkbox.js +1 -1
  168. package/dist/components/ui/collapsible.d.ts +4 -4
  169. package/dist/components/ui/collapsible.js +1 -1
  170. package/dist/components/ui/command-palette.js +3 -3
  171. package/dist/components/ui/command.d.ts +9 -9
  172. package/dist/components/ui/command.js +2 -2
  173. package/dist/components/ui/context-menu.d.ts +15 -15
  174. package/dist/components/ui/context-menu.js +1 -1
  175. package/dist/components/ui/date-picker.js +4 -4
  176. package/dist/components/ui/dialog.d.ts +9 -9
  177. package/dist/components/ui/dialog.js +1 -1
  178. package/dist/components/ui/drawer.d.ts +7 -7
  179. package/dist/components/ui/dropdown-menu.d.ts +15 -15
  180. package/dist/components/ui/dropdown-menu.js +1 -1
  181. package/dist/components/ui/hover-card.d.ts +4 -4
  182. package/dist/components/ui/hover-card.js +1 -1
  183. package/dist/components/ui/input-otp.d.ts +2 -2
  184. package/dist/components/ui/label.d.ts +2 -2
  185. package/dist/components/ui/label.js +1 -1
  186. package/dist/components/ui/menubar.d.ts +16 -16
  187. package/dist/components/ui/menubar.js +1 -1
  188. package/dist/components/ui/navigation-menu.d.ts +9 -9
  189. package/dist/components/ui/navigation-menu.js +1 -1
  190. package/dist/components/ui/pagination.js +2 -2
  191. package/dist/components/ui/password-toggle-field.d.ts +121 -0
  192. package/dist/components/ui/password-toggle-field.js +4 -0
  193. package/dist/components/ui/password-toggle-field.js.map +1 -0
  194. package/dist/components/ui/popover.d.ts +4 -4
  195. package/dist/components/ui/popover.js +1 -1
  196. package/dist/components/ui/radio-group.d.ts +3 -3
  197. package/dist/components/ui/radio-group.js +1 -1
  198. package/dist/components/ui/resizable.d.ts +1 -1
  199. package/dist/components/ui/scroll-area.d.ts +60 -3
  200. package/dist/components/ui/scroll-area.js +1 -1
  201. package/dist/components/ui/select.d.ts +9 -9
  202. package/dist/components/ui/select.js +1 -1
  203. package/dist/components/ui/sheet.d.ts +11 -9
  204. package/dist/components/ui/sheet.js +1 -1
  205. package/dist/components/ui/slider.d.ts +2 -2
  206. package/dist/components/ui/slider.js +1 -1
  207. package/dist/components/ui/switch.d.ts +2 -2
  208. package/dist/components/ui/switch.js +1 -1
  209. package/dist/components/ui/tabs.d.ts +29 -14
  210. package/dist/components/ui/tabs.js +1 -1
  211. package/dist/components/ui/toggle-group.d.ts +3 -3
  212. package/dist/components/ui/toggle-group.js +2 -2
  213. package/dist/components/ui/toggle.d.ts +2 -2
  214. package/dist/components/ui/toggle.js +1 -1
  215. package/dist/components/ui/toolbar.d.ts +104 -0
  216. package/dist/components/ui/toolbar.js +6 -0
  217. package/dist/components/ui/toolbar.js.map +1 -0
  218. package/dist/components/ui/tooltip.d.ts +46 -4
  219. package/dist/components/ui/tooltip.js +1 -1
  220. package/dist/components/ui/visually-hidden.d.ts +48 -0
  221. package/dist/components/ui/visually-hidden.js +4 -0
  222. package/dist/components/ui/visually-hidden.js.map +1 -0
  223. package/dist/dashboard-page-C0TihRl5.d.ts +107 -0
  224. package/dist/hooks/index.d.ts +2 -2
  225. package/dist/hooks/index.js +6 -6
  226. package/dist/hooks/use-keyboard-navigation.d.ts +58 -2
  227. package/dist/hooks/use-keyboard-navigation.js +1 -1
  228. package/dist/hooks/use-resource-page.d.ts +2 -2
  229. package/dist/hooks/use-resource-page.js +2 -2
  230. package/dist/hooks/use-view-preference.d.ts +1 -1
  231. package/dist/hooks/use-view-preference.js +1 -1
  232. package/dist/index.d.ts +23 -25
  233. package/dist/index.js +54 -38
  234. package/dist/spacing-BS6qECD4.d.ts +191 -0
  235. package/dist/split-Dk9f4Mi0.d.ts +57 -0
  236. package/dist/{use-resource-page-BXBv_Xah.d.ts → use-resource-page-CPs6wjKt.d.ts} +1 -1
  237. package/dist/view-settings--KXNT_jE.d.ts +12 -0
  238. package/package.json +73 -32
  239. package/dist/chunk-2B3MFHBQ.js.map +0 -1
  240. package/dist/chunk-2MXMWBDV.js.map +0 -1
  241. package/dist/chunk-4V4H2ZM7.js.map +0 -1
  242. package/dist/chunk-5J7BP5DI.js.map +0 -1
  243. package/dist/chunk-5Q4FABZQ.js.map +0 -1
  244. package/dist/chunk-63FW5ZUA.js.map +0 -1
  245. package/dist/chunk-7OBL3H5P.js.map +0 -1
  246. package/dist/chunk-AUECDWP2.js.map +0 -1
  247. package/dist/chunk-C6H7AMO4.js.map +0 -1
  248. package/dist/chunk-EI5EGKHL.js +0 -32
  249. package/dist/chunk-EI5EGKHL.js.map +0 -1
  250. package/dist/chunk-IP6TMT3D.js.map +0 -1
  251. package/dist/chunk-JJ4NOMMI.js +0 -73
  252. package/dist/chunk-JJ4NOMMI.js.map +0 -1
  253. package/dist/chunk-JXI2MYL3.js.map +0 -1
  254. package/dist/chunk-KBS4BHME.js +0 -11
  255. package/dist/chunk-KBS4BHME.js.map +0 -1
  256. package/dist/chunk-LRXDZBV2.js +0 -26
  257. package/dist/chunk-LRXDZBV2.js.map +0 -1
  258. package/dist/chunk-NJMO3VPA.js.map +0 -1
  259. package/dist/chunk-OCTV3S4I.js +0 -29
  260. package/dist/chunk-OCTV3S4I.js.map +0 -1
  261. package/dist/chunk-QBZMY2AF.js.map +0 -1
  262. package/dist/chunk-QLCDAFS7.js.map +0 -1
  263. package/dist/chunk-SBJV3K4G.js.map +0 -1
  264. package/dist/chunk-SCUT2ZCD.js.map +0 -1
  265. package/dist/chunk-SVXDWPLO.js.map +0 -1
  266. package/dist/chunk-T465NJRL.js +0 -134
  267. package/dist/chunk-T465NJRL.js.map +0 -1
  268. package/dist/chunk-T5VX5DYO.js.map +0 -1
  269. package/dist/chunk-TKA4LOMT.js.map +0 -1
  270. package/dist/chunk-UGHQFQOE.js.map +0 -1
  271. package/dist/chunk-WALXVR6V.js +0 -9
  272. package/dist/chunk-WALXVR6V.js.map +0 -1
  273. package/dist/chunk-WZWR2ELB.js.map +0 -1
  274. package/dist/chunk-YZEA5TGK.js.map +0 -1
  275. package/dist/chunk-ZDAT5JDH.js.map +0 -1
  276. package/dist/chunk-ZJFSZFPI.js.map +0 -1
  277. package/dist/components/ui/empty-state.d.ts +0 -22
  278. /package/dist/components/{ui → patterns}/empty-state.js.map +0 -0
  279. /package/dist/components/{ui → patterns}/form-field.js.map +0 -0
  280. /package/dist/{ViewToggle-DDe_Luj2.d.ts → view-toggle-DDe_Luj2.d.ts} +0 -0
@@ -0,0 +1,1100 @@
1
+ "use client";
2
+ import { ViewToggle, ViewSettings } from './chunk-46TZGMFW.js';
3
+ import { Collapsible, CollapsibleContent } from './chunk-B2GIBUQD.js';
4
+ import { Badge } from './chunk-NPNADZHM.js';
5
+ import { Popover, PopoverTrigger, PopoverContent } from './chunk-22XB3Q6H.js';
6
+ import { Switch } from './chunk-27ELYTGE.js';
7
+ import { Button } from './chunk-NWSCYLPT.js';
8
+ import { Label } from './chunk-F47U5KNW.js';
9
+ import { cn } from './chunk-NZ5WBA4R.js';
10
+ import { DefaultLink, isPathActive } from './chunk-6NPEMLBA.js';
11
+ import { useId, useState, useEffect, useMemo, useCallback } from 'react';
12
+ import { ListFilter, SlidersHorizontal, Menu, X, PanelLeft, PanelLeftClose, Sun, Moon, ChevronDown } from 'lucide-react';
13
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
14
+
15
+ // src/option-storage.ts
16
+ function readStoredOptions(key) {
17
+ if (typeof window === "undefined") return {};
18
+ try {
19
+ const raw = localStorage.getItem(key);
20
+ if (!raw) return {};
21
+ const parsed = JSON.parse(raw);
22
+ if (!parsed || typeof parsed !== "object") return {};
23
+ return parsed;
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+ function writeStoredOptions(key, value) {
29
+ if (typeof window === "undefined") return;
30
+ try {
31
+ localStorage.setItem(key, JSON.stringify(value));
32
+ } catch {
33
+ }
34
+ }
35
+ function pluralizeEntity(entity, count) {
36
+ if (count === 1) return entity;
37
+ return entity.endsWith("s") ? entity : `${entity}s`;
38
+ }
39
+ var OPTION_DEFAULTS = {
40
+ showIcon: true,
41
+ showSubtitle: true,
42
+ showCount: true,
43
+ showViewToggle: true,
44
+ showViewSettings: true,
45
+ showCreate: true,
46
+ showDetailsToggle: true,
47
+ showDetailsHint: true,
48
+ subtitleInDetails: void 0,
49
+ defaultDetailsOpen: false,
50
+ detailsLabel: "Details",
51
+ configurable: void 0
52
+ };
53
+ var BOOLEAN_OPTION_KEYS = [
54
+ "showIcon",
55
+ "showSubtitle",
56
+ "showCount",
57
+ "showViewToggle",
58
+ "showViewSettings",
59
+ "showCreate",
60
+ "showDetailsToggle",
61
+ "showDetailsHint",
62
+ "subtitleInDetails",
63
+ "defaultDetailsOpen",
64
+ "configurable"
65
+ ];
66
+ var STRING_OPTION_KEYS = ["detailsLabel"];
67
+ function readStoredOptions2(key) {
68
+ const raw = readStoredOptions(key);
69
+ const sanitized = {};
70
+ for (const optKey of BOOLEAN_OPTION_KEYS) {
71
+ const value = raw[optKey];
72
+ if (typeof value === "boolean") {
73
+ sanitized[optKey] = value;
74
+ }
75
+ }
76
+ for (const optKey of STRING_OPTION_KEYS) {
77
+ const value = raw[optKey];
78
+ if (typeof value === "string") {
79
+ sanitized[optKey] = value;
80
+ }
81
+ }
82
+ return sanitized;
83
+ }
84
+ function PageHeader({
85
+ title,
86
+ subtitle,
87
+ icon: Icon,
88
+ options: optionsProp,
89
+ optionsStorageKey,
90
+ actions,
91
+ details,
92
+ detailsOpen,
93
+ onDetailsOpenChange,
94
+ detailsActive = false,
95
+ detailsHint,
96
+ count,
97
+ totalCount,
98
+ entityName,
99
+ countActive = false,
100
+ viewMode,
101
+ onViewModeChange,
102
+ pageSize,
103
+ onPageSizeChange,
104
+ density,
105
+ onDensityChange,
106
+ createAction
107
+ }) {
108
+ const detailsRegionId = useId();
109
+ const hasDetails = details != null;
110
+ const hasCount = count !== void 0 && totalCount !== void 0;
111
+ const hasViewToggle = Boolean(viewMode && onViewModeChange);
112
+ const hasViewSettings = Boolean(onPageSizeChange && onDensityChange);
113
+ const hasCreate = createAction != null;
114
+ const [userOverrides, setUserOverrides] = useState(
115
+ {}
116
+ );
117
+ const [hydratedKey, setHydratedKey] = useState(
118
+ optionsStorageKey ? void 0 : ""
119
+ );
120
+ useEffect(() => {
121
+ if (!optionsStorageKey) {
122
+ setUserOverrides({});
123
+ setHydratedKey("");
124
+ return;
125
+ }
126
+ setUserOverrides(readStoredOptions2(optionsStorageKey));
127
+ setHydratedKey(optionsStorageKey);
128
+ }, [optionsStorageKey]);
129
+ useEffect(() => {
130
+ if (!optionsStorageKey || hydratedKey !== optionsStorageKey) return;
131
+ writeStoredOptions(optionsStorageKey, userOverrides);
132
+ }, [optionsStorageKey, userOverrides, hydratedKey]);
133
+ const resolved = useMemo(() => {
134
+ const merged = {
135
+ ...OPTION_DEFAULTS,
136
+ ...optionsProp,
137
+ ...userOverrides
138
+ };
139
+ if (merged.subtitleInDetails === void 0) {
140
+ merged.subtitleInDetails = hasDetails;
141
+ }
142
+ if (merged.configurable === void 0) {
143
+ merged.configurable = hasDetails || hasCount || hasViewSettings || hasViewToggle;
144
+ }
145
+ return merged;
146
+ }, [
147
+ optionsProp,
148
+ userOverrides,
149
+ hasDetails,
150
+ hasCount,
151
+ hasViewSettings,
152
+ hasViewToggle
153
+ ]);
154
+ const patchOption = useCallback((key, value) => {
155
+ setUserOverrides((prev) => ({ ...prev, [key]: value }));
156
+ }, []);
157
+ const moveSubtitle = Boolean(resolved.subtitleInDetails) && hasDetails;
158
+ const showTitleSubtitle = Boolean(subtitle) && Boolean(resolved.showSubtitle) && !moveSubtitle;
159
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(
160
+ () => optionsProp?.defaultDetailsOpen ?? OPTION_DEFAULTS.defaultDetailsOpen
161
+ );
162
+ const isControlled = detailsOpen !== void 0;
163
+ const open = isControlled ? detailsOpen : uncontrolledOpen;
164
+ const setOpen = (next) => {
165
+ if (!isControlled) setUncontrolledOpen(next);
166
+ onDetailsOpenChange?.(next);
167
+ };
168
+ const detailsLabel = resolved.detailsLabel ?? "Details";
169
+ const entity = entityName || "item";
170
+ const toggles = [
171
+ { key: "showIcon", label: "Icon", available: Boolean(Icon) },
172
+ {
173
+ key: "showSubtitle",
174
+ label: "Subtitle",
175
+ available: Boolean(subtitle) && !moveSubtitle
176
+ },
177
+ { key: "showCount", label: "Count", available: hasCount },
178
+ { key: "showViewToggle", label: "View toggle", available: hasViewToggle },
179
+ {
180
+ key: "showViewSettings",
181
+ label: "Display settings",
182
+ available: hasViewSettings
183
+ },
184
+ { key: "showCreate", label: "Create action", available: hasCreate },
185
+ {
186
+ key: "showDetailsToggle",
187
+ label: "Filters / details",
188
+ available: hasDetails
189
+ },
190
+ {
191
+ key: "showDetailsHint",
192
+ label: "Collapsed hint",
193
+ available: hasDetails && detailsHint != null
194
+ }
195
+ ].filter((t) => t.available);
196
+ const showDetailsToggle = hasDetails && Boolean(resolved.showDetailsToggle);
197
+ const showHint = hasDetails && !open && Boolean(resolved.showDetailsHint) && detailsHint != null;
198
+ const actionsCluster = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 shrink-0", children: [
199
+ showHint ? /* @__PURE__ */ jsx("span", { className: "hidden max-w-[14rem] truncate text-xs text-muted-foreground sm:inline", children: detailsHint }) : null,
200
+ hasCount && resolved.showCount !== false ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: countActive ? `${count} of ${totalCount}` : `${totalCount} ${pluralizeEntity(entity, totalCount)}` }) : null,
201
+ hasViewToggle && resolved.showViewToggle !== false ? /* @__PURE__ */ jsx(ViewToggle, { viewMode, onViewModeChange }) : null,
202
+ showDetailsToggle ? /* @__PURE__ */ jsxs(
203
+ Button,
204
+ {
205
+ type: "button",
206
+ variant: "ghost",
207
+ size: "icon",
208
+ className: cn(
209
+ "relative h-8 w-8",
210
+ (open || detailsActive) && "bg-accent text-accent-foreground"
211
+ ),
212
+ onClick: () => setOpen(!open),
213
+ "data-testid": "page-header-details-toggle",
214
+ title: open ? `Hide ${detailsLabel}` : `Show ${detailsLabel}`,
215
+ "aria-label": open ? `Hide ${detailsLabel}` : `Show ${detailsLabel}`,
216
+ "aria-expanded": open,
217
+ "aria-controls": detailsRegionId,
218
+ testID: "page-header-details-toggle",
219
+ children: [
220
+ /* @__PURE__ */ jsx(ListFilter, { className: "h-4 w-4" }),
221
+ detailsActive ? /* @__PURE__ */ jsx(
222
+ "span",
223
+ {
224
+ className: "absolute right-1.5 top-1.5 size-1.5 rounded-full bg-primary",
225
+ "aria-hidden": true
226
+ }
227
+ ) : null
228
+ ]
229
+ }
230
+ ) : null,
231
+ hasViewSettings && resolved.showViewSettings !== false ? /* @__PURE__ */ jsx(
232
+ ViewSettings,
233
+ {
234
+ pageSize: pageSize ?? 25,
235
+ onPageSizeChange,
236
+ density: density ?? "comfortable",
237
+ onDensityChange
238
+ }
239
+ ) : null,
240
+ hasCreate && resolved.showCreate !== false ? createAction : null,
241
+ actions,
242
+ resolved.configurable && toggles.length > 0 ? /* @__PURE__ */ jsxs(Popover, { children: [
243
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
244
+ Button,
245
+ {
246
+ type: "button",
247
+ variant: "ghost",
248
+ size: "icon",
249
+ className: "h-8 w-8",
250
+ title: "Header options",
251
+ "aria-label": "Header options",
252
+ "data-testid": "page-header-options",
253
+ testID: "page-header-options",
254
+ children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-4 w-4" })
255
+ }
256
+ ) }),
257
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-64 space-y-3", children: [
258
+ /* @__PURE__ */ jsxs("div", { children: [
259
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Header options" }),
260
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Customize this page header. Saved on this device when a storage key is set." })
261
+ ] }),
262
+ /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: toggles.map((t) => {
263
+ const checked = Boolean(resolved[t.key] ?? true);
264
+ return /* @__PURE__ */ jsxs(
265
+ "li",
266
+ {
267
+ className: "flex items-center justify-between gap-3",
268
+ children: [
269
+ /* @__PURE__ */ jsx(
270
+ Label,
271
+ {
272
+ htmlFor: `page-header-opt-${t.key}`,
273
+ className: "text-sm font-normal",
274
+ children: t.label
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsx(
278
+ Switch,
279
+ {
280
+ id: `page-header-opt-${t.key}`,
281
+ checked,
282
+ onCheckedChange: (v) => patchOption(t.key, v)
283
+ }
284
+ )
285
+ ]
286
+ },
287
+ t.key
288
+ );
289
+ }) }),
290
+ optionsStorageKey && Object.keys(userOverrides).length > 0 ? /* @__PURE__ */ jsx(
291
+ Button,
292
+ {
293
+ type: "button",
294
+ variant: "ghost",
295
+ size: "sm",
296
+ className: "h-8 w-full text-xs",
297
+ onClick: () => setUserOverrides({}),
298
+ "data-testid": "page-header-options-reset",
299
+ "aria-label": "Reset header options",
300
+ testID: "page-header-options-reset",
301
+ children: "Reset to defaults"
302
+ }
303
+ ) : null
304
+ ] })
305
+ ] }) : null
306
+ ] });
307
+ const titleRow = /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
308
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
309
+ Icon && resolved.showIcon !== false ? /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-primary/10", children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 text-primary" }) }) : null,
310
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
311
+ /* @__PURE__ */ jsx(
312
+ "h1",
313
+ {
314
+ "data-testid": "page-title",
315
+ className: "truncate font-display text-xl font-bold tracking-tight sm:text-2xl",
316
+ children: title
317
+ }
318
+ ),
319
+ showTitleSubtitle ? /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-muted-foreground", children: subtitle }) : null
320
+ ] })
321
+ ] }),
322
+ actionsCluster
323
+ ] });
324
+ if (!hasDetails) {
325
+ return titleRow;
326
+ }
327
+ return /* @__PURE__ */ jsxs(Collapsible, { open, onOpenChange: setOpen, children: [
328
+ titleRow,
329
+ /* @__PURE__ */ jsx(CollapsibleContent, { id: detailsRegionId, className: "mt-3", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-lg border border-border bg-card/40 p-3 sm:p-4", children: [
330
+ moveSubtitle && subtitle && resolved.showSubtitle !== false ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle }) : null,
331
+ details
332
+ ] }) })
333
+ ] });
334
+ }
335
+ var OPTION_DEFAULTS2 = {
336
+ showLogo: true,
337
+ showAppName: true,
338
+ showThemeToggle: true,
339
+ showCollapse: true,
340
+ showUserSlot: true,
341
+ showFooter: true,
342
+ showSecondaryNav: true,
343
+ showMobileBottomNav: true,
344
+ defaultCollapsed: false,
345
+ configurable: void 0,
346
+ density: "comfortable",
347
+ railWidth: "narrow",
348
+ scrollRoot: "container"
349
+ };
350
+ function readStoredCollapsed(key) {
351
+ if (typeof window === "undefined") return void 0;
352
+ try {
353
+ const raw = localStorage.getItem(`${key}:collapsed`);
354
+ if (raw === "1") return true;
355
+ if (raw === "0") return false;
356
+ return void 0;
357
+ } catch {
358
+ return void 0;
359
+ }
360
+ }
361
+ function writeStoredCollapsed(key, collapsed) {
362
+ if (typeof window === "undefined") return;
363
+ try {
364
+ localStorage.setItem(`${key}:collapsed`, collapsed ? "1" : "0");
365
+ } catch {
366
+ }
367
+ }
368
+ function ThemeToggle({ testId }) {
369
+ const [dark, setDark] = useState(false);
370
+ useEffect(() => {
371
+ const stored = localStorage.getItem("theme");
372
+ const prefersDark = window.matchMedia(
373
+ "(prefers-color-scheme: dark)"
374
+ ).matches;
375
+ const isDark = stored === "dark" || !stored && prefersDark;
376
+ setDark(isDark);
377
+ document.documentElement.classList.toggle("dark", isDark);
378
+ }, []);
379
+ function toggle() {
380
+ const next = !dark;
381
+ setDark(next);
382
+ document.documentElement.classList.toggle("dark", next);
383
+ localStorage.setItem("theme", next ? "dark" : "light");
384
+ }
385
+ return /* @__PURE__ */ jsx(
386
+ Button,
387
+ {
388
+ variant: "ghost",
389
+ size: "icon",
390
+ onClick: toggle,
391
+ className: "h-8 w-8",
392
+ title: "Toggle theme",
393
+ "aria-label": "Toggle theme",
394
+ "data-testid": testId,
395
+ testID: testId,
396
+ children: dark ? /* @__PURE__ */ jsx(Sun, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Moon, { className: "h-4 w-4" })
397
+ }
398
+ );
399
+ }
400
+ function itemIsActive(item, activePath, isActive) {
401
+ if (item.end) {
402
+ return Boolean(activePath && activePath === item.to);
403
+ }
404
+ if (isActive(item.to, activePath)) return true;
405
+ return Boolean(
406
+ item.children?.some((child) => itemIsActive(child, activePath, isActive))
407
+ );
408
+ }
409
+ function NavLinkRow({
410
+ item,
411
+ collapsed,
412
+ density,
413
+ onNavClick,
414
+ linkComponent: LinkComp,
415
+ activePath,
416
+ isActive
417
+ }) {
418
+ const active = itemIsActive(item, activePath, isActive);
419
+ const pad = density === "compact" ? "px-2.5 py-1.5" : "px-3 py-2";
420
+ if (item.disabled) {
421
+ return /* @__PURE__ */ jsxs(
422
+ "span",
423
+ {
424
+ title: item.label,
425
+ className: cn(
426
+ "flex items-center gap-3 rounded-lg text-sm font-medium opacity-40",
427
+ pad,
428
+ collapsed && "justify-center px-2"
429
+ ),
430
+ "aria-disabled": true,
431
+ children: [
432
+ /* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
433
+ !collapsed ? /* @__PURE__ */ jsx("span", { className: "truncate", children: item.label }) : null
434
+ ]
435
+ }
436
+ );
437
+ }
438
+ return /* @__PURE__ */ jsxs(
439
+ LinkComp,
440
+ {
441
+ href: item.to,
442
+ onClick: onNavClick,
443
+ title: collapsed ? item.label : void 0,
444
+ "aria-label": collapsed ? item.label : void 0,
445
+ "aria-current": active ? "page" : void 0,
446
+ className: cn(
447
+ "flex items-center gap-3 rounded-lg text-sm font-medium transition-colors",
448
+ pad,
449
+ collapsed && "justify-center px-2",
450
+ active ? "bg-sidebar-accent text-sidebar-primary" : "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
451
+ ),
452
+ children: [
453
+ /* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
454
+ !collapsed ? /* @__PURE__ */ jsxs(Fragment, { children: [
455
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
456
+ item.badge != null ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "h-5 shrink-0 px-1.5 text-[10px]", children: item.badge }) : null
457
+ ] }) : null
458
+ ]
459
+ }
460
+ );
461
+ }
462
+ function NavGroup({
463
+ item,
464
+ collapsed,
465
+ density,
466
+ onNavClick,
467
+ linkComponent,
468
+ activePath,
469
+ isActive
470
+ }) {
471
+ const children = item.children ?? [];
472
+ const childActive = children.some(
473
+ (c) => itemIsActive(c, activePath, isActive)
474
+ );
475
+ const [open, setOpen] = useState(childActive);
476
+ useEffect(() => {
477
+ if (childActive) setOpen(true);
478
+ }, [childActive]);
479
+ if (children.length === 0) {
480
+ return /* @__PURE__ */ jsx(
481
+ NavLinkRow,
482
+ {
483
+ item,
484
+ collapsed,
485
+ density,
486
+ onNavClick,
487
+ linkComponent,
488
+ activePath,
489
+ isActive
490
+ }
491
+ );
492
+ }
493
+ if (collapsed) {
494
+ const target = item.to ? { ...item, children: void 0 } : { ...children[0], children: void 0 };
495
+ return /* @__PURE__ */ jsx(
496
+ NavLinkRow,
497
+ {
498
+ item: target,
499
+ collapsed: true,
500
+ density,
501
+ onNavClick,
502
+ linkComponent,
503
+ activePath,
504
+ isActive
505
+ }
506
+ );
507
+ }
508
+ const selfExact = Boolean(item.to && activePath && activePath === item.to);
509
+ const selfActive = item.to ? item.end ? selfExact : isActive(item.to, activePath) : false;
510
+ const groupHighlight = selfActive || childActive;
511
+ const pad = density === "compact" ? "px-2.5 py-1.5" : "px-3 py-2";
512
+ const LinkComp = linkComponent;
513
+ const parentClass = cn(
514
+ "flex min-w-0 flex-1 items-center gap-3 rounded-lg text-sm font-medium transition-colors",
515
+ pad,
516
+ groupHighlight ? "bg-sidebar-accent/60 text-sidebar-primary" : "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
517
+ );
518
+ return /* @__PURE__ */ jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
519
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", "data-testid": `nav-group-${item.label}`, children: [
520
+ item.to && !item.disabled ? /* @__PURE__ */ jsxs(
521
+ LinkComp,
522
+ {
523
+ href: item.to,
524
+ onClick: onNavClick,
525
+ "aria-current": selfExact ? "page" : void 0,
526
+ className: parentClass,
527
+ children: [
528
+ /* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
529
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label })
530
+ ]
531
+ }
532
+ ) : /* @__PURE__ */ jsxs(
533
+ "button",
534
+ {
535
+ type: "button",
536
+ className: parentClass,
537
+ onClick: () => setOpen((o) => !o),
538
+ "aria-expanded": open,
539
+ "aria-disabled": item.disabled || void 0,
540
+ disabled: item.disabled,
541
+ children: [
542
+ /* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
543
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label })
544
+ ]
545
+ }
546
+ ),
547
+ /* @__PURE__ */ jsx(
548
+ "button",
549
+ {
550
+ type: "button",
551
+ className: cn(
552
+ "shrink-0 rounded-lg p-1.5 text-sidebar-foreground transition-colors hover:bg-sidebar-accent",
553
+ groupHighlight && "text-sidebar-primary",
554
+ item.disabled && "pointer-events-none opacity-40"
555
+ ),
556
+ onClick: () => setOpen((o) => !o),
557
+ "aria-expanded": open,
558
+ "aria-label": open ? `Collapse ${item.label}` : `Expand ${item.label}`,
559
+ "aria-disabled": item.disabled || void 0,
560
+ disabled: item.disabled,
561
+ "data-testid": `nav-group-toggle-${item.label}`,
562
+ children: /* @__PURE__ */ jsx(
563
+ ChevronDown,
564
+ {
565
+ className: cn(
566
+ "h-3.5 w-3.5 opacity-60 transition-transform",
567
+ open && "rotate-180"
568
+ )
569
+ }
570
+ )
571
+ }
572
+ )
573
+ ] }),
574
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "ml-3 space-y-0.5 border-l border-sidebar-border pl-2", children: children.map((child) => /* @__PURE__ */ jsx(
575
+ NavLinkRow,
576
+ {
577
+ item: child,
578
+ collapsed: false,
579
+ density,
580
+ onNavClick,
581
+ linkComponent,
582
+ activePath,
583
+ isActive
584
+ },
585
+ child.to + child.label
586
+ )) }) })
587
+ ] }) });
588
+ }
589
+ function SidebarNav({
590
+ navItems,
591
+ secondaryNavItems,
592
+ secondaryNavLabel,
593
+ showSecondary,
594
+ collapsed,
595
+ density,
596
+ onNavClick,
597
+ linkComponent,
598
+ activePath,
599
+ isActive
600
+ }) {
601
+ return /* @__PURE__ */ jsxs(
602
+ "nav",
603
+ {
604
+ className: cn(
605
+ "flex-1 space-y-1 overflow-y-auto",
606
+ collapsed ? "p-2" : "p-3"
607
+ ),
608
+ "aria-label": "Main",
609
+ children: [
610
+ navItems.map((item) => /* @__PURE__ */ jsx(
611
+ NavGroup,
612
+ {
613
+ item,
614
+ collapsed,
615
+ density,
616
+ onNavClick,
617
+ linkComponent,
618
+ activePath,
619
+ isActive
620
+ },
621
+ item.to + item.label
622
+ )),
623
+ showSecondary && secondaryNavItems && secondaryNavItems.length > 0 ? /* @__PURE__ */ jsxs(
624
+ "div",
625
+ {
626
+ className: cn(
627
+ "mt-4 space-y-1 border-t border-sidebar-border pt-4",
628
+ collapsed && "mt-2 pt-2"
629
+ ),
630
+ children: [
631
+ !collapsed ? /* @__PURE__ */ jsx("p", { className: "mb-2 px-3 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/40", children: secondaryNavLabel }) : null,
632
+ secondaryNavItems.map((item) => /* @__PURE__ */ jsx(
633
+ NavGroup,
634
+ {
635
+ item,
636
+ collapsed,
637
+ density,
638
+ onNavClick,
639
+ linkComponent,
640
+ activePath,
641
+ isActive
642
+ },
643
+ item.to + item.label
644
+ ))
645
+ ]
646
+ }
647
+ ) : null
648
+ ]
649
+ }
650
+ );
651
+ }
652
+ function SidebarBrand({
653
+ appName,
654
+ appLogo,
655
+ logo,
656
+ showLogo,
657
+ showAppName,
658
+ collapsed
659
+ }) {
660
+ if (!showLogo && !showAppName) return null;
661
+ return /* @__PURE__ */ jsxs(
662
+ "div",
663
+ {
664
+ className: cn(
665
+ "flex min-w-0 items-center gap-2",
666
+ collapsed && "justify-center"
667
+ ),
668
+ children: [
669
+ showLogo ? logo ? /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden", children: logo }) : appLogo ? /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-primary-foreground", children: appLogo }) }) : null : null,
670
+ showAppName && !collapsed ? /* @__PURE__ */ jsx("span", { className: "truncate font-semibold text-sidebar-foreground", children: appName }) : null
671
+ ]
672
+ }
673
+ );
674
+ }
675
+ function AppShell({
676
+ appName,
677
+ appLogo,
678
+ logo,
679
+ navItems,
680
+ secondaryNavItems,
681
+ secondaryNavLabel = "More",
682
+ mobileNavItems,
683
+ headerSlot,
684
+ collapsedHeaderSlot,
685
+ footer,
686
+ userSlot,
687
+ children,
688
+ showThemeToggle: showThemeToggleProp,
689
+ options: optionsProp,
690
+ optionsStorageKey,
691
+ collapsed: collapsedProp,
692
+ onCollapsedChange,
693
+ linkComponent = DefaultLink,
694
+ activePath,
695
+ isActive = isPathActive
696
+ }) {
697
+ const [mobileOpen, setMobileOpen] = useState(false);
698
+ const [userOverrides, setUserOverrides] = useState(
699
+ {}
700
+ );
701
+ const [hydratedKey, setHydratedKey] = useState(
702
+ optionsStorageKey ? void 0 : ""
703
+ );
704
+ useEffect(() => {
705
+ setMobileOpen(false);
706
+ }, [activePath]);
707
+ const hasSecondary = Boolean(secondaryNavItems && secondaryNavItems.length);
708
+ const hasUser = userSlot != null;
709
+ const hasFooter = footer != null;
710
+ const hasLogo = Boolean(logo || appLogo);
711
+ useEffect(() => {
712
+ if (!optionsStorageKey) {
713
+ setUserOverrides({});
714
+ setHydratedKey("");
715
+ return;
716
+ }
717
+ setUserOverrides(readStoredOptions(optionsStorageKey));
718
+ setHydratedKey(optionsStorageKey);
719
+ }, [optionsStorageKey]);
720
+ useEffect(() => {
721
+ if (!optionsStorageKey || hydratedKey !== optionsStorageKey) return;
722
+ writeStoredOptions(optionsStorageKey, userOverrides);
723
+ }, [optionsStorageKey, userOverrides, hydratedKey]);
724
+ const lockedOptions = optionsProp?.lockedOptions;
725
+ const resolved = useMemo(() => {
726
+ const merged = {
727
+ ...OPTION_DEFAULTS2,
728
+ ...showThemeToggleProp !== void 0 ? { showThemeToggle: showThemeToggleProp } : {},
729
+ ...optionsProp,
730
+ ...userOverrides
731
+ };
732
+ if (merged.configurable === void 0) {
733
+ merged.configurable = hasSecondary || hasUser || hasFooter || Boolean(merged.showCollapse) || Boolean(merged.showThemeToggle);
734
+ }
735
+ if (lockedOptions?.length) {
736
+ const defaults = OPTION_DEFAULTS2;
737
+ const devOptions = optionsProp;
738
+ const target = merged;
739
+ for (const key of lockedOptions) {
740
+ target[key] = devOptions?.[key] ?? defaults[key];
741
+ }
742
+ }
743
+ return merged;
744
+ }, [
745
+ optionsProp,
746
+ userOverrides,
747
+ showThemeToggleProp,
748
+ hasSecondary,
749
+ hasUser,
750
+ hasFooter,
751
+ lockedOptions
752
+ ]);
753
+ const patchOption = useCallback(
754
+ (key, value) => {
755
+ if (lockedOptions?.includes(key)) return;
756
+ setUserOverrides((prev) => ({ ...prev, [key]: value }));
757
+ },
758
+ [lockedOptions]
759
+ );
760
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(
761
+ () => optionsProp?.defaultCollapsed ?? OPTION_DEFAULTS2.defaultCollapsed
762
+ );
763
+ useEffect(() => {
764
+ if (!optionsStorageKey || collapsedProp !== void 0) return;
765
+ const stored = readStoredCollapsed(optionsStorageKey);
766
+ if (stored !== void 0) setUncontrolledCollapsed(stored);
767
+ }, [optionsStorageKey, collapsedProp]);
768
+ const isCollapseControlled = collapsedProp !== void 0;
769
+ const collapsed = isCollapseControlled ? collapsedProp : uncontrolledCollapsed;
770
+ const setCollapsed = (next) => {
771
+ if (!isCollapseControlled) {
772
+ setUncontrolledCollapsed(next);
773
+ if (optionsStorageKey) writeStoredCollapsed(optionsStorageKey, next);
774
+ }
775
+ onCollapsedChange?.(next);
776
+ };
777
+ const density = resolved.density ?? "comfortable";
778
+ const scrollWindow = resolved.scrollRoot === "window";
779
+ const railExpanded = resolved.railWidth === "wide" ? "md:w-64" : "md:w-52";
780
+ const railClass = collapsed ? "md:w-14" : railExpanded;
781
+ const bottomItems = (mobileNavItems ?? navItems.filter((i) => !i.children || i.children.length === 0)).filter((i) => !i.disabled);
782
+ const toggles = [
783
+ { key: "showLogo", label: "Logo", available: hasLogo },
784
+ { key: "showAppName", label: "App name", available: true },
785
+ {
786
+ key: "showThemeToggle",
787
+ label: "Theme toggle",
788
+ available: true
789
+ },
790
+ { key: "showCollapse", label: "Collapse control", available: true },
791
+ {
792
+ key: "showSecondaryNav",
793
+ label: "Secondary nav",
794
+ available: hasSecondary
795
+ },
796
+ { key: "showFooter", label: "Footer", available: hasFooter },
797
+ { key: "showUserSlot", label: "User block", available: hasUser },
798
+ {
799
+ key: "showMobileBottomNav",
800
+ label: "Mobile bottom nav",
801
+ available: bottomItems.length > 0
802
+ }
803
+ ].filter((t) => t.available && !lockedOptions?.includes(t.key));
804
+ const showTheme = resolved.showThemeToggle !== false;
805
+ const showCollapseCtrl = resolved.showCollapse !== false;
806
+ const showSecondary = hasSecondary && resolved.showSecondaryNav !== false;
807
+ const showFooterSlot = hasFooter && resolved.showFooter !== false;
808
+ const showUser = hasUser && resolved.showUserSlot !== false;
809
+ const showMobileBottom = bottomItems.length > 0 && resolved.showMobileBottomNav !== false;
810
+ const optionsControl = resolved.configurable && toggles.length > 0 ? /* @__PURE__ */ jsxs(Popover, { children: [
811
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
812
+ Button,
813
+ {
814
+ type: "button",
815
+ variant: "ghost",
816
+ size: "icon",
817
+ className: "h-8 w-8",
818
+ title: "Sidebar options",
819
+ "aria-label": "Sidebar options",
820
+ "data-testid": "app-shell-options",
821
+ testID: "app-shell-options",
822
+ children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-4 w-4" })
823
+ }
824
+ ) }),
825
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "start", side: "top", className: "w-64 space-y-3", children: [
826
+ /* @__PURE__ */ jsxs("div", { children: [
827
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Sidebar options" }),
828
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Customize this sidebar. Saved on this device when a storage key is set." })
829
+ ] }),
830
+ /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: toggles.map((t) => {
831
+ const checked = Boolean(resolved[t.key] ?? true);
832
+ return /* @__PURE__ */ jsxs(
833
+ "li",
834
+ {
835
+ className: "flex items-center justify-between gap-3",
836
+ children: [
837
+ /* @__PURE__ */ jsx(
838
+ Label,
839
+ {
840
+ htmlFor: `app-shell-opt-${t.key}`,
841
+ className: "text-sm font-normal",
842
+ children: t.label
843
+ }
844
+ ),
845
+ /* @__PURE__ */ jsx(
846
+ Switch,
847
+ {
848
+ id: `app-shell-opt-${t.key}`,
849
+ checked,
850
+ onCheckedChange: (v) => patchOption(t.key, v)
851
+ }
852
+ )
853
+ ]
854
+ },
855
+ t.key
856
+ );
857
+ }) }),
858
+ optionsStorageKey && Object.keys(userOverrides).length > 0 ? /* @__PURE__ */ jsx(
859
+ Button,
860
+ {
861
+ type: "button",
862
+ variant: "ghost",
863
+ size: "sm",
864
+ className: "h-8 w-full text-xs",
865
+ onClick: () => setUserOverrides({}),
866
+ "data-testid": "app-shell-options-reset",
867
+ "aria-label": "Reset sidebar options",
868
+ testID: "app-shell-options-reset",
869
+ children: "Reset to defaults"
870
+ }
871
+ ) : null
872
+ ] })
873
+ ] }) : null;
874
+ const sidebarChrome = (opts) => /* @__PURE__ */ jsxs(Fragment, { children: [
875
+ /* @__PURE__ */ jsxs(
876
+ "div",
877
+ {
878
+ className: cn(
879
+ "flex h-14 shrink-0 items-center gap-2 border-b border-sidebar-border",
880
+ collapsed && !opts.mobile ? "justify-center px-2" : "px-3"
881
+ ),
882
+ children: [
883
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
884
+ SidebarBrand,
885
+ {
886
+ appName,
887
+ appLogo,
888
+ logo,
889
+ showLogo: resolved.showLogo !== false,
890
+ showAppName: resolved.showAppName !== false,
891
+ collapsed: collapsed && !opts.mobile
892
+ }
893
+ ) }),
894
+ opts.mobile ? /* @__PURE__ */ jsx(
895
+ Button,
896
+ {
897
+ variant: "ghost",
898
+ size: "icon",
899
+ onClick: () => setMobileOpen(false),
900
+ "aria-label": "Close navigation",
901
+ "data-testid": "app-shell-mobile-close",
902
+ testID: "app-shell-mobile-close",
903
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
904
+ }
905
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
906
+ showCollapseCtrl ? /* @__PURE__ */ jsx(
907
+ Button,
908
+ {
909
+ variant: "ghost",
910
+ size: "icon",
911
+ className: "h-8 w-8",
912
+ onClick: () => setCollapsed(!collapsed),
913
+ title: collapsed ? "Expand sidebar" : "Collapse sidebar",
914
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
915
+ "aria-pressed": collapsed,
916
+ "data-testid": "app-shell-collapse",
917
+ testID: "app-shell-collapse",
918
+ children: collapsed ? /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
919
+ }
920
+ ) : null,
921
+ !collapsed ? optionsControl : null
922
+ ] })
923
+ ]
924
+ }
925
+ ),
926
+ collapsed && !opts.mobile ? collapsedHeaderSlot ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-sidebar-border p-2", children: collapsedHeaderSlot }) : null : headerSlot ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-sidebar-border", children: headerSlot }) : null,
927
+ /* @__PURE__ */ jsx(
928
+ SidebarNav,
929
+ {
930
+ navItems,
931
+ secondaryNavItems,
932
+ secondaryNavLabel,
933
+ showSecondary,
934
+ collapsed: collapsed && !opts.mobile,
935
+ density,
936
+ onNavClick: opts.mobile ? () => setMobileOpen(false) : void 0,
937
+ linkComponent,
938
+ activePath,
939
+ isActive
940
+ }
941
+ ),
942
+ (showFooterSlot || showUser || showTheme || collapsed && optionsControl) && !opts.mobile ? /* @__PURE__ */ jsxs("div", { className: "mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-2", children: [
943
+ showFooterSlot && !collapsed ? footer : null,
944
+ showUser ? /* @__PURE__ */ jsx(
945
+ "div",
946
+ {
947
+ className: cn(
948
+ collapsed && "flex justify-center [&_[data-shell-user-detail]]:hidden"
949
+ ),
950
+ children: userSlot
951
+ }
952
+ ) : null,
953
+ /* @__PURE__ */ jsxs(
954
+ "div",
955
+ {
956
+ className: cn(
957
+ "flex items-center gap-1",
958
+ collapsed ? "flex-col" : "justify-end"
959
+ ),
960
+ children: [
961
+ collapsed ? optionsControl : null,
962
+ showTheme ? /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-desktop" }) : null
963
+ ]
964
+ }
965
+ )
966
+ ] }) : null,
967
+ opts.mobile && (showFooterSlot || showUser || showTheme) ? /* @__PURE__ */ jsxs("div", { className: "mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-3", children: [
968
+ showFooterSlot ? footer : null,
969
+ showUser ? userSlot : null,
970
+ showTheme ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-mobile-drawer" }) }) : null
971
+ ] }) : null
972
+ ] });
973
+ return /* @__PURE__ */ jsxs(
974
+ "div",
975
+ {
976
+ className: cn(
977
+ "flex",
978
+ scrollWindow ? "min-h-screen" : "h-screen overflow-hidden"
979
+ ),
980
+ children: [
981
+ /* @__PURE__ */ jsx(
982
+ "aside",
983
+ {
984
+ className: cn(
985
+ "hidden flex-col border-r border-sidebar-border bg-sidebar transition-[width] duration-200 md:flex",
986
+ scrollWindow && "sticky top-0 h-screen",
987
+ railClass
988
+ ),
989
+ "data-collapsed": collapsed || void 0,
990
+ "data-testid": "app-shell-sidebar",
991
+ children: sidebarChrome({})
992
+ }
993
+ ),
994
+ mobileOpen ? /* @__PURE__ */ jsx(
995
+ "div",
996
+ {
997
+ className: "fixed inset-0 z-40 bg-black/50 md:hidden",
998
+ onClick: () => setMobileOpen(false),
999
+ "aria-hidden": true
1000
+ }
1001
+ ) : null,
1002
+ /* @__PURE__ */ jsx(
1003
+ "aside",
1004
+ {
1005
+ className: cn(
1006
+ "fixed inset-y-0 left-0 z-50 flex w-60 flex-col border-r border-sidebar-border bg-sidebar transition-transform md:hidden",
1007
+ mobileOpen ? "translate-x-0" : "-translate-x-full pointer-events-none"
1008
+ ),
1009
+ id: "app-shell-mobile-nav",
1010
+ "aria-hidden": !mobileOpen,
1011
+ inert: !mobileOpen || void 0,
1012
+ children: sidebarChrome({ mobile: true })
1013
+ }
1014
+ ),
1015
+ /* @__PURE__ */ jsxs(
1016
+ "div",
1017
+ {
1018
+ className: cn(
1019
+ "flex min-w-0 flex-1 flex-col",
1020
+ !scrollWindow && "overflow-hidden"
1021
+ ),
1022
+ children: [
1023
+ /* @__PURE__ */ jsxs(
1024
+ "header",
1025
+ {
1026
+ className: cn(
1027
+ "flex h-14 items-center gap-3 border-b px-4 md:hidden",
1028
+ scrollWindow && "sticky top-0 z-20 bg-background"
1029
+ ),
1030
+ children: [
1031
+ /* @__PURE__ */ jsx(
1032
+ Button,
1033
+ {
1034
+ variant: "ghost",
1035
+ size: "icon",
1036
+ onClick: () => setMobileOpen(true),
1037
+ "aria-label": "Open navigation",
1038
+ "aria-expanded": mobileOpen,
1039
+ "aria-controls": "app-shell-mobile-nav",
1040
+ "data-testid": "app-shell-mobile-open",
1041
+ testID: "app-shell-mobile-open",
1042
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate font-semibold", children: appName }),
1046
+ showTheme ? /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-mobile-header" }) : null
1047
+ ]
1048
+ }
1049
+ ),
1050
+ /* @__PURE__ */ jsx(
1051
+ "main",
1052
+ {
1053
+ className: cn(
1054
+ "flex-1",
1055
+ !scrollWindow && "overflow-y-auto",
1056
+ showMobileBottom ? "pb-16 md:pb-0" : "pb-0"
1057
+ ),
1058
+ children: /* @__PURE__ */ jsx("div", { className: "min-w-0 p-4 sm:p-6 lg:p-8", children })
1059
+ }
1060
+ ),
1061
+ showMobileBottom ? /* @__PURE__ */ jsx(
1062
+ "nav",
1063
+ {
1064
+ className: "fixed bottom-0 left-0 right-0 z-30 flex border-t bg-background md:hidden",
1065
+ "aria-label": "Primary",
1066
+ children: bottomItems.map((item) => {
1067
+ const LinkComp = linkComponent;
1068
+ const active = itemIsActive(item, activePath, isActive);
1069
+ return /* @__PURE__ */ jsxs(
1070
+ LinkComp,
1071
+ {
1072
+ href: item.to,
1073
+ title: item.label,
1074
+ "aria-label": item.label,
1075
+ "aria-current": active ? "page" : void 0,
1076
+ className: cn(
1077
+ "flex flex-1 flex-col items-center gap-1 py-2 text-xs font-medium transition-colors",
1078
+ active ? "text-primary" : "text-muted-foreground"
1079
+ ),
1080
+ children: [
1081
+ /* @__PURE__ */ jsx(item.icon, { className: "h-5 w-5" }),
1082
+ /* @__PURE__ */ jsx("span", { className: "max-w-full truncate px-0.5", children: item.label })
1083
+ ]
1084
+ },
1085
+ item.to + item.label
1086
+ );
1087
+ })
1088
+ }
1089
+ ) : null
1090
+ ]
1091
+ }
1092
+ )
1093
+ ]
1094
+ }
1095
+ );
1096
+ }
1097
+
1098
+ export { AppShell, PageHeader };
1099
+ //# sourceMappingURL=chunk-FEJAZFP3.js.map
1100
+ //# sourceMappingURL=chunk-FEJAZFP3.js.map