@marktiderman/genesis-ui 1.2.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 (295) 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-AI6CCNWK.js → chunk-6NPEMLBA.js} +2 -2
  17. package/dist/chunk-6NPEMLBA.js.map +1 -0
  18. package/dist/chunk-6Q6CLLJ4.js +9 -0
  19. package/dist/chunk-6Q6CLLJ4.js.map +1 -0
  20. package/dist/{chunk-UGHQFQOE.js → chunk-6TVMQH6M.js} +28 -28
  21. package/dist/chunk-6TVMQH6M.js.map +1 -0
  22. package/dist/{chunk-UKSBLFEK.js → chunk-6XPMU65K.js} +5 -5
  23. package/dist/{chunk-UKSBLFEK.js.map → chunk-6XPMU65K.js.map} +1 -1
  24. package/dist/{chunk-YZEA5TGK.js → chunk-7XMZVISM.js} +16 -16
  25. package/dist/chunk-7XMZVISM.js.map +1 -0
  26. package/dist/chunk-A7AHVDG7.js +52 -0
  27. package/dist/chunk-A7AHVDG7.js.map +1 -0
  28. package/dist/chunk-AFZ76Q26.js +84 -0
  29. package/dist/chunk-AFZ76Q26.js.map +1 -0
  30. package/dist/{chunk-4V4H2ZM7.js → chunk-AL5TMEZQ.js} +31 -31
  31. package/dist/chunk-AL5TMEZQ.js.map +1 -0
  32. package/dist/{chunk-O4B5ALO7.js → chunk-AOZGQM6W.js} +3 -3
  33. package/dist/chunk-AOZGQM6W.js.map +1 -0
  34. package/dist/chunk-ARJI2JUV.js +122 -0
  35. package/dist/chunk-ARJI2JUV.js.map +1 -0
  36. package/dist/chunk-AVFZ4P4B.js +25 -0
  37. package/dist/chunk-AVFZ4P4B.js.map +1 -0
  38. package/dist/chunk-B2GIBUQD.js +11 -0
  39. package/dist/chunk-B2GIBUQD.js.map +1 -0
  40. package/dist/{chunk-JXI2MYL3.js → chunk-C46ITXE4.js} +5 -5
  41. package/dist/chunk-C46ITXE4.js.map +1 -0
  42. package/dist/chunk-CNTWQSUT.js +65 -0
  43. package/dist/chunk-CNTWQSUT.js.map +1 -0
  44. package/dist/chunk-D2LIXA2Y.js +65 -0
  45. package/dist/chunk-D2LIXA2Y.js.map +1 -0
  46. package/dist/{chunk-FCGFG4RV.js → chunk-D4JTQYP5.js} +3 -3
  47. package/dist/{chunk-FCGFG4RV.js.map → chunk-D4JTQYP5.js.map} +1 -1
  48. package/dist/{chunk-T5VX5DYO.js → chunk-F47U5KNW.js} +5 -5
  49. package/dist/chunk-F47U5KNW.js.map +1 -0
  50. package/dist/chunk-FEJAZFP3.js +1100 -0
  51. package/dist/chunk-FEJAZFP3.js.map +1 -0
  52. package/dist/{chunk-2B3MFHBQ.js → chunk-FK42ZGGV.js} +21 -21
  53. package/dist/chunk-FK42ZGGV.js.map +1 -0
  54. package/dist/{chunk-XISSLIMH.js → chunk-FTTNGGLA.js} +2 -2
  55. package/dist/{chunk-XISSLIMH.js.map → chunk-FTTNGGLA.js.map} +1 -1
  56. package/dist/chunk-G5YKBPBF.js +74 -0
  57. package/dist/chunk-G5YKBPBF.js.map +1 -0
  58. package/dist/chunk-GCQFVMPI.js +56 -0
  59. package/dist/chunk-GCQFVMPI.js.map +1 -0
  60. package/dist/{chunk-LDPPIBIJ.js → chunk-IJCT5IJD.js} +3 -3
  61. package/dist/{chunk-LDPPIBIJ.js.map → chunk-IJCT5IJD.js.map} +1 -1
  62. package/dist/{chunk-DGRHPJ3M.js → chunk-J6FBA7PH.js} +3 -3
  63. package/dist/{chunk-DGRHPJ3M.js.map → chunk-J6FBA7PH.js.map} +1 -1
  64. package/dist/chunk-JAMLFMDS.js +12 -0
  65. package/dist/chunk-JAMLFMDS.js.map +1 -0
  66. package/dist/{chunk-C6H7AMO4.js → chunk-LNFF2RBD.js} +9 -9
  67. package/dist/chunk-LNFF2RBD.js.map +1 -0
  68. package/dist/{chunk-QBZMY2AF.js → chunk-M62CHO3N.js} +18 -18
  69. package/dist/chunk-M62CHO3N.js.map +1 -0
  70. package/dist/{chunk-5Q4FABZQ.js → chunk-NUXW6NPP.js} +8 -8
  71. package/dist/chunk-NUXW6NPP.js.map +1 -0
  72. package/dist/{chunk-I6JQHP6D.js → chunk-NWSCYLPT.js} +78 -17
  73. package/dist/chunk-NWSCYLPT.js.map +1 -0
  74. package/dist/{chunk-63FW5ZUA.js → chunk-P3ZL6FGR.js} +7 -7
  75. package/dist/chunk-P3ZL6FGR.js.map +1 -0
  76. package/dist/chunk-P64MFQXZ.js +191 -0
  77. package/dist/chunk-P64MFQXZ.js.map +1 -0
  78. package/dist/chunk-PLIYF6KI.js +52 -0
  79. package/dist/chunk-PLIYF6KI.js.map +1 -0
  80. package/dist/chunk-PSR47JRA.js +26 -0
  81. package/dist/chunk-PSR47JRA.js.map +1 -0
  82. package/dist/{chunk-QLCDAFS7.js → chunk-Q4I4SFBD.js} +3 -3
  83. package/dist/chunk-Q4I4SFBD.js.map +1 -0
  84. package/dist/{chunk-SBJV3K4G.js → chunk-QJ6P2DW6.js} +28 -28
  85. package/dist/chunk-QJ6P2DW6.js.map +1 -0
  86. package/dist/{chunk-IP6TMT3D.js → chunk-R7QFP6EL.js} +3 -3
  87. package/dist/chunk-R7QFP6EL.js.map +1 -0
  88. package/dist/{chunk-WZWR2ELB.js → chunk-SEBU6JCK.js} +6 -6
  89. package/dist/chunk-SEBU6JCK.js.map +1 -0
  90. package/dist/{chunk-7OBL3H5P.js → chunk-SEFK4NZV.js} +3 -3
  91. package/dist/chunk-SEFK4NZV.js.map +1 -0
  92. package/dist/chunk-SRIT4NFQ.js +67 -0
  93. package/dist/chunk-SRIT4NFQ.js.map +1 -0
  94. package/dist/chunk-SY62XJFA.js +19 -0
  95. package/dist/chunk-SY62XJFA.js.map +1 -0
  96. package/dist/{chunk-SVXDWPLO.js → chunk-THD3YOOZ.js} +17 -17
  97. package/dist/chunk-THD3YOOZ.js.map +1 -0
  98. package/dist/chunk-VXFYETRK.js +58 -0
  99. package/dist/chunk-VXFYETRK.js.map +1 -0
  100. package/dist/chunk-XBX6QMR5.js +58 -0
  101. package/dist/chunk-XBX6QMR5.js.map +1 -0
  102. package/dist/{chunk-SCUT2ZCD.js → chunk-XCGZPFT7.js} +17 -17
  103. package/dist/chunk-XCGZPFT7.js.map +1 -0
  104. package/dist/chunk-XMK3T4TA.js +73 -0
  105. package/dist/chunk-XMK3T4TA.js.map +1 -0
  106. package/dist/chunk-YJ6MPS6U.js +226 -0
  107. package/dist/chunk-YJ6MPS6U.js.map +1 -0
  108. package/dist/chunk-YRYQXU3G.js +38 -0
  109. package/dist/chunk-YRYQXU3G.js.map +1 -0
  110. package/dist/{chunk-5J7BP5DI.js → chunk-YWFPMND7.js} +10 -10
  111. package/dist/chunk-YWFPMND7.js.map +1 -0
  112. package/dist/chunk-YYEGAFUE.js +94 -0
  113. package/dist/chunk-YYEGAFUE.js.map +1 -0
  114. package/dist/{chunk-TKA4LOMT.js → chunk-ZZ7H7VVX.js} +8 -8
  115. package/dist/chunk-ZZ7H7VVX.js.map +1 -0
  116. package/dist/components/data/index.d.ts +223 -74
  117. package/dist/components/data/index.js +504 -539
  118. package/dist/components/data/index.js.map +1 -1
  119. package/dist/components/layout/dashboard-page.d.ts +4 -0
  120. package/dist/components/layout/dashboard-page.js +5 -0
  121. package/dist/components/layout/dashboard-page.js.map +1 -0
  122. package/dist/components/layout/detail-page.d.ts +91 -0
  123. package/dist/components/layout/detail-page.js +6 -0
  124. package/dist/components/layout/detail-page.js.map +1 -0
  125. package/dist/components/layout/form-page.d.ts +82 -0
  126. package/dist/components/layout/form-page.js +5 -0
  127. package/dist/components/layout/form-page.js.map +1 -0
  128. package/dist/components/layout/index.d.ts +15 -0
  129. package/dist/components/layout/index.js +21 -0
  130. package/dist/components/layout/index.js.map +1 -0
  131. package/dist/components/layout/settings-page.d.ts +114 -0
  132. package/dist/components/layout/settings-page.js +6 -0
  133. package/dist/components/layout/settings-page.js.map +1 -0
  134. package/dist/components/patterns/empty-state.d.ts +74 -0
  135. package/dist/components/{ui → patterns}/empty-state.js +2 -1
  136. package/dist/components/{ui → patterns}/form-field.d.ts +2 -2
  137. package/dist/components/{ui → patterns}/form-field.js +2 -2
  138. package/dist/components/patterns/index.d.ts +18 -0
  139. package/dist/components/patterns/index.js +18 -0
  140. package/dist/components/patterns/index.js.map +1 -0
  141. package/dist/components/patterns/page-loading.d.ts +25 -0
  142. package/dist/components/patterns/page-loading.js +5 -0
  143. package/dist/components/patterns/page-loading.js.map +1 -0
  144. package/dist/components/patterns/settings-row.d.ts +42 -0
  145. package/dist/components/patterns/settings-row.js +6 -0
  146. package/dist/components/patterns/settings-row.js.map +1 -0
  147. package/dist/components/patterns/status-badge.d.ts +38 -0
  148. package/dist/components/patterns/status-badge.js +4 -0
  149. package/dist/components/patterns/status-badge.js.map +1 -0
  150. package/dist/components/patterns/toggle-row.d.ts +36 -0
  151. package/dist/components/patterns/toggle-row.js +8 -0
  152. package/dist/components/patterns/toggle-row.js.map +1 -0
  153. package/dist/components/patterns/user-avatar.d.ts +34 -0
  154. package/dist/components/patterns/user-avatar.js +5 -0
  155. package/dist/components/patterns/user-avatar.js.map +1 -0
  156. package/dist/components/ui/accessible-icon.d.ts +48 -0
  157. package/dist/components/ui/accessible-icon.js +3 -0
  158. package/dist/components/ui/accessible-icon.js.map +1 -0
  159. package/dist/components/ui/accordion.d.ts +5 -5
  160. package/dist/components/ui/accordion.js +1 -1
  161. package/dist/components/ui/alert-dialog.d.ts +10 -10
  162. package/dist/components/ui/alert-dialog.js +1 -1
  163. package/dist/components/ui/aspect-ratio.d.ts +2 -2
  164. package/dist/components/ui/aspect-ratio.js +1 -1
  165. package/dist/components/ui/avatar.d.ts +4 -4
  166. package/dist/components/ui/avatar.js +1 -1
  167. package/dist/components/ui/button.d.ts +75 -8
  168. package/dist/components/ui/button.js +1 -1
  169. package/dist/components/ui/calendar.js +2 -2
  170. package/dist/components/ui/carousel.d.ts +4 -3
  171. package/dist/components/ui/carousel.js +2 -2
  172. package/dist/components/ui/checkbox.d.ts +2 -2
  173. package/dist/components/ui/checkbox.js +1 -1
  174. package/dist/components/ui/collapsible.d.ts +4 -4
  175. package/dist/components/ui/collapsible.js +1 -1
  176. package/dist/components/ui/command-palette.d.ts +30 -0
  177. package/dist/components/ui/command-palette.js +6 -0
  178. package/dist/components/ui/command-palette.js.map +1 -0
  179. package/dist/components/ui/command.d.ts +9 -9
  180. package/dist/components/ui/command.js +2 -2
  181. package/dist/components/ui/context-menu.d.ts +15 -15
  182. package/dist/components/ui/context-menu.js +1 -1
  183. package/dist/components/ui/date-picker.js +4 -4
  184. package/dist/components/ui/dialog.d.ts +9 -9
  185. package/dist/components/ui/dialog.js +1 -1
  186. package/dist/components/ui/drawer.d.ts +7 -7
  187. package/dist/components/ui/dropdown-menu.d.ts +15 -15
  188. package/dist/components/ui/dropdown-menu.js +1 -1
  189. package/dist/components/ui/hover-card.d.ts +4 -4
  190. package/dist/components/ui/hover-card.js +1 -1
  191. package/dist/components/ui/input-otp.d.ts +2 -2
  192. package/dist/components/ui/label.d.ts +2 -2
  193. package/dist/components/ui/label.js +1 -1
  194. package/dist/components/ui/menubar.d.ts +16 -16
  195. package/dist/components/ui/menubar.js +1 -1
  196. package/dist/components/ui/navigation-menu.d.ts +9 -9
  197. package/dist/components/ui/navigation-menu.js +1 -1
  198. package/dist/components/ui/pagination.js +2 -2
  199. package/dist/components/ui/password-toggle-field.d.ts +121 -0
  200. package/dist/components/ui/password-toggle-field.js +4 -0
  201. package/dist/components/ui/password-toggle-field.js.map +1 -0
  202. package/dist/components/ui/popover.d.ts +4 -4
  203. package/dist/components/ui/popover.js +1 -1
  204. package/dist/components/ui/radio-group.d.ts +3 -3
  205. package/dist/components/ui/radio-group.js +1 -1
  206. package/dist/components/ui/resizable.d.ts +1 -1
  207. package/dist/components/ui/scroll-area.d.ts +60 -3
  208. package/dist/components/ui/scroll-area.js +1 -1
  209. package/dist/components/ui/select.d.ts +9 -9
  210. package/dist/components/ui/select.js +1 -1
  211. package/dist/components/ui/sheet.d.ts +11 -9
  212. package/dist/components/ui/sheet.js +1 -1
  213. package/dist/components/ui/slider.d.ts +2 -2
  214. package/dist/components/ui/slider.js +1 -1
  215. package/dist/components/ui/switch.d.ts +2 -2
  216. package/dist/components/ui/switch.js +1 -1
  217. package/dist/components/ui/tabs.d.ts +29 -14
  218. package/dist/components/ui/tabs.js +1 -1
  219. package/dist/components/ui/toggle-group.d.ts +3 -3
  220. package/dist/components/ui/toggle-group.js +2 -2
  221. package/dist/components/ui/toggle.d.ts +2 -2
  222. package/dist/components/ui/toggle.js +1 -1
  223. package/dist/components/ui/toolbar.d.ts +104 -0
  224. package/dist/components/ui/toolbar.js +6 -0
  225. package/dist/components/ui/toolbar.js.map +1 -0
  226. package/dist/components/ui/tooltip.d.ts +46 -4
  227. package/dist/components/ui/tooltip.js +1 -1
  228. package/dist/components/ui/visually-hidden.d.ts +48 -0
  229. package/dist/components/ui/visually-hidden.js +4 -0
  230. package/dist/components/ui/visually-hidden.js.map +1 -0
  231. package/dist/dashboard-page-C0TihRl5.d.ts +107 -0
  232. package/dist/hooks/index.d.ts +5 -3
  233. package/dist/hooks/index.js +10 -5
  234. package/dist/hooks/use-data-filters.d.ts +1 -1
  235. package/dist/hooks/use-data-filters.js +2 -2
  236. package/dist/hooks/use-keyboard-navigation.d.ts +58 -2
  237. package/dist/hooks/use-keyboard-navigation.js +1 -1
  238. package/dist/hooks/use-resource-page.d.ts +3 -2
  239. package/dist/hooks/use-resource-page.js +2 -2
  240. package/dist/hooks/use-saved-views.d.ts +28 -0
  241. package/dist/hooks/use-saved-views.js +3 -0
  242. package/dist/hooks/use-saved-views.js.map +1 -0
  243. package/dist/hooks/use-view-preference.d.ts +1 -1
  244. package/dist/hooks/use-view-preference.js +1 -1
  245. package/dist/index.d.ts +25 -26
  246. package/dist/index.js +55 -38
  247. package/dist/{navigation-qI86Ldug.d.ts → navigation-CpOEyUSb.d.ts} +4 -0
  248. package/dist/spacing-BS6qECD4.d.ts +191 -0
  249. package/dist/split-Dk9f4Mi0.d.ts +57 -0
  250. package/dist/{use-resource-page-B-CFcikK.d.ts → use-resource-page-CPs6wjKt.d.ts} +10 -2
  251. package/dist/view-settings--KXNT_jE.d.ts +12 -0
  252. package/package.json +77 -32
  253. package/dist/chunk-2B3MFHBQ.js.map +0 -1
  254. package/dist/chunk-2MXMWBDV.js.map +0 -1
  255. package/dist/chunk-4V4H2ZM7.js.map +0 -1
  256. package/dist/chunk-5J7BP5DI.js.map +0 -1
  257. package/dist/chunk-5Q4FABZQ.js.map +0 -1
  258. package/dist/chunk-63FW5ZUA.js.map +0 -1
  259. package/dist/chunk-7OBL3H5P.js.map +0 -1
  260. package/dist/chunk-AI6CCNWK.js.map +0 -1
  261. package/dist/chunk-C6H7AMO4.js.map +0 -1
  262. package/dist/chunk-EI5EGKHL.js +0 -32
  263. package/dist/chunk-EI5EGKHL.js.map +0 -1
  264. package/dist/chunk-I6JQHP6D.js.map +0 -1
  265. package/dist/chunk-IP6TMT3D.js.map +0 -1
  266. package/dist/chunk-JJ4NOMMI.js +0 -73
  267. package/dist/chunk-JJ4NOMMI.js.map +0 -1
  268. package/dist/chunk-JXI2MYL3.js.map +0 -1
  269. package/dist/chunk-KBS4BHME.js +0 -11
  270. package/dist/chunk-KBS4BHME.js.map +0 -1
  271. package/dist/chunk-LRXDZBV2.js +0 -26
  272. package/dist/chunk-LRXDZBV2.js.map +0 -1
  273. package/dist/chunk-NJMO3VPA.js.map +0 -1
  274. package/dist/chunk-O4B5ALO7.js.map +0 -1
  275. package/dist/chunk-OCTV3S4I.js +0 -29
  276. package/dist/chunk-OCTV3S4I.js.map +0 -1
  277. package/dist/chunk-QBZMY2AF.js.map +0 -1
  278. package/dist/chunk-QLCDAFS7.js.map +0 -1
  279. package/dist/chunk-SBJV3K4G.js.map +0 -1
  280. package/dist/chunk-SCUT2ZCD.js.map +0 -1
  281. package/dist/chunk-SVXDWPLO.js.map +0 -1
  282. package/dist/chunk-T465NJRL.js +0 -134
  283. package/dist/chunk-T465NJRL.js.map +0 -1
  284. package/dist/chunk-T5VX5DYO.js.map +0 -1
  285. package/dist/chunk-TKA4LOMT.js.map +0 -1
  286. package/dist/chunk-UGHQFQOE.js.map +0 -1
  287. package/dist/chunk-WALXVR6V.js +0 -9
  288. package/dist/chunk-WALXVR6V.js.map +0 -1
  289. package/dist/chunk-WZWR2ELB.js.map +0 -1
  290. package/dist/chunk-YZEA5TGK.js.map +0 -1
  291. package/dist/chunk-ZDAT5JDH.js.map +0 -1
  292. package/dist/components/ui/empty-state.d.ts +0 -22
  293. /package/dist/components/{ui → patterns}/empty-state.js.map +0 -0
  294. /package/dist/components/{ui → patterns}/form-field.js.map +0 -0
  295. /package/dist/{ViewToggle-DDe_Luj2.d.ts → view-toggle-DDe_Luj2.d.ts} +0 -0
@@ -0,0 +1,73 @@
1
+ import { cn } from './chunk-NZ5WBA4R.js';
2
+ import { cva } from 'class-variance-authority';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var statusBadgeVariants = cva(
6
+ "inline-flex w-fit items-center whitespace-nowrap rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7
+ {
8
+ variants: {
9
+ // AA-safe status text on the tinted chip, same technique as `Badge`'s
10
+ // success/warning/info variants (see badge.tsx): same-hue text on the
11
+ // raw tint fails WCAG AA, so text uses the semantic Tailwind ramp
12
+ // (same hues as the Genesis status tokens). All four hues clear 4.5:1
13
+ // at the 700(light)/300(dark) stops over the /15(light) and /20(dark)
14
+ // tints (emerald-700 4.76:1, blue-700 5.64:1, red-700 ~5.3:1);
15
+ // warning needs the 800/200 bump (amber-700 was 4.48:1 — under the
16
+ // 4.5:1 floor).
17
+ //
18
+ // NOTE on `destructive` vs `error`: the offTrack/blocked tint is
19
+ // `bg-destructive`, NOT `bg-error`, even though these two map to the
20
+ // `semantic.error` token upstream. The token layer names this
21
+ // semantic `error`; the CSS theme layer (apps' `@theme` blocks,
22
+ // design-system's generated genesis.css) names the very same color
23
+ // `destructive` and registers no `--color-error` at all — so
24
+ // `bg-error` would silently emit no utility and render an untinted
25
+ // chip. In light mode the two are literally the same value
26
+ // (`--destructive` = 0 84.2% 60.2% = #EF4444 = semantic.error.DEFAULT).
27
+ // Dark mode is the one asymmetry: the theme darkens `--destructive`
28
+ // (0 62.8% 30.6%) while keeping success/warning/info bright, so these
29
+ // two chips read as a more subdued tint there. Left as-is rather than
30
+ // hand-tuning a red-only opacity — that is a theme-layer
31
+ // characteristic, not this component's to paper over.
32
+ //
33
+ // TOKEN GAP — do NOT "fix" the text colours below to `text-*-foreground`
34
+ // (CMT-365-001 proposed exactly that). Those tokens pair with a SOLID
35
+ // fill: `--success-foreground` is `0 0% 100%` (pure white) and
36
+ // `--destructive-foreground` is `0 0% 98%`. These chips use a 15%/20%
37
+ // TINT, so white text on them measures roughly 1.2:1 — unreadable. That
38
+ // review was right about the defect and wrong about the remedy.
39
+ //
40
+ // The correct fix needs a token that does not exist yet: an "on-tint"
41
+ // foreground per status. The scales already carry the right value —
42
+ // `success.dark` is #065F46, which is what `emerald-700` approximates —
43
+ // but no `--color-success-dark` utility is emitted, so it cannot be
44
+ // referenced from here today. Adding one is a design-system API
45
+ // decision, so it is recorded rather than smuggled in.
46
+ //
47
+ // Until then this is a KNOWN brand-theming gap, stated plainly: a
48
+ // consumer brand that redefines `success` gets the new background and
49
+ // the old text colour.
50
+ status: {
51
+ onTrack: "border-transparent bg-success/15 text-emerald-700 dark:bg-success/20 dark:text-emerald-300",
52
+ done: "border-transparent bg-success/15 text-emerald-700 dark:bg-success/20 dark:text-emerald-300",
53
+ atRisk: "border-transparent bg-warning/15 text-amber-800 dark:bg-warning/20 dark:text-amber-200",
54
+ inProgress: "border-transparent bg-info/15 text-blue-700 dark:bg-info/20 dark:text-blue-300",
55
+ offTrack: "border-transparent bg-destructive/15 text-red-700 dark:bg-destructive/20 dark:text-red-300",
56
+ blocked: "border-transparent bg-destructive/15 text-red-700 dark:bg-destructive/20 dark:text-red-300"
57
+ }
58
+ }
59
+ }
60
+ );
61
+ function StatusBadge({ className, status, ...props }) {
62
+ return /* @__PURE__ */ jsx(
63
+ "span",
64
+ {
65
+ className: cn(statusBadgeVariants({ status }), className),
66
+ ...props
67
+ }
68
+ );
69
+ }
70
+
71
+ export { StatusBadge, statusBadgeVariants };
72
+ //# sourceMappingURL=chunk-XMK3T4TA.js.map
73
+ //# sourceMappingURL=chunk-XMK3T4TA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/patterns/status-badge.tsx"],"names":[],"mappings":";;;;AAgBO,IAAM,mBAAA,GAAsB,GAAA;AAAA,EACjC,8LAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA0CR,MAAA,EAAQ;AAAA,QACN,OAAA,EACE,4FAAA;AAAA,QACF,IAAA,EAAM,4FAAA;AAAA,QACN,MAAA,EACE,wFAAA;AAAA,QACF,UAAA,EACE,gFAAA;AAAA,QACF,QAAA,EACE,4FAAA;AAAA,QACF,OAAA,EACE;AAAA;AACJ;AACF;AAEJ;AAwBO,SAAS,YAAY,EAAE,SAAA,EAAW,MAAA,EAAQ,GAAG,OAAM,EAAqB;AAC7E,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,WAAW,EAAA,CAAG,mBAAA,CAAoB,EAAE,MAAA,EAAQ,GAAG,SAAS,CAAA;AAAA,MACvD,GAAG;AAAA;AAAA,GACN;AAEJ","file":"chunk-XMK3T4TA.js","sourcesContent":["import * as React from \"react\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport type { status } from \"@marktiderman/genesis-design-system/tokens\";\nimport { cn } from \"../../utils\";\n\n/**\n * The domain status vocabulary `StatusBadge` understands. A `Pick` (not a\n * hand-copied literal union) of `@marktiderman/genesis-design-system`'s\n * `status` tokens, so removing/renaming one of these keys upstream breaks\n * this file's typecheck instead of silently drifting.\n */\ntype StatusBadgeStatus = keyof Pick<\n typeof status,\n \"onTrack\" | \"atRisk\" | \"offTrack\" | \"done\" | \"blocked\" | \"inProgress\"\n>;\n\nexport const statusBadgeVariants = cva(\n \"inline-flex w-fit items-center whitespace-nowrap rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n {\n variants: {\n // AA-safe status text on the tinted chip, same technique as `Badge`'s\n // success/warning/info variants (see badge.tsx): same-hue text on the\n // raw tint fails WCAG AA, so text uses the semantic Tailwind ramp\n // (same hues as the Genesis status tokens). All four hues clear 4.5:1\n // at the 700(light)/300(dark) stops over the /15(light) and /20(dark)\n // tints (emerald-700 4.76:1, blue-700 5.64:1, red-700 ~5.3:1);\n // warning needs the 800/200 bump (amber-700 was 4.48:1 — under the\n // 4.5:1 floor).\n //\n // NOTE on `destructive` vs `error`: the offTrack/blocked tint is\n // `bg-destructive`, NOT `bg-error`, even though these two map to the\n // `semantic.error` token upstream. The token layer names this\n // semantic `error`; the CSS theme layer (apps' `@theme` blocks,\n // design-system's generated genesis.css) names the very same color\n // `destructive` and registers no `--color-error` at all — so\n // `bg-error` would silently emit no utility and render an untinted\n // chip. In light mode the two are literally the same value\n // (`--destructive` = 0 84.2% 60.2% = #EF4444 = semantic.error.DEFAULT).\n // Dark mode is the one asymmetry: the theme darkens `--destructive`\n // (0 62.8% 30.6%) while keeping success/warning/info bright, so these\n // two chips read as a more subdued tint there. Left as-is rather than\n // hand-tuning a red-only opacity — that is a theme-layer\n // characteristic, not this component's to paper over.\n //\n // TOKEN GAP — do NOT \"fix\" the text colours below to `text-*-foreground`\n // (CMT-365-001 proposed exactly that). Those tokens pair with a SOLID\n // fill: `--success-foreground` is `0 0% 100%` (pure white) and\n // `--destructive-foreground` is `0 0% 98%`. These chips use a 15%/20%\n // TINT, so white text on them measures roughly 1.2:1 — unreadable. That\n // review was right about the defect and wrong about the remedy.\n //\n // The correct fix needs a token that does not exist yet: an \"on-tint\"\n // foreground per status. The scales already carry the right value —\n // `success.dark` is #065F46, which is what `emerald-700` approximates —\n // but no `--color-success-dark` utility is emitted, so it cannot be\n // referenced from here today. Adding one is a design-system API\n // decision, so it is recorded rather than smuggled in.\n //\n // Until then this is a KNOWN brand-theming gap, stated plainly: a\n // consumer brand that redefines `success` gets the new background and\n // the old text colour.\n status: {\n onTrack:\n \"border-transparent bg-success/15 text-emerald-700 dark:bg-success/20 dark:text-emerald-300\",\n done: \"border-transparent bg-success/15 text-emerald-700 dark:bg-success/20 dark:text-emerald-300\",\n atRisk:\n \"border-transparent bg-warning/15 text-amber-800 dark:bg-warning/20 dark:text-amber-200\",\n inProgress:\n \"border-transparent bg-info/15 text-blue-700 dark:bg-info/20 dark:text-blue-300\",\n offTrack:\n \"border-transparent bg-destructive/15 text-red-700 dark:bg-destructive/20 dark:text-red-300\",\n blocked:\n \"border-transparent bg-destructive/15 text-red-700 dark:bg-destructive/20 dark:text-red-300\",\n } satisfies Record<StatusBadgeStatus, string>,\n },\n }\n);\n\nexport interface StatusBadgeProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"color\"> {\n /** Which status to render. Drives the tint via `statusBadgeVariants`. */\n status: StatusBadgeStatus;\n}\n\n/**\n * Status-specific badge for progress/health states — the EOS/OKR-style\n * \"on track / at risk / off track\" goal vocabulary plus common\n * task-lifecycle states (\"done\", \"blocked\", \"in progress\").\n *\n * `Badge` answers \"what color is this badge\"; `StatusBadge` answers \"what\n * does this business status look like as a badge\" — it owns the\n * status → semantic-token mapping so consumers pass a status name instead\n * of hand-rolling their own `status-to-color` lookup (the pattern this\n * component exists to retire). Colors are token-driven — the same\n * `bg-success` / `bg-warning` / `bg-destructive` / `bg-info` semantic\n * Tailwind families `Badge` already uses, backed by\n * `@marktiderman/genesis-design-system`'s `status` tokens — no raw hex here.\n *\n * @stability Beta\n */\nexport function StatusBadge({ className, status, ...props }: StatusBadgeProps) {\n return (\n <span\n className={cn(statusBadgeVariants({ status }), className)}\n {...props}\n />\n );\n}\n\nexport type { StatusBadgeStatus };\n"]}
@@ -0,0 +1,226 @@
1
+ "use client";
2
+ import { useState, useRef, useEffect, useCallback } from 'react';
3
+
4
+ // src/hooks/use-keyboard-navigation.ts
5
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["BUTTON", "A", "SUMMARY"]);
6
+ var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
7
+ "button",
8
+ "link",
9
+ "checkbox",
10
+ "radio",
11
+ "switch",
12
+ "tab",
13
+ "menuitem",
14
+ "menuitemcheckbox",
15
+ "menuitemradio",
16
+ "option",
17
+ "textbox"
18
+ ]);
19
+ var OVERLAY_SELECTOR = '[role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"]';
20
+ var KEEPS_BACKSPACE_SELECTOR = 'canvas, [role="listbox"], [role="menu"], [role="menubar"], [role="tree"], [role="grid"], [role="combobox"], [role="spinbutton"], [role="slider"], [data-keyboard-nav="off"]';
21
+ function keepsBackspace(el) {
22
+ if (!el || typeof el.closest !== "function") return false;
23
+ return el.closest(KEEPS_BACKSPACE_SELECTOR) !== null;
24
+ }
25
+ function isTypingTarget(el) {
26
+ if (!el || typeof el.tagName !== "string") return false;
27
+ const tag = el.tagName;
28
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable === true;
29
+ }
30
+ function measureColumns(container) {
31
+ if (!container) return 1;
32
+ const view = container.ownerDocument?.defaultView;
33
+ if (typeof view?.getComputedStyle !== "function") return 1;
34
+ const template = view.getComputedStyle(container).gridTemplateColumns;
35
+ if (!template || template === "none") return 1;
36
+ if (template.includes("(")) return 1;
37
+ const tracks = template.trim().split(/\s+/).filter(Boolean).length;
38
+ return tracks > 0 ? tracks : 1;
39
+ }
40
+ function ownsItsKeys(el) {
41
+ if (!el || typeof el.tagName !== "string") return false;
42
+ if (INTERACTIVE_TAGS.has(el.tagName)) return true;
43
+ const role = el.getAttribute?.("role");
44
+ if (role && INTERACTIVE_ROLES.has(role)) return true;
45
+ return typeof el.closest === "function" && el.closest(OVERLAY_SELECTOR) !== null;
46
+ }
47
+ function useKeyboardNavigation({
48
+ itemCount,
49
+ onOpen,
50
+ onToggleSelect,
51
+ onBack,
52
+ searchInputRef,
53
+ enabled = true,
54
+ columns,
55
+ overlayOpen = false
56
+ }) {
57
+ const [focusedIndex, setFocusedIndex] = useState(-1);
58
+ const [isActive, setIsActive] = useState(false);
59
+ const containerRef = useRef(null);
60
+ const [scrollRequest, setScrollRequest] = useState(0);
61
+ const scrollInstantRef = useRef(false);
62
+ useEffect(() => {
63
+ setFocusedIndex(-1);
64
+ setIsActive(false);
65
+ }, [itemCount]);
66
+ const scrollToItem = useCallback((index, instant = false) => {
67
+ if (!containerRef.current) return;
68
+ const items = containerRef.current.querySelectorAll("[data-nav-item]");
69
+ const item = items[index];
70
+ if (item) {
71
+ item.scrollIntoView({
72
+ block: "nearest",
73
+ behavior: instant ? "auto" : "smooth"
74
+ });
75
+ }
76
+ }, []);
77
+ const navigableCount = useCallback(() => {
78
+ const container = containerRef.current;
79
+ if (!container) return itemCount;
80
+ const rendered = container.querySelectorAll("[data-nav-item]").length;
81
+ return rendered > 0 ? Math.min(rendered, itemCount) : itemCount;
82
+ }, [itemCount]);
83
+ const firstVisibleIndex = useCallback(() => {
84
+ const container = containerRef.current;
85
+ const view = container?.ownerDocument?.defaultView;
86
+ if (!container || !view) return -1;
87
+ const viewportBottom = view.innerHeight || container.ownerDocument.documentElement.clientHeight;
88
+ if (!viewportBottom) return -1;
89
+ const items = container.querySelectorAll("[data-nav-item]");
90
+ for (let i = 0; i < items.length; i++) {
91
+ const rect = items[i].getBoundingClientRect();
92
+ if (rect.height <= 0 && rect.width <= 0) continue;
93
+ if (rect.bottom > 0 && rect.top < viewportBottom) return i;
94
+ }
95
+ return -1;
96
+ }, []);
97
+ const step = useCallback(
98
+ (delta, instant = false) => {
99
+ const max = navigableCount();
100
+ if (max <= 0) return false;
101
+ const current = focusedIndex;
102
+ const next = current < 0 ? (
103
+ // First move from "nothing focused" adopts the top visible item
104
+ // whichever direction it came from, rather than the row a
105
+ // stride-sized jump would land on.
106
+ Math.min(Math.max(firstVisibleIndex(), 0), max - 1)
107
+ ) : delta > 0 ? Math.min(current + delta, max - 1) : Math.max(current + delta, 0);
108
+ if (next === current) {
109
+ if (delta > 0 && max < itemCount) {
110
+ scrollInstantRef.current = instant;
111
+ setScrollRequest((n) => n + 1);
112
+ return true;
113
+ }
114
+ return false;
115
+ }
116
+ setIsActive(true);
117
+ setFocusedIndex(next);
118
+ scrollInstantRef.current = instant;
119
+ setScrollRequest((n) => n + 1);
120
+ return true;
121
+ },
122
+ [focusedIndex, firstVisibleIndex, itemCount, navigableCount]
123
+ );
124
+ useEffect(() => {
125
+ if (scrollRequest === 0 || focusedIndex < 0) return;
126
+ scrollToItem(focusedIndex, scrollInstantRef.current);
127
+ }, [scrollRequest, focusedIndex, scrollToItem]);
128
+ const columnCount = useCallback(() => {
129
+ if (typeof columns === "number") return Math.max(1, Math.floor(columns));
130
+ return measureColumns(containerRef.current);
131
+ }, [columns]);
132
+ useEffect(() => {
133
+ if (!enabled) return;
134
+ const handler = (e) => {
135
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
136
+ const target = e.target;
137
+ if (isTypingTarget(target)) {
138
+ if (e.key === "Escape") {
139
+ target?.blur();
140
+ e.preventDefault();
141
+ }
142
+ return;
143
+ }
144
+ if (e.key === "Backspace") {
145
+ if (onBack && !keepsBackspace(target)) {
146
+ e.preventDefault();
147
+ onBack(focusedIndex);
148
+ }
149
+ return;
150
+ }
151
+ if (e.key === "Escape") {
152
+ setFocusedIndex(-1);
153
+ setIsActive(false);
154
+ return;
155
+ }
156
+ if (ownsItsKeys(target)) return;
157
+ if (overlayOpen) return;
158
+ switch (e.key) {
159
+ case "j":
160
+ case "ArrowDown": {
161
+ if (step(columnCount(), e.repeat)) e.preventDefault();
162
+ break;
163
+ }
164
+ case "k":
165
+ case "ArrowUp": {
166
+ if (step(-columnCount(), e.repeat)) e.preventDefault();
167
+ break;
168
+ }
169
+ case "ArrowRight": {
170
+ if (columnCount() <= 1) break;
171
+ if (step(1, e.repeat)) e.preventDefault();
172
+ break;
173
+ }
174
+ case "ArrowLeft": {
175
+ if (columnCount() <= 1) break;
176
+ if (step(-1, e.repeat)) e.preventDefault();
177
+ break;
178
+ }
179
+ case "Enter": {
180
+ if (focusedIndex >= 0 && focusedIndex < navigableCount() && onOpen) {
181
+ e.preventDefault();
182
+ onOpen(focusedIndex);
183
+ }
184
+ break;
185
+ }
186
+ case "x": {
187
+ if (focusedIndex >= 0 && focusedIndex < navigableCount() && onToggleSelect) {
188
+ e.preventDefault();
189
+ onToggleSelect(focusedIndex);
190
+ }
191
+ break;
192
+ }
193
+ case "/": {
194
+ if (searchInputRef?.current) {
195
+ e.preventDefault();
196
+ searchInputRef.current.focus();
197
+ }
198
+ break;
199
+ }
200
+ }
201
+ };
202
+ document.addEventListener("keydown", handler);
203
+ return () => document.removeEventListener("keydown", handler);
204
+ }, [
205
+ enabled,
206
+ overlayOpen,
207
+ focusedIndex,
208
+ onOpen,
209
+ onToggleSelect,
210
+ onBack,
211
+ searchInputRef,
212
+ step,
213
+ navigableCount,
214
+ columnCount
215
+ ]);
216
+ return {
217
+ focusedIndex,
218
+ setFocusedIndex,
219
+ containerRef,
220
+ isActive
221
+ };
222
+ }
223
+
224
+ export { useKeyboardNavigation };
225
+ //# sourceMappingURL=chunk-YJ6MPS6U.js.map
226
+ //# sourceMappingURL=chunk-YJ6MPS6U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-keyboard-navigation.ts"],"names":[],"mappings":"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","file":"chunk-YJ6MPS6U.js","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { useState, useEffect, useCallback, useRef } from \"react\";\n\n/** Elements that own their own key handling — never steal keys from these. */\nconst INTERACTIVE_TAGS = new Set([\"BUTTON\", \"A\", \"SUMMARY\"]);\nconst INTERACTIVE_ROLES = new Set([\n \"button\",\n \"link\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n \"tab\",\n \"menuitem\",\n \"menuitemcheckbox\",\n \"menuitemradio\",\n \"option\",\n \"textbox\",\n]);\n/** Containers whose contents sit \"above\" the list and own their keys. */\nconst OVERLAY_SELECTOR =\n '[role=\"dialog\"], [role=\"alertdialog\"], [role=\"menu\"], [role=\"listbox\"]';\n\n/**\n * Composite widgets that run their own key handling and may want Backspace\n * for themselves — type-ahead reset in a listbox/menu/tree, value editing in\n * a spinbutton/slider, \"delete selection\" in a canvas-based editor. ⌫ must\n * not be repurposed as \"dismiss the overlay\" while focus sits inside one.\n * A plain button or the dialog container itself is deliberately NOT here:\n * that is the common case after opening a detail panel, and dismissing is\n * exactly what ⌫ means there. `[data-keyboard-nav=\"off\"]` is the explicit\n * escape hatch for anything this list cannot know about.\n */\nconst KEEPS_BACKSPACE_SELECTOR =\n 'canvas, [role=\"listbox\"], [role=\"menu\"], [role=\"menubar\"], [role=\"tree\"],' +\n ' [role=\"grid\"], [role=\"combobox\"], [role=\"spinbutton\"], [role=\"slider\"],' +\n ' [data-keyboard-nav=\"off\"]';\n\n/** Whether `el` sits inside a widget that owns Backspace (see above). */\nfunction keepsBackspace(el: HTMLElement | null): boolean {\n if (!el || typeof el.closest !== \"function\") return false;\n return el.closest(KEEPS_BACKSPACE_SELECTOR) !== null;\n}\n\n/** Text-entry surfaces: every key belongs to them (including Backspace). */\nfunction isTypingTarget(el: HTMLElement | null): boolean {\n if (!el || typeof el.tagName !== \"string\") return false;\n const tag = el.tagName;\n return (\n tag === \"INPUT\" ||\n tag === \"TEXTAREA\" ||\n tag === \"SELECT\" ||\n el.isContentEditable === true\n );\n}\n\n/**\n * Live column count of a navigable container: the number of tracks in its\n * resolved `grid-template-columns`.\n *\n * Measuring beats being told. A responsive ramp (`sm:grid-cols-2\n * lg:grid-cols-3`) has no single column count a caller could pass in — the\n * answer depends on the viewport at the moment the key is pressed, which is\n * exactly what the computed style reports. Anything that isn't a CSS grid (a\n * list view's stacked rows, a bare test container) computes to `none`/empty\n * and counts as one column, preserving flat-list stepping.\n */\nfunction measureColumns(container: HTMLElement | null): number {\n if (!container) return 1;\n const view = container.ownerDocument?.defaultView;\n if (typeof view?.getComputedStyle !== \"function\") return 1;\n const template = view.getComputedStyle(container).gridTemplateColumns;\n if (!template || template === \"none\") return 1;\n // Browsers resolve the track list to used values (\"240px 240px 240px\").\n // An unresolved *specified* value (\"repeat(3, minmax(0, 1fr))\") is not a\n // track list and its spaces don't count tracks — don't guess from it.\n if (template.includes(\"(\")) return 1;\n const tracks = template.trim().split(/\\s+/).filter(Boolean).length;\n return tracks > 0 ? tracks : 1;\n}\n\n/**\n * A focused control (or anything inside a dialog/menu/listbox) owns its own\n * keys — most importantly Enter and Space. Stealing those for the list\n * cursor is what makes an open overlay keyboard-hostile.\n */\nfunction ownsItsKeys(el: HTMLElement | null): boolean {\n if (!el || typeof el.tagName !== \"string\") return false;\n if (INTERACTIVE_TAGS.has(el.tagName)) return true;\n const role = el.getAttribute?.(\"role\");\n if (role && INTERACTIVE_ROLES.has(role)) return true;\n return (\n typeof el.closest === \"function\" && el.closest(OVERLAY_SELECTOR) !== null\n );\n}\n\n/**\n * Vim/Superhuman-style list navigation: j/k (or Arrow Up/Down) move a\n * focused-item cursor *one row at a time*, Enter opens the focused item, x\n * toggles its selection, and Backspace goes \"back\" (e.g. closes an open\n * detail view). In a multi-column grid the vertical keys step by the\n * container's live column count and Arrow Left/Right step by one, so the\n * cursor follows the grid the user can see rather than treating it as a flat\n * list. Intended for list/grid views such as `DataGrid` — attach\n * `containerRef` to the scrollable item container so the column count can be\n * measured and focus changes can scroll the focused item into view.\n *\n * Deliberately inert, so a default-on consumer can't break the surrounding\n * app: modified chords (Ctrl/Meta/Alt) are never captured, text-entry\n * surfaces keep every key, focused controls (or anything inside a\n * dialog/menu/listbox) keep theirs, composite widgets keep Backspace, and a\n * key that *does nothing* is never swallowed — Arrow Up/Down fall through to\n * the browser's page scrolling on an empty list, or at either end of a full\n * one, instead of dying silently.\n *\n * @stability Beta\n */\nexport interface KeyboardNavOptions {\n /** Total number of navigable items */\n itemCount: number;\n /** Called when Enter is pressed on focused item */\n onOpen?: (index: number) => void;\n /** Called when x is pressed on focused item */\n onToggleSelect?: (index: number) => void;\n /**\n * Called when Backspace (⌫) is pressed — the conventional \"go back\" key,\n * e.g. to close an open detail panel/modal and return to the list. Fires\n * regardless of whether an item is currently focused (mirrors Escape),\n * since \"go back\" should still work if a detail view was opened by mouse\n * click rather than keyboard navigation. Not gated behind `focusedIndex`.\n *\n * Backspace is only intercepted (and its default suppressed) while this\n * is supplied — pass it conditionally, i.e. only while there is something\n * to go back from, so the key stays available to the browser and the\n * surrounding app the rest of the time. Even then it is left alone for\n * text-entry surfaces and for composite widgets that use Backspace\n * themselves (see `KEEPS_BACKSPACE_SELECTOR`).\n */\n onBack?: (focusedIndex: number) => void;\n /** Ref to the search input to focus on \"/\" */\n searchInputRef?: React.RefObject<HTMLInputElement | null>;\n /** Whether navigation is enabled. Default true */\n enabled?: boolean;\n /**\n * Columns in the navigable grid. Drives the vertical stride: Arrow\n * Down/Up (and j/k) move by `columns`, Arrow Left/Right by one — so in a\n * 3-up grid ArrowDown moves *down* a row instead of one card to the right.\n *\n * Leave unset (the default) to measure the live count off `containerRef`'s\n * resolved `grid-template-columns` on each keypress, which is the only\n * value that stays correct across a responsive column ramp. Pass a number\n * only to override that measurement. Either way a single-column container\n * behaves exactly as a flat list: stride 1, Left/Right inert.\n */\n columns?: number;\n /**\n * An overlay (detail panel, modal, form) is open above the list. Cursor\n * movement and activation keys are suspended so they can't drive the list\n * hidden behind it, while the dismissal keys (`onBack`/Backspace, Escape)\n * stay live so the overlay can still be closed from the keyboard.\n * Default false.\n */\n overlayOpen?: boolean;\n}\n\n/** @stability Beta */\nexport interface KeyboardNavResult {\n /** Currently focused item index (-1 = none) */\n focusedIndex: number;\n /** Set focused index manually */\n setFocusedIndex: (index: number) => void;\n /** Ref to attach to the grid container for scroll management */\n containerRef: React.RefObject<HTMLDivElement | null>;\n /** Whether keyboard nav is actively engaged (user has pressed j/k) */\n isActive: boolean;\n}\n\n/** @stability Beta */\nexport function useKeyboardNavigation({\n itemCount,\n onOpen,\n onToggleSelect,\n onBack,\n searchInputRef,\n enabled = true,\n columns,\n overlayOpen = false,\n}: KeyboardNavOptions): KeyboardNavResult {\n const [focusedIndex, setFocusedIndex] = useState(-1);\n const [isActive, setIsActive] = useState(false);\n const containerRef = useRef<HTMLDivElement | null>(null);\n /**\n * Scrolling is requested by `step`, not inferred from `focusedIndex`\n * changing, for two reasons. (1) A *clamped* step leaves the index where\n * it was yet must still scroll — bringing the last rendered item back into\n * view is what trips `DataGrid`'s batch observer and lifts the ceiling\n * described on `navigableCount` below. (2) State updaters must be pure;\n * React StrictMode double-invokes them in development, so a scroll called\n * from inside one fires twice. Bumping a nonce keeps the updater pure and\n * moves the scroll into a committed effect, where the row it targets\n * actually exists in the DOM.\n */\n const [scrollRequest, setScrollRequest] = useState(0);\n const scrollInstantRef = useRef(false);\n\n // Reset when items change\n useEffect(() => {\n setFocusedIndex(-1);\n setIsActive(false);\n }, [itemCount]);\n\n const scrollToItem = useCallback((index: number, instant = false) => {\n if (!containerRef.current) return;\n const items = containerRef.current.querySelectorAll(\"[data-nav-item]\");\n const item = items[index] as HTMLElement | undefined;\n if (item) {\n // Key-repeat fires faster than a smooth scroll settles, and a new\n // programmatic scroll can interrupt the in-flight one (MDN\n // scrollIntoView). Instant scroll under repeat lets the batch\n // IntersectionObserver see the sentinel before the next keydown.\n item.scrollIntoView({\n block: \"nearest\",\n behavior: instant ? \"auto\" : \"smooth\",\n });\n }\n }, []);\n\n /**\n * How far the cursor may travel *right now*. A batched grid (see\n * `DataGrid`'s infinite-scroll batching) renders only a slice of\n * `itemCount`, so the DOM — not the data length — is the real ceiling.\n * Walking past the rendered slice would drop the focus ring and leave\n * Enter/x acting on a row the user cannot see.\n *\n * Stopping at the last rendered item is self-healing: `scrollToItem`\n * brings it into view, which trips the batch observer, which renders the\n * next batch — so the next keypress continues where this one stopped.\n * (Do not feed the rendered count into `itemCount` — the reset effect\n * above clears focus whenever `itemCount` changes.)\n */\n const navigableCount = useCallback(() => {\n const container = containerRef.current;\n if (!container) return itemCount;\n const rendered = container.querySelectorAll(\"[data-nav-item]\").length;\n // No nav items marked up (a view that doesn't opt in) — trust itemCount.\n return rendered > 0 ? Math.min(rendered, itemCount) : itemCount;\n }, [itemCount]);\n\n /**\n * Where an *unset* cursor should land on its first move: the first item\n * that is still at or below the top of the viewport, so a user who has\n * scrolled halfway down a long grid isn't yanked back to item 0 by their\n * first ArrowDown. Returns -1 when geometry can't answer (no layout, no\n * container, everything scrolled past), leaving the caller on item 0.\n */\n const firstVisibleIndex = useCallback((): number => {\n const container = containerRef.current;\n const view = container?.ownerDocument?.defaultView;\n if (!container || !view) return -1;\n const viewportBottom =\n view.innerHeight || container.ownerDocument.documentElement.clientHeight;\n if (!viewportBottom) return -1;\n const items = container.querySelectorAll(\"[data-nav-item]\");\n for (let i = 0; i < items.length; i++) {\n const rect = (items[i] as HTMLElement).getBoundingClientRect();\n // A zero-height rect means \"no layout information\" (jsdom, display:none)\n // rather than \"at the very top\" — skip rather than guess.\n if (rect.height <= 0 && rect.width <= 0) continue;\n if (rect.bottom > 0 && rect.top < viewportBottom) return i;\n }\n return -1;\n }, []);\n\n /**\n * Move the cursor by `delta` and report whether the keystroke actually did\n * something. The caller uses the return value to decide whether to\n * `preventDefault()`: a default-on hook that swallows ArrowUp/Down even\n * when it has nothing to move would silently take page scrolling away from\n * every existing consumer — worst of all on an empty page, where there is\n * no cursor at all.\n */\n const step = useCallback(\n (delta: number, instant = false): boolean => {\n const max = navigableCount();\n // Empty / not-yet-mounted list: no cursor exists, so the key is not\n // ours. (Also guards the upward clamp from inventing index 0.)\n if (max <= 0) return false;\n\n const current = focusedIndex;\n const next =\n current < 0\n ? // First move from \"nothing focused\" adopts the top visible item\n // whichever direction it came from, rather than the row a\n // stride-sized jump would land on.\n Math.min(Math.max(firstVisibleIndex(), 0), max - 1)\n : delta > 0\n ? Math.min(current + delta, max - 1)\n : Math.max(current + delta, 0);\n\n if (next === current) {\n // Pinned at the *rendered* ceiling while more rows exist: scrolling\n // the last rendered item into view trips DataGrid's batch observer,\n // so the next keypress continues (see `navigableCount`). That is\n // real work, so the key stays ours.\n if (delta > 0 && max < itemCount) {\n scrollInstantRef.current = instant;\n setScrollRequest((n) => n + 1);\n return true;\n }\n // Genuinely at the top or the end of the data — hand the key back so\n // the browser can scroll the page.\n return false;\n }\n\n setIsActive(true);\n setFocusedIndex(next);\n scrollInstantRef.current = instant;\n setScrollRequest((n) => n + 1);\n return true;\n },\n [focusedIndex, firstVisibleIndex, itemCount, navigableCount],\n );\n\n // Committed scroll: the row exists in the DOM by the time this runs.\n useEffect(() => {\n // 0 = no step taken yet. A negative index is the Escape/itemCount-reset\n // path, where scrolling to a stale row would yank the viewport.\n if (scrollRequest === 0 || focusedIndex < 0) return;\n scrollToItem(focusedIndex, scrollInstantRef.current);\n }, [scrollRequest, focusedIndex, scrollToItem]);\n\n /**\n * Columns to step per vertical key press, measured live unless the caller\n * pinned it with the `columns` option.\n */\n const columnCount = useCallback((): number => {\n if (typeof columns === \"number\") return Math.max(1, Math.floor(columns));\n return measureColumns(containerRef.current);\n }, [columns]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const handler = (e: KeyboardEvent) => {\n // Never hijack a modified chord. Cmd/Ctrl+K (command palettes),\n // Cmd/Ctrl+X (cut), Alt+Arrow (browser history) and friends belong to\n // the app or the browser — a default-on list must not eat them.\n if (e.ctrlKey || e.metaKey || e.altKey) return;\n\n const target = e.target as HTMLElement | null;\n\n // Don't capture when typing in inputs/textareas\n if (isTypingTarget(target)) {\n // Only handle Escape to blur out of search\n if (e.key === \"Escape\") {\n target?.blur();\n e.preventDefault();\n }\n return;\n }\n\n // ── Dismissal keys — live even while an overlay covers the list,\n // since dismissing that overlay is exactly what they are for. Handled\n // ahead of the interactive-target guard below so ⌫ still works when\n // focus sits on the dialog itself (or a button inside it).\n if (e.key === \"Backspace\") {\n if (onBack && !keepsBackspace(target)) {\n e.preventDefault();\n onBack(focusedIndex);\n }\n return;\n }\n if (e.key === \"Escape\") {\n setFocusedIndex(-1);\n setIsActive(false);\n return;\n }\n\n // A focused control owns its own keys — notably Enter/Space. Without\n // this, Enter on a dialog's button would be prevented and re-trigger\n // the list's \"open focused row\" instead of activating the button.\n if (ownsItsKeys(target)) return;\n\n // Everything below moves or acts on the list cursor.\n if (overlayOpen) return;\n\n // Vertical keys move a whole row; horizontal keys move one cell. In a\n // single-column container the two are the same stride, which is why a\n // list keeps behaving exactly as before.\n switch (e.key) {\n case \"j\":\n case \"ArrowDown\": {\n // preventDefault only if the cursor moved: see `step`.\n if (step(columnCount(), e.repeat)) e.preventDefault();\n break;\n }\n case \"k\":\n case \"ArrowUp\": {\n if (step(-columnCount(), e.repeat)) e.preventDefault();\n break;\n }\n case \"ArrowRight\": {\n if (columnCount() <= 1) break;\n if (step(1, e.repeat)) e.preventDefault();\n break;\n }\n case \"ArrowLeft\": {\n if (columnCount() <= 1) break;\n if (step(-1, e.repeat)) e.preventDefault();\n break;\n }\n case \"Enter\": {\n // Re-check the live rendered ceiling — focus can outlive a\n // shrunk batch (filter/reset) without `itemCount` changing.\n if (\n focusedIndex >= 0 &&\n focusedIndex < navigableCount() &&\n onOpen\n ) {\n e.preventDefault();\n onOpen(focusedIndex);\n }\n break;\n }\n case \"x\": {\n if (\n focusedIndex >= 0 &&\n focusedIndex < navigableCount() &&\n onToggleSelect\n ) {\n e.preventDefault();\n onToggleSelect(focusedIndex);\n }\n break;\n }\n case \"/\": {\n if (searchInputRef?.current) {\n e.preventDefault();\n searchInputRef.current.focus();\n }\n break;\n }\n }\n };\n\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [\n enabled,\n overlayOpen,\n focusedIndex,\n onOpen,\n onToggleSelect,\n onBack,\n searchInputRef,\n step,\n navigableCount,\n columnCount,\n ]);\n\n return {\n focusedIndex,\n setFocusedIndex,\n containerRef,\n isActive,\n };\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import { Spinner } from './chunk-OVU3Z2MT.js';
2
+ import { cn } from './chunk-NZ5WBA4R.js';
3
+ import { cva } from 'class-variance-authority';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ var pageLoadingVariants = cva(
7
+ "flex flex-col items-center justify-center gap-3 px-4 text-center",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ page: "min-h-[50vh]",
12
+ section: "min-h-48 py-12"
13
+ }
14
+ },
15
+ defaultVariants: {
16
+ variant: "page"
17
+ }
18
+ }
19
+ );
20
+ function PageLoading({ label, variant, className, testID }) {
21
+ return /* @__PURE__ */ jsxs(
22
+ "div",
23
+ {
24
+ "data-slot": "page-loading",
25
+ "data-testid": testID,
26
+ role: "status",
27
+ className: cn(pageLoadingVariants({ variant }), className),
28
+ children: [
29
+ /* @__PURE__ */ jsx(Spinner, { size: variant === "section" ? "default" : "lg", "aria-hidden": "true" }),
30
+ label ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading" })
31
+ ]
32
+ }
33
+ );
34
+ }
35
+
36
+ export { PageLoading, pageLoadingVariants };
37
+ //# sourceMappingURL=chunk-YRYQXU3G.js.map
38
+ //# sourceMappingURL=chunk-YRYQXU3G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/patterns/page-loading.tsx"],"names":[],"mappings":";;;;;AAKA,IAAM,mBAAA,GAAsB,GAAA;AAAA,EAC1B,kEAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,OAAA,EAAS;AAAA,QACP,IAAA,EAAM,cAAA;AAAA,QACN,OAAA,EAAS;AAAA;AACX,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,OAAA,EAAS;AAAA;AACX;AAEJ;AAkBO,SAAS,YAAY,EAAE,KAAA,EAAO,OAAA,EAAS,SAAA,EAAW,QAAO,EAAqB;AACnF,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,WAAA,EAAU,cAAA;AAAA,MACV,aAAA,EAAa,MAAA;AAAA,MACb,IAAA,EAAK,QAAA;AAAA,MACL,WAAW,EAAA,CAAG,mBAAA,CAAoB,EAAE,OAAA,EAAS,GAAG,SAAS,CAAA;AAAA,MAEzD,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,WAAQ,IAAA,EAAM,OAAA,KAAY,YAAY,SAAA,GAAY,IAAA,EAAM,eAAY,MAAA,EAAO,CAAA;AAAA,QAC3E,KAAA,mBACC,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,+BAAA,EAAiC,QAAA,EAAA,KAAA,EAAM,CAAA,mBAEpD,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EAAU,QAAA,EAAA,SAAA,EAAO;AAAA;AAAA;AAAA,GAErC;AAEJ","file":"chunk-YRYQXU3G.js","sourcesContent":["import * as React from \"react\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { Spinner } from \"../ui/spinner\";\nimport { cn } from \"../../utils\";\n\nconst pageLoadingVariants = cva(\n \"flex flex-col items-center justify-center gap-3 px-4 text-center\",\n {\n variants: {\n variant: {\n page: \"min-h-[50vh]\",\n section: \"min-h-48 py-12\",\n },\n },\n defaultVariants: {\n variant: \"page\",\n },\n },\n);\n\nexport interface PageLoadingProps extends VariantProps<typeof pageLoadingVariants> {\n /** Optional message shown under the spinner (e.g. \"Loading dashboard…\"). */\n label?: string;\n className?: string;\n /** Stable test/automation selector (emitted as `data-testid`). */\n testID?: string;\n}\n\n/**\n * PageLoading — full-page or in-section loading state: a centered `Spinner`\n * with an optional message. The loading-state counterpart to `EmptyState`.\n * Reach for `Skeleton` instead when the placeholder should mimic the shape\n * of the content that's arriving rather than a generic wait state.\n *\n * @stability Beta\n */\nexport function PageLoading({ label, variant, className, testID }: PageLoadingProps) {\n return (\n <div\n data-slot=\"page-loading\"\n data-testid={testID}\n role=\"status\"\n className={cn(pageLoadingVariants({ variant }), className)}\n >\n <Spinner size={variant === \"section\" ? \"default\" : \"lg\"} aria-hidden=\"true\" />\n {label ? (\n <p className=\"text-sm text-muted-foreground\">{label}</p>\n ) : (\n <span className=\"sr-only\">Loading</span>\n )}\n </div>\n );\n}\n\nexport { pageLoadingVariants };\n"]}
@@ -1,13 +1,13 @@
1
1
  "use client";
2
2
  import { cn } from './chunk-NZ5WBA4R.js';
3
3
  import * as React from 'react';
4
- import * as AccordionPrimitive from '@radix-ui/react-accordion';
4
+ import { Accordion as Accordion$1 } from 'radix-ui';
5
5
  import { ChevronDown } from 'lucide-react';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
7
 
8
- var Accordion = AccordionPrimitive.Root;
8
+ var Accordion = Accordion$1.Root;
9
9
  var AccordionItem = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
10
- AccordionPrimitive.Item,
10
+ Accordion$1.Item,
11
11
  {
12
12
  ref,
13
13
  className: cn("border-b", className),
@@ -15,8 +15,8 @@ var AccordionItem = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
15
15
  }
16
16
  ));
17
17
  AccordionItem.displayName = "AccordionItem";
18
- var AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(AccordionPrimitive.Header, { className: "flex", children: /* @__PURE__ */ jsxs(
19
- AccordionPrimitive.Trigger,
18
+ var AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(Accordion$1.Header, { className: "flex", children: /* @__PURE__ */ jsxs(
19
+ Accordion$1.Trigger,
20
20
  {
21
21
  ref,
22
22
  className: cn(
@@ -30,9 +30,9 @@ var AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref)
30
30
  ]
31
31
  }
32
32
  ) }));
33
- AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
33
+ AccordionTrigger.displayName = Accordion$1.Trigger.displayName;
34
34
  var AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(
35
- AccordionPrimitive.Content,
35
+ Accordion$1.Content,
36
36
  {
37
37
  ref,
38
38
  className: "overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
@@ -40,8 +40,8 @@ var AccordionContent = React.forwardRef(({ className, children, ...props }, ref)
40
40
  children: /* @__PURE__ */ jsx("div", { className: cn("pb-4 pt-0", className), children })
41
41
  }
42
42
  ));
43
- AccordionContent.displayName = AccordionPrimitive.Content.displayName;
43
+ AccordionContent.displayName = Accordion$1.Content.displayName;
44
44
 
45
45
  export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
46
- //# sourceMappingURL=chunk-5J7BP5DI.js.map
47
- //# sourceMappingURL=chunk-5J7BP5DI.js.map
46
+ //# sourceMappingURL=chunk-YWFPMND7.js.map
47
+ //# sourceMappingURL=chunk-YWFPMND7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ui/accordion.tsx"],"names":["AccordionPrimitive"],"mappings":";;;;;;AAOA,IAAM,YAAYA,WAAA,CAAmB;AAErC,IAAM,aAAA,GAAsB,iBAG1B,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC1B,GAAA;AAAA,EAACA,WAAA,CAAmB,IAAA;AAAA,EAAnB;AAAA,IACC,GAAA;AAAA,IACA,SAAA,EAAW,EAAA,CAAG,UAAA,EAAY,SAAS,CAAA;AAAA,IAClC,GAAG;AAAA;AACN,CACD;AACD,aAAA,CAAc,WAAA,GAAc,eAAA;AAE5B,IAAM,gBAAA,GAAyB,KAAA,CAAA,UAAA,CAG7B,CAAC,EAAE,WAAW,QAAA,EAAU,GAAG,KAAA,EAAM,EAAG,wBACpC,GAAA,CAACA,WAAA,CAAmB,MAAA,EAAnB,EAA0B,WAAU,MAAA,EACnC,QAAA,kBAAA,IAAA;AAAA,EAACA,WAAA,CAAmB,OAAA;AAAA,EAAnB;AAAA,IACC,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACT,8HAAA;AAAA,MACA;AAAA,KACF;AAAA,IACC,GAAG,KAAA;AAAA,IAEH,QAAA,EAAA;AAAA,MAAA,QAAA;AAAA,sBACD,GAAA,CAAC,WAAA,EAAA,EAAY,SAAA,EAAU,oDAAA,EAAqD;AAAA;AAAA;AAC9E,CAAA,EACF,CACD;AACD,gBAAA,CAAiB,WAAA,GAAcA,YAAmB,OAAA,CAAQ,WAAA;AAE1D,IAAM,gBAAA,GAAyB,iBAG7B,CAAC,EAAE,WAAW,QAAA,EAAU,GAAG,KAAA,EAAM,EAAG,GAAA,qBACpC,GAAA;AAAA,EAACA,WAAA,CAAmB,OAAA;AAAA,EAAnB;AAAA,IACC,GAAA;AAAA,IACA,SAAA,EAAU,0HAAA;AAAA,IACT,GAAG,KAAA;AAAA,IAEJ,8BAAC,KAAA,EAAA,EAAI,SAAA,EAAW,GAAG,WAAA,EAAa,SAAS,GAAI,QAAA,EAAS;AAAA;AACxD,CACD;AACD,gBAAA,CAAiB,WAAA,GAAcA,YAAmB,OAAA,CAAQ,WAAA","file":"chunk-YWFPMND7.js","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Accordion as AccordionPrimitive } from \"radix-ui\";\nimport { ChevronDown } from \"lucide-react\";\nimport { cn } from \"../../utils\";\n\nconst Accordion = AccordionPrimitive.Root;\n\nconst AccordionItem = React.forwardRef<\n React.ComponentRef<typeof AccordionPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <AccordionPrimitive.Item\n ref={ref}\n className={cn(\"border-b\", className)}\n {...props}\n />\n));\nAccordionItem.displayName = \"AccordionItem\";\n\nconst AccordionTrigger = React.forwardRef<\n React.ComponentRef<typeof AccordionPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n <AccordionPrimitive.Header className=\"flex\">\n <AccordionPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronDown className=\"h-4 w-4 shrink-0 transition-transform duration-200\" />\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n));\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;\n\nconst AccordionContent = React.forwardRef<\n React.ComponentRef<typeof AccordionPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <AccordionPrimitive.Content\n ref={ref}\n className=\"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down\"\n {...props}\n >\n <div className={cn(\"pb-4 pt-0\", className)}>{children}</div>\n </AccordionPrimitive.Content>\n));\nAccordionContent.displayName = AccordionPrimitive.Content.displayName;\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"]}
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { toggleVariants } from './chunk-C46ITXE4.js';
3
+ import { buttonVariants } from './chunk-NWSCYLPT.js';
4
+ import { cn } from './chunk-NZ5WBA4R.js';
5
+ import * as React from 'react';
6
+ import { Toolbar as Toolbar$1 } from 'radix-ui';
7
+ import { cva } from 'class-variance-authority';
8
+ import { jsx } from 'react/jsx-runtime';
9
+
10
+ var toolbarVariants = cva(
11
+ "flex items-center gap-1 rounded-md border border-border bg-background p-1",
12
+ {
13
+ variants: {
14
+ orientation: {
15
+ horizontal: "flex-row",
16
+ vertical: "w-fit flex-col"
17
+ }
18
+ },
19
+ defaultVariants: {
20
+ orientation: "horizontal"
21
+ }
22
+ }
23
+ );
24
+ var Toolbar = React.forwardRef(({ className, orientation = "horizontal", testID, ...props }, ref) => /* @__PURE__ */ jsx(
25
+ Toolbar$1.Root,
26
+ {
27
+ ref,
28
+ orientation,
29
+ "data-testid": testID,
30
+ className: cn(toolbarVariants({ orientation }), className),
31
+ ...props
32
+ }
33
+ ));
34
+ Toolbar.displayName = Toolbar$1.Root.displayName;
35
+ var ToolbarButton = React.forwardRef(({ className, variant = "ghost", size = "sm", testID, ...props }, ref) => /* @__PURE__ */ jsx(
36
+ Toolbar$1.Button,
37
+ {
38
+ ref,
39
+ "data-testid": testID,
40
+ className: cn(buttonVariants({ variant, size }), className),
41
+ ...props
42
+ }
43
+ ));
44
+ ToolbarButton.displayName = Toolbar$1.Button.displayName;
45
+ var ToolbarLink = React.forwardRef(({ className, testID, ...props }, ref) => /* @__PURE__ */ jsx(
46
+ Toolbar$1.Link,
47
+ {
48
+ ref,
49
+ "data-testid": testID,
50
+ className: cn(
51
+ "inline-flex h-9 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium text-foreground underline-offset-4 ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
52
+ className
53
+ ),
54
+ ...props
55
+ }
56
+ ));
57
+ ToolbarLink.displayName = Toolbar$1.Link.displayName;
58
+ var ToolbarSeparator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
59
+ Toolbar$1.Separator,
60
+ {
61
+ ref,
62
+ className: cn(
63
+ "shrink-0 bg-border",
64
+ "data-[orientation=vertical]:mx-1 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-4 data-[orientation=vertical]:w-px",
65
+ "data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full",
66
+ className
67
+ ),
68
+ ...props
69
+ }
70
+ ));
71
+ ToolbarSeparator.displayName = Toolbar$1.Separator.displayName;
72
+ var ToolbarToggleGroup = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
73
+ Toolbar$1.ToggleGroup,
74
+ {
75
+ ref,
76
+ className: cn("flex items-center gap-1", className),
77
+ ...props
78
+ }
79
+ ));
80
+ ToolbarToggleGroup.displayName = Toolbar$1.ToggleGroup.displayName;
81
+ var ToolbarToggleItem = React.forwardRef(({ className, variant, size = "sm", testID, ...props }, ref) => /* @__PURE__ */ jsx(
82
+ Toolbar$1.ToggleItem,
83
+ {
84
+ ref,
85
+ "data-testid": testID,
86
+ className: cn(toggleVariants({ variant, size }), className),
87
+ ...props
88
+ }
89
+ ));
90
+ ToolbarToggleItem.displayName = Toolbar$1.ToggleItem.displayName;
91
+
92
+ export { Toolbar, ToolbarButton, ToolbarLink, ToolbarSeparator, ToolbarToggleGroup, ToolbarToggleItem, toolbarVariants };
93
+ //# sourceMappingURL=chunk-YYEGAFUE.js.map
94
+ //# sourceMappingURL=chunk-YYEGAFUE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ui/toolbar.tsx"],"names":["ToolbarPrimitive"],"mappings":"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","file":"chunk-YYEGAFUE.js","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Toolbar as ToolbarPrimitive } from \"radix-ui\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\n\nimport { cn } from \"../../utils\";\nimport { buttonVariants } from \"./button\";\nimport { toggleVariants } from \"./toggle\";\n\nexport const toolbarVariants = cva(\n \"flex items-center gap-1 rounded-md border border-border bg-background p-1\",\n {\n variants: {\n orientation: {\n horizontal: \"flex-row\",\n vertical: \"w-fit flex-col\",\n },\n },\n defaultVariants: {\n orientation: \"horizontal\",\n },\n }\n);\n\nexport interface ToolbarProps\n extends React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Root> {\n /** Stable test/automation selector (emitted as `data-testid`). */\n testID?: string;\n}\n\n/**\n * Toolbar — a grouped set of controls that behaves as ONE tab stop.\n *\n * This is a primitive, not a layout, and the distinction is the whole reason\n * it wraps Radix instead of a `<div className=\"flex gap-1\">`. Radix's Toolbar\n * owns a roving tabindex: Tab moves into and out of the whole group, and\n * Arrow/Home/End move between the items inside it. A layout, by the framework's\n * own definition, \"positions things without knowing what they are\" — this one\n * knows its children are focusable controls and manages their focus, so it\n * fails that test and passes the primitive one.\n *\n * `orientation` is passed to BOTH Radix (which uses it to decide whether\n * Left/Right or Up/Down are the navigation keys) and the CVA class (which uses\n * it to decide the flex direction). Splitting those would let the visual axis\n * and the keyboard axis disagree — arrows moving \"down\" a row of buttons.\n *\n * @stability Beta\n */\nexport const Toolbar = React.forwardRef<\n React.ComponentRef<typeof ToolbarPrimitive.Root>,\n ToolbarProps\n>(({ className, orientation = \"horizontal\", testID, ...props }, ref) => (\n <ToolbarPrimitive.Root\n ref={ref}\n orientation={orientation}\n data-testid={testID}\n className={cn(toolbarVariants({ orientation }), className)}\n {...props}\n />\n));\nToolbar.displayName = ToolbarPrimitive.Root.displayName;\n\nexport interface ToolbarButtonProps\n extends React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Button>,\n VariantProps<typeof buttonVariants> {\n /** Stable test/automation selector (emitted as `data-testid`). */\n testID?: string;\n}\n\n/**\n * ToolbarButton — a button that participates in the toolbar's roving tabindex.\n *\n * Styling reuses `buttonVariants` rather than declaring a second button look:\n * per the framework's \"one home per concept\", the question \"what does a button\n * look like?\" already has an answer. Only the DEFAULTS differ — `ghost`/`sm`,\n * because a toolbar is a dense strip of controls, not a page's primary action —\n * and both remain overridable.\n *\n * @stability Beta\n */\nexport const ToolbarButton = React.forwardRef<\n React.ComponentRef<typeof ToolbarPrimitive.Button>,\n ToolbarButtonProps\n>(({ className, variant = \"ghost\", size = \"sm\", testID, ...props }, ref) => (\n <ToolbarPrimitive.Button\n ref={ref}\n data-testid={testID}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n));\nToolbarButton.displayName = ToolbarPrimitive.Button.displayName;\n\nexport interface ToolbarLinkProps\n extends React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Link> {\n /** Stable test/automation selector (emitted as `data-testid`). */\n testID?: string;\n}\n\n/**\n * ToolbarLink — an anchor inside the toolbar's roving tabindex.\n *\n * Kept distinct from `ToolbarButton` because the element matters: a link\n * navigates and must stay an `<a>` for middle-click, \"open in new tab\" and the\n * screen-reader link rotor. Radix also gives it the toolbar's Enter/Space\n * handling without turning it into a button.\n *\n * @stability Beta\n */\nexport const ToolbarLink = React.forwardRef<\n React.ComponentRef<typeof ToolbarPrimitive.Link>,\n ToolbarLinkProps\n>(({ className, testID, ...props }, ref) => (\n <ToolbarPrimitive.Link\n ref={ref}\n data-testid={testID}\n className={cn(\n \"inline-flex h-9 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium text-foreground underline-offset-4 ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n className\n )}\n {...props}\n />\n));\nToolbarLink.displayName = ToolbarPrimitive.Link.displayName;\n\nexport interface ToolbarSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Separator> {}\n\n/**\n * ToolbarSeparator — a decorative divider between toolbar groups.\n *\n * Radix excludes it from the roving tabindex, so arrow keys skip straight over\n * it instead of landing on a 1px line.\n *\n * Note the inverted axis, which is easy to get backwards: Radix gives the\n * separator the orientation PERPENDICULAR to the toolbar's, so a horizontal\n * toolbar contains separators with `data-orientation=\"vertical\"`. The classes\n * below are keyed on the separator's own orientation accordingly — a vertical\n * separator is the tall thin rule.\n *\n * @stability Beta\n */\nexport const ToolbarSeparator = React.forwardRef<\n React.ComponentRef<typeof ToolbarPrimitive.Separator>,\n ToolbarSeparatorProps\n>(({ className, ...props }, ref) => (\n <ToolbarPrimitive.Separator\n ref={ref}\n className={cn(\n \"shrink-0 bg-border\",\n \"data-[orientation=vertical]:mx-1 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-4 data-[orientation=vertical]:w-px\",\n \"data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full\",\n className\n )}\n {...props}\n />\n));\nToolbarSeparator.displayName = ToolbarPrimitive.Separator.displayName;\n\n/**\n * ToolbarToggleGroup — a set of pressed/unpressed controls inside a toolbar\n * (the classic bold / italic / underline cluster).\n *\n * @stability Beta\n */\nexport const ToolbarToggleGroup = React.forwardRef<\n React.ComponentRef<typeof ToolbarPrimitive.ToggleGroup>,\n React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleGroup>\n>(({ className, ...props }, ref) => (\n <ToolbarPrimitive.ToggleGroup\n ref={ref}\n className={cn(\"flex items-center gap-1\", className)}\n {...props}\n />\n));\nToolbarToggleGroup.displayName = ToolbarPrimitive.ToggleGroup.displayName;\n\nexport interface ToolbarToggleItemProps\n extends React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleItem>,\n VariantProps<typeof toggleVariants> {\n /** Stable test/automation selector (emitted as `data-testid`). */\n testID?: string;\n}\n\n/**\n * ToolbarToggleItem — one member of a `ToolbarToggleGroup`.\n *\n * Reuses `toggleVariants` for the same reason `ToolbarButton` reuses\n * `buttonVariants`: `Toggle` already answers \"what does a pressed control look\n * like?\", and a second answer is how two implementations get born.\n *\n * @stability Beta\n */\nexport const ToolbarToggleItem = React.forwardRef<\n React.ComponentRef<typeof ToolbarPrimitive.ToggleItem>,\n ToolbarToggleItemProps\n>(({ className, variant, size = \"sm\", testID, ...props }, ref) => (\n <ToolbarPrimitive.ToggleItem\n ref={ref}\n data-testid={testID}\n className={cn(toggleVariants({ variant, size }), className)}\n {...props}\n />\n));\nToolbarToggleItem.displayName = ToolbarPrimitive.ToggleItem.displayName;\n"]}