@eifi1/ui-kit 0.4.1 → 0.5.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 (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +89 -19
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +74 -19
  63. package/dist/components/dropdown.js +72 -22
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +21 -7
  75. package/dist/components/file-dropzone.js +28 -7
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +21 -2
  105. package/dist/components/number-input.js +2 -1
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +25 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +5 -3
  176. package/dist/components/user-avatar.js +4 -2
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +14 -1
  270. package/dist/theme/palette-presets.js +231 -3
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +66 -13
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -0,0 +1,153 @@
1
+ import type { ComponentPropsWithoutRef } from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ export interface ToggleOption<T extends string> {
5
+ value: T;
6
+ label: string;
7
+ className?: string;
8
+ }
9
+
10
+ /**
11
+ * `onChange` is the group's own — the chosen VALUE, not a DOM event — so the div's
12
+ * `onChange` is omitted rather than shadowed: leaving both in scope would give the
13
+ * prop two incompatible meanings depending on which overload TypeScript picked.
14
+ *
15
+ * `children` is omitted too: this renders its `options` and nothing else, so a `children` the type
16
+ * accepted and the component ignored would be a prop that silently does nothing —
17
+ * worse than one that does not compile.
18
+ */
19
+ export interface ToggleGroupProps<T extends string>
20
+ extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
21
+ value: T;
22
+ onChange: (value: T) => void;
23
+ options: ToggleOption<T>[];
24
+ className?: string;
25
+ /** Applied to every option button (e.g. to tune height/rounding to match
26
+ * adjacent fields). Per-option `className` still wins over this. */
27
+ optionClassName?: string;
28
+ /**
29
+ * @deprecated Pass `aria-label` instead — the DOM spelling, which every other
30
+ * control in this kit now answers to. Kept working because three applications ship
31
+ * this one today; it names the group only when `aria-label` is absent.
32
+ */
33
+ ariaLabel?: string;
34
+ /**
35
+ * Show, refuse the change (Keksdose live #288: a payment dated in the future has no
36
+ * state to set).
37
+ *
38
+ * Whatever `value` says stays pressed and keeps its own fill rather than going grey
39
+ * with the rest — a reader who cannot see WHICH option is chosen has been told less
40
+ * than before it was disabled. A caller with nothing to show passes no value, and
41
+ * the group renders dimmed with nothing pressed, which is the shape Keksdose's
42
+ * status picker uses for a row whose status does not exist yet.
43
+ *
44
+ * On the whole GROUP, not per option: a segmented control where some segments are
45
+ * live and others are not is a menu with holes in it, and no caller here wants one.
46
+ */
47
+ disabled?: boolean;
48
+ }
49
+
50
+ export function ToggleGroup<T extends string>({
51
+ value,
52
+ onChange,
53
+ options,
54
+ className,
55
+ optionClassName,
56
+ ariaLabel,
57
+ disabled = false,
58
+ "aria-label": ariaLabelAttr,
59
+ ...rest
60
+ }: ToggleGroupProps<T>) {
61
+ return (
62
+ <div
63
+ // The audit's named example of a closed prop list (§"Public API design"): the
64
+ // tour locates a step by CSS SELECTOR, so a component that drops every attribute
65
+ // it was not expecting cannot be spotlighted at all — and Keksdose's rule editor
66
+ // carries a comment explaining that it wraps this group in a bare <div> for
67
+ // exactly that reason.
68
+ //
69
+ // `...rest` first, then the attributes the group cannot do without: a caller
70
+ // hanging an anchor or a test id on the group must not be able to overwrite the
71
+ // radiogroup role or the disabled state by accident. `className` is destructured
72
+ // out entirely and merged through `cn`, so it is never in here.
73
+ {...rest}
74
+ role="radiogroup"
75
+ // The DOM spelling wins; `ariaLabel` is the fallback for the call sites that
76
+ // have not moved yet.
77
+ aria-label={ariaLabelAttr ?? ariaLabel}
78
+ // `aria-disabled` on the group as well as `disabled` on each button: a radio
79
+ // group is what the user is being refused, and a screen reader announcing
80
+ // three separately-disabled radios does not say that.
81
+ aria-disabled={disabled || undefined}
82
+ className={cn(
83
+ // `gap-0.5` — the same 2px as the container's own padding, so EVERY segment
84
+ // sits in a uniform 2px moat and no two fills ever touch. Flush segments were
85
+ // Keksdose live #268's rework: the pressed segment wears a saturated fill and
86
+ // an unpressed neighbour wears a pale hover fill, and with a shared edge the
87
+ // two rectangles read as one smeared shape — *"the boundary of the selected
88
+ // option and hovering next to it overlays the boundary of the selected
89
+ // button"*. A gap is what makes each segment its own chip; it cannot be
90
+ // undone by a caller's per-option colour, which a hover-only fix could.
91
+ //
92
+ // (The hover fill is the DESKTOP half of that report: Tailwind v4 wraps every
93
+ // `hover:` in `@media (hover: hover)`, so a phone never paints it. The half a
94
+ // phone does see is the focus ring — see the segment's own note below.)
95
+ "inline-flex w-full gap-0.5 rounded-md border border-[var(--border-strong)] bg-[var(--bg-surface)] p-0.5 shadow-sm",
96
+ // The whole group fades, the way every other disabled control in this
97
+ // package does; `cursor-not-allowed` is on the buttons, which is what a
98
+ // pointer is actually over.
99
+ disabled && "opacity-60",
100
+ className,
101
+ )}
102
+ >
103
+ {options.map((opt) => {
104
+ const active = opt.value === value;
105
+ return (
106
+ <button
107
+ key={opt.value}
108
+ type="button"
109
+ role="radio"
110
+ aria-checked={active}
111
+ disabled={disabled}
112
+ onClick={() => onChange(opt.value)}
113
+ className={cn(
114
+ // `truncate` (which carries whitespace-nowrap) rather than letting a
115
+ // label wrap: a segmented control sizes its whole row to the tallest
116
+ // option, so one two-word option — Keksdose feedback #147's "Where I
117
+ // am" — silently doubles the height of every segment beside it.
118
+ //
119
+ // `basis-auto` is what keeps that ellipsis a LAST resort rather than
120
+ // the normal state (Keksdose dev#475). With flex-1's `basis-0`, a
121
+ // shrink-to-fit group (`w-auto`) still resolves to the sum of the
122
+ // labels' widths — and then splits it EQUALLY, so the short option
123
+ // got 66px it did not need and "Where I am" got 66 of the 81 it did:
124
+ // truncated at 1778px of free screen. Basing each segment on its own
125
+ // content and sharing only the LEFTOVER space keeps a full-width
126
+ // group's segments near-equal and an auto-width group's exact.
127
+ //
128
+ // `focus-visible` + `ring-inset`, not `focus` + an outset ring. A ring
129
+ // is a box-shadow that spreads OUTWARD, so on a flush group it painted
130
+ // 2px of ring over both neighbours and over the container's own border
131
+ // — and on a phone it appeared on every TAP, because a tap focuses the
132
+ // button. That is the other half of what live #268's rework saw
133
+ // overlaying the selected segment's boundary. Inset keeps the ring
134
+ // inside the segment it belongs to; focus-visible keeps it for the
135
+ // keyboard, which is the only input that needs it.
136
+ "min-w-0 flex-1 basis-auto truncate rounded px-3 py-1.5 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--border-strong)]",
137
+ active
138
+ ? "bg-[var(--bg-inverse)] text-[var(--text-inverse)]"
139
+ : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]",
140
+ // No hover fill on a group that cannot be changed — a segment that
141
+ // lights up under the pointer is an offer, and there is none here.
142
+ disabled && "cursor-not-allowed hover:bg-transparent dark:hover:bg-transparent",
143
+ optionClassName,
144
+ opt.className,
145
+ )}
146
+ >
147
+ {opt.label}
148
+ </button>
149
+ );
150
+ })}
151
+ </div>
152
+ );
153
+ }
@@ -0,0 +1,229 @@
1
+ // A legend whose entries are SWITCHES — and the stroke table it shares with
2
+ // `SeriesChart`. Lifted out of lenkbank's measurement plots, where ten legends over
3
+ // thirteen charts took channels on and off the picture.
4
+ //
5
+ // No recharts in this module: a legend of switches is plain buttons, and it is as
6
+ // useful under a chart the consumer drew by hand as under `SeriesChart`.
7
+ import type { ReactNode } from "react";
8
+ import { cn } from "../lib/cn";
9
+ import { useKitLabels } from "../i18n/kit-labels";
10
+ import { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels } from "./series-chart-labels";
11
+
12
+ /**
13
+ * The strokes a reader can tell apart, in the order they are handed out.
14
+ *
15
+ * For the charts where the colour is already spoken for: a comparison draws each
16
+ * measurement in its own colour, so a chart carrying several CHANNELS of each has
17
+ * nothing left but the stroke — and "solid versus dashed" only gets you two.
18
+ *
19
+ * **Five, and the fifth is the last one.** They are the draughtsman's line types, in
20
+ * the draughtsman's order — continuous, dashed, dotted, dash-dot, dash-dot-dot — the
21
+ * one family of five a century of drawings has already proved legible at a hair's
22
+ * width. A sixth is not distinguishable from one of these at two pixels wide, so a
23
+ * chart needing one is a chart that should be split.
24
+ *
25
+ * Kept here, beside the legend's own mark, and read by `SeriesChart` from here — so
26
+ * the key under a chart and the lines on it cannot disagree.
27
+ */
28
+ export const STROKE_PATTERNS: readonly (string | undefined)[] = [
29
+ undefined,
30
+ "5 4",
31
+ "1 3",
32
+ "9 3 2 3",
33
+ "9 3 2 3 2 3",
34
+ ];
35
+
36
+ /** The dash array for the nth pattern, wrapping past the fifth. */
37
+ export function strokeDash(order: number): string | undefined {
38
+ const n = STROKE_PATTERNS.length;
39
+ return STROKE_PATTERNS[((Math.trunc(order) % n) + n) % n];
40
+ }
41
+
42
+ /** Which of the patterns a `step` series is drawn in.
43
+ *
44
+ * A whole-number channel that jumps rather than travels is drawn dashed, and it has
45
+ * to be one of {@link STROKE_PATTERNS} rather than a dash array written out at the
46
+ * call site: a legend entry says its pattern by index, so a step line drawn in
47
+ * anything else would be a legend promising a solid line for a dashed one. */
48
+ export const STEP_DASH = 1;
49
+
50
+ /**
51
+ * One key switched, as a new hidden set.
52
+ *
53
+ * Every caller writes `onToggle={(key) => setHidden(toggleHidden(hidden, key))}`. A
54
+ * new set rather than a mutated one, because the state is read during render and a
55
+ * mutation of something a component already holds is invisible to React.
56
+ */
57
+ export function toggleHidden(hidden: ReadonlySet<string>, key: string): ReadonlySet<string> {
58
+ const next = new Set(hidden);
59
+ if (!next.delete(key)) next.add(key);
60
+ return next;
61
+ }
62
+
63
+ export interface LegendEntry {
64
+ key: string;
65
+ label: ReactNode;
66
+ /** Any CSS colour — `paletteFor(i)` for the kit's own ramp. */
67
+ color: string;
68
+ /**
69
+ * What the entry's mark looks like. `swatch` — a filled square, the default — says
70
+ * WHICH MEASUREMENT. `stroke` says WHICH QUANTITY, for the charts that carry both at
71
+ * once and tell them apart by colour and by dash.
72
+ */
73
+ marker?: "swatch" | "stroke";
74
+ /** Which of {@link STROKE_PATTERNS}, when the marker is a stroke — the same index
75
+ * `SeriesChartSeries.dash` takes, so a legend cannot promise a dot-dash the plot
76
+ * draws dashed. */
77
+ dash?: number;
78
+ }
79
+
80
+ export interface ToggleLegendProps {
81
+ entries: LegendEntry[];
82
+ /** The keys currently off the chart. */
83
+ hidden: ReadonlySet<string>;
84
+ onToggle: (key: string) => void;
85
+ /** `vertical` is for a legend standing BESIDE the charts rather than under them —
86
+ * what a row of charts sharing one legend wants, since under two charts there is
87
+ * no "under", and putting it under one says it belongs to that one. */
88
+ orientation?: "horizontal" | "vertical";
89
+ /**
90
+ * Draw a legend of ONE entry rather than nothing.
91
+ *
92
+ * A single line's legend is usually noise. It is not where the legend is the only
93
+ * SWITCH that line has — a series that starts hidden and has no entry to bring it
94
+ * back is gone for good.
95
+ */
96
+ showSingle?: boolean;
97
+ className?: string;
98
+ /** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */
99
+ labels?: Partial<SeriesChartLabels>;
100
+ }
101
+
102
+ /**
103
+ * A legend whose entries are switches.
104
+ *
105
+ * `ChartLegendContent` with `onItemClick`/`activeKey` is the other kind: clicking an
106
+ * entry HIGHLIGHTS it and dims the rest, which is right when every series belongs on
107
+ * the chart and one of them is momentarily interesting. This is for the opposite —
108
+ * three coordinates on one axis where y runs to 800 and z barely moves, and the only
109
+ * way to see z is to take y off the chart entirely. Any number may be off at once.
110
+ *
111
+ * A hidden entry keeps its mark and its place. Removing it would reflow the legend on
112
+ * every click and lose the one thing it is for: saying what COULD be shown.
113
+ *
114
+ * Each entry is a real `<button>` with `aria-pressed` — pressed means "on the chart" —
115
+ * so Tab reaches every switch and Space/Enter flips it.
116
+ */
117
+ export function ToggleLegend({
118
+ entries,
119
+ hidden,
120
+ onToggle,
121
+ orientation = "horizontal",
122
+ showSingle = false,
123
+ className,
124
+ labels: labelsProp,
125
+ }: ToggleLegendProps) {
126
+ const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
127
+ if (entries.length < (showSingle ? 1 : 2)) return null;
128
+ return (
129
+ <div
130
+ role="group"
131
+ aria-label={labels.legend}
132
+ className={cn(
133
+ "flex gap-x-3 gap-y-1",
134
+ orientation === "vertical"
135
+ ? "flex-col items-start justify-center"
136
+ : "mt-2 flex-wrap items-center",
137
+ className,
138
+ )}
139
+ >
140
+ {entries.map((entry) => {
141
+ const off = hidden.has(entry.key);
142
+ return (
143
+ <button
144
+ key={entry.key}
145
+ type="button"
146
+ // Pressed rather than a checkbox: it is a control over what the chart
147
+ // draws, and "not pressed" is the same sentence the dimmed mark says.
148
+ aria-pressed={!off}
149
+ onClick={() => onToggle(entry.key)}
150
+ className={cn(
151
+ "flex items-center gap-1.5 rounded text-start text-[11px] text-[var(--text-secondary)] transition-opacity hover:opacity-80",
152
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]",
153
+ off && "opacity-35",
154
+ )}
155
+ >
156
+ <LegendMark entry={entry} off={off} />
157
+ {entry.label}
158
+ </button>
159
+ );
160
+ })}
161
+ </div>
162
+ );
163
+ }
164
+
165
+ /**
166
+ * The column a vertical legend stands in: beside the charts, on their horizontal
167
+ * centre line. `h-full` + `justify-center`, so it centres in a grid cell or a flex row
168
+ * that stretches it — and stacks whatever it is given, which is what a panel showing
169
+ * two legends at once needs. The width is the caller's: how wide a legend column is
170
+ * is a fact about that screen's layout.
171
+ */
172
+ export function LegendColumn({ children, className }: { children: ReactNode; className?: string }) {
173
+ return (
174
+ <div className={cn("flex h-full flex-col justify-center gap-3", className)}>{children}</div>
175
+ );
176
+ }
177
+
178
+ /**
179
+ * A legend and what it is a legend OF, as a headed column.
180
+ *
181
+ * For legends that are several vocabularies at once — which measurement and which
182
+ * channel — so the entries are read down a group and the groups across. Wrapped rows
183
+ * would put the second half of one group on a line under the first half of the next.
184
+ */
185
+ export function LegendGroup({ title, children }: { title: ReactNode; children: ReactNode }) {
186
+ return (
187
+ <div className="flex min-w-0 flex-col items-start gap-1">
188
+ <span className="text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]">
189
+ {title}
190
+ </span>
191
+ <div className="flex flex-col items-start gap-y-0.5">{children}</div>
192
+ </div>
193
+ );
194
+ }
195
+
196
+ /** The entry's mark: a square for a measurement, a stroke for a quantity. A hidden one
197
+ * keeps its outline — a mark that vanished would leave a line of text with nothing in
198
+ * front of it.
199
+ *
200
+ * A stroke is an SVG line with the chart's own `strokeDasharray`, not a CSS gradient
201
+ * approximating it: a gradient can say "solid or dashed" and nothing more. */
202
+ function LegendMark({ entry, off }: { entry: LegendEntry; off: boolean }) {
203
+ if (entry.marker === "stroke") {
204
+ return (
205
+ <svg aria-hidden viewBox="0 0 20 2" className="h-0.5 w-5 shrink-0 overflow-visible">
206
+ <line
207
+ x1={0}
208
+ y1={1}
209
+ x2={20}
210
+ y2={1}
211
+ stroke={entry.color}
212
+ strokeWidth={2}
213
+ strokeDasharray={strokeDash(entry.dash ?? 0)}
214
+ opacity={off ? 0.35 : 1}
215
+ />
216
+ </svg>
217
+ );
218
+ }
219
+ return (
220
+ <span
221
+ aria-hidden
222
+ className="h-2.5 w-2.5 shrink-0 rounded-[3px]"
223
+ style={{
224
+ backgroundColor: off ? "transparent" : entry.color,
225
+ boxShadow: `inset 0 0 0 1.5px ${entry.color}`,
226
+ }}
227
+ />
228
+ );
229
+ }