@eifi1/ui-kit 0.10.0 → 0.12.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 (382) hide show
  1. package/README.md +66 -37
  2. package/dist/chart.d.ts +3 -1
  3. package/dist/chart.js +2 -0
  4. package/dist/chart.js.map +1 -1
  5. package/dist/components/account-settings-labels.d.ts +105 -0
  6. package/dist/components/account-settings-labels.js +81 -0
  7. package/dist/components/account-settings-labels.js.map +1 -0
  8. package/dist/components/account-settings.d.ts +36 -52
  9. package/dist/components/account-settings.js +44 -8
  10. package/dist/components/account-settings.js.map +1 -1
  11. package/dist/components/alert-banner.d.ts +26 -1
  12. package/dist/components/alert-banner.js +10 -7
  13. package/dist/components/alert-banner.js.map +1 -1
  14. package/dist/components/amount-input.d.ts +26 -4
  15. package/dist/components/amount-input.js +5 -2
  16. package/dist/components/amount-input.js.map +1 -1
  17. package/dist/components/authed-image.d.ts +68 -0
  18. package/dist/components/authed-image.js +117 -0
  19. package/dist/components/authed-image.js.map +1 -0
  20. package/dist/components/breadcrumbs.d.ts +2 -1
  21. package/dist/components/breadcrumbs.js +5 -2
  22. package/dist/components/breadcrumbs.js.map +1 -1
  23. package/dist/components/bulk-action-bar.d.ts +51 -5
  24. package/dist/components/bulk-action-bar.js +88 -41
  25. package/dist/components/bulk-action-bar.js.map +1 -1
  26. package/dist/components/button-group.d.ts +107 -3
  27. package/dist/components/button-group.js +60 -5
  28. package/dist/components/button-group.js.map +1 -1
  29. package/dist/components/calculator.d.ts +17 -4
  30. package/dist/components/calendar-heatmap.d.ts +162 -0
  31. package/dist/components/calendar-heatmap.js +336 -0
  32. package/dist/components/calendar-heatmap.js.map +1 -0
  33. package/dist/components/chip.d.ts +2 -1
  34. package/dist/components/chip.js +6 -3
  35. package/dist/components/chip.js.map +1 -1
  36. package/dist/components/choice-card.d.ts +66 -2
  37. package/dist/components/choice-card.js +108 -1
  38. package/dist/components/choice-card.js.map +1 -1
  39. package/dist/components/combobox-core.js +2 -0
  40. package/dist/components/combobox-core.js.map +1 -1
  41. package/dist/components/combobox.d.ts +2 -2
  42. package/dist/components/combobox.js +31 -9
  43. package/dist/components/combobox.js.map +1 -1
  44. package/dist/components/copy-button.d.ts +52 -70
  45. package/dist/components/copy-button.js +1 -1
  46. package/dist/components/copy-button.js.map +1 -1
  47. package/dist/components/data-table-cells.d.ts +63 -0
  48. package/dist/components/data-table-cells.js +64 -0
  49. package/dist/components/data-table-cells.js.map +1 -0
  50. package/dist/components/data-table-filter-popover.d.ts +1 -1
  51. package/dist/components/data-table-filters.d.ts +1 -1
  52. package/dist/components/data-table-filters.js +31 -1
  53. package/dist/components/data-table-filters.js.map +1 -1
  54. package/dist/components/data-table-labels.d.ts +13 -0
  55. package/dist/components/data-table-labels.js +8 -1
  56. package/dist/components/data-table-labels.js.map +1 -1
  57. package/dist/components/data-table.d.ts +1 -1
  58. package/dist/components/data-table.js +104 -12
  59. package/dist/components/data-table.js.map +1 -1
  60. package/dist/components/date-picker.d.ts +3 -1
  61. package/dist/components/date-picker.js +15 -2
  62. package/dist/components/date-picker.js.map +1 -1
  63. package/dist/components/description-list.d.ts +23 -3
  64. package/dist/components/description-list.js +23 -3
  65. package/dist/components/description-list.js.map +1 -1
  66. package/dist/components/dialog-frame.d.ts +1 -0
  67. package/dist/components/entity-combobox.d.ts +1 -1
  68. package/dist/components/entity-combobox.js +18 -3
  69. package/dist/components/entity-combobox.js.map +1 -1
  70. package/dist/components/error-boundary.d.ts +92 -0
  71. package/dist/components/error-boundary.js +111 -0
  72. package/dist/components/error-boundary.js.map +1 -0
  73. package/dist/components/field.d.ts +69 -0
  74. package/dist/components/field.js +75 -0
  75. package/dist/components/field.js.map +1 -0
  76. package/dist/components/file-button.d.ts +51 -208
  77. package/dist/components/file-dropzone.d.ts +50 -2
  78. package/dist/components/floating-panel.d.ts +156 -4
  79. package/dist/components/floating-panel.js +193 -29
  80. package/dist/components/floating-panel.js.map +1 -1
  81. package/dist/components/form-actions.d.ts +53 -0
  82. package/dist/components/form-actions.js +98 -0
  83. package/dist/components/form-actions.js.map +1 -0
  84. package/dist/components/image-grid.d.ts +67 -0
  85. package/dist/components/image-grid.js +112 -0
  86. package/dist/components/image-grid.js.map +1 -0
  87. package/dist/components/lightbox.d.ts +104 -0
  88. package/dist/components/lightbox.js +229 -0
  89. package/dist/components/lightbox.js.map +1 -0
  90. package/dist/components/line-items.d.ts +114 -0
  91. package/dist/components/line-items.js +238 -0
  92. package/dist/components/line-items.js.map +1 -0
  93. package/dist/components/list.d.ts +37 -3
  94. package/dist/components/list.js +39 -14
  95. package/dist/components/list.js.map +1 -1
  96. package/dist/components/loading-state.d.ts +24 -0
  97. package/dist/components/loading-state.js +32 -0
  98. package/dist/components/loading-state.js.map +1 -0
  99. package/dist/components/menu-item.d.ts +13 -2
  100. package/dist/components/menu-item.js +11 -2
  101. package/dist/components/menu-item.js.map +1 -1
  102. package/dist/components/mini-calendar.d.ts +82 -5
  103. package/dist/components/mini-calendar.js +212 -64
  104. package/dist/components/mini-calendar.js.map +1 -1
  105. package/dist/components/modal.d.ts +20 -1
  106. package/dist/components/modal.js +26 -2
  107. package/dist/components/modal.js.map +1 -1
  108. package/dist/components/month-picker.d.ts +21 -1
  109. package/dist/components/month-picker.js +139 -26
  110. package/dist/components/month-picker.js.map +1 -1
  111. package/dist/components/multi-entity-combobox.d.ts +1 -1
  112. package/dist/components/multi-entity-combobox.js +19 -3
  113. package/dist/components/multi-entity-combobox.js.map +1 -1
  114. package/dist/components/nav-pills.d.ts +67 -0
  115. package/dist/components/nav-pills.js +69 -0
  116. package/dist/components/nav-pills.js.map +1 -0
  117. package/dist/components/number-field.d.ts +26 -5
  118. package/dist/components/number-field.js +5 -7
  119. package/dist/components/number-field.js.map +1 -1
  120. package/dist/components/number-input.d.ts +27 -5
  121. package/dist/components/number-input.js +7 -1
  122. package/dist/components/number-input.js.map +1 -1
  123. package/dist/components/numpad-sheet.d.ts +17 -4
  124. package/dist/components/passkeys-setting.d.ts +67 -0
  125. package/dist/components/passkeys-setting.js +214 -0
  126. package/dist/components/passkeys-setting.js.map +1 -0
  127. package/dist/components/picker-sheet.js +2 -1
  128. package/dist/components/picker-sheet.js.map +1 -1
  129. package/dist/components/pie-chart-labels.d.ts +20 -0
  130. package/dist/components/pie-chart-labels.js +12 -0
  131. package/dist/components/pie-chart-labels.js.map +1 -0
  132. package/dist/components/pie-chart.d.ts +101 -0
  133. package/dist/components/pie-chart.js +283 -0
  134. package/dist/components/pie-chart.js.map +1 -0
  135. package/dist/components/progress-bar.d.ts +73 -3
  136. package/dist/components/progress-bar.js +110 -8
  137. package/dist/components/progress-bar.js.map +1 -1
  138. package/dist/components/qr-code.d.ts +34 -0
  139. package/dist/components/qr-code.js +57 -0
  140. package/dist/components/qr-code.js.map +1 -0
  141. package/dist/components/search-field.d.ts +15 -3
  142. package/dist/components/search-field.js +5 -1
  143. package/dist/components/search-field.js.map +1 -1
  144. package/dist/components/series-chart.d.ts +16 -0
  145. package/dist/components/series-chart.js +4 -1
  146. package/dist/components/series-chart.js.map +1 -1
  147. package/dist/components/settings-fields.d.ts +50 -1
  148. package/dist/components/signed-amount.d.ts +121 -0
  149. package/dist/components/signed-amount.js +152 -0
  150. package/dist/components/signed-amount.js.map +1 -0
  151. package/dist/components/skeleton.d.ts +10 -3
  152. package/dist/components/skeleton.js +12 -2
  153. package/dist/components/skeleton.js.map +1 -1
  154. package/dist/components/stat-tile.d.ts +2 -1
  155. package/dist/components/stat-tile.js +5 -2
  156. package/dist/components/stat-tile.js.map +1 -1
  157. package/dist/components/status-dot.d.ts +6 -2
  158. package/dist/components/status-dot.js +6 -1
  159. package/dist/components/status-dot.js.map +1 -1
  160. package/dist/components/table.d.ts +77 -11
  161. package/dist/components/table.js +85 -17
  162. package/dist/components/table.js.map +1 -1
  163. package/dist/components/text-link.d.ts +176 -0
  164. package/dist/components/text-link.js +109 -0
  165. package/dist/components/text-link.js.map +1 -0
  166. package/dist/components/text.d.ts +8 -1
  167. package/dist/components/text.js +1 -0
  168. package/dist/components/text.js.map +1 -1
  169. package/dist/components/time-input.d.ts +50 -1
  170. package/dist/components/toggle-group.d.ts +28 -0
  171. package/dist/components/toggle-group.js +58 -9
  172. package/dist/components/toggle-group.js.map +1 -1
  173. package/dist/components/toggle-legend.d.ts +36 -3
  174. package/dist/components/toggle-legend.js +49 -0
  175. package/dist/components/toggle-legend.js.map +1 -1
  176. package/dist/components/tooltip.d.ts +11 -3
  177. package/dist/components/tooltip.js +2 -0
  178. package/dist/components/tooltip.js.map +1 -1
  179. package/dist/components/ui.d.ts +52 -729
  180. package/dist/components/ui.js +296 -27
  181. package/dist/components/ui.js.map +1 -1
  182. package/dist/components/use-table-state.d.ts +3 -52
  183. package/dist/components/use-table-state.js +128 -30
  184. package/dist/components/use-table-state.js.map +1 -1
  185. package/dist/components/user-avatar.d.ts +25 -3
  186. package/dist/components/user-avatar.js +32 -3
  187. package/dist/components/user-avatar.js.map +1 -1
  188. package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
  189. package/dist/data-table.d.ts +2 -1
  190. package/dist/data-table.js +5 -1
  191. package/dist/data-table.js.map +1 -1
  192. package/dist/feedback/feedback-dialog.d.ts +26 -3
  193. package/dist/feedback/feedback-dialog.js +16 -2
  194. package/dist/feedback/feedback-dialog.js.map +1 -1
  195. package/dist/feedback/feedback-thread.d.ts +133 -0
  196. package/dist/feedback/feedback-thread.js +237 -0
  197. package/dist/feedback/feedback-thread.js.map +1 -0
  198. package/dist/feedback.d.ts +2 -1
  199. package/dist/feedback.js +1 -0
  200. package/dist/feedback.js.map +1 -1
  201. package/dist/hooks/use-authed-src.d.ts +71 -0
  202. package/dist/hooks/use-authed-src.js +63 -0
  203. package/dist/hooks/use-authed-src.js.map +1 -0
  204. package/dist/hooks/use-file-drop.d.ts +50 -2
  205. package/dist/hooks/use-hotkey.d.ts +59 -0
  206. package/dist/hooks/use-hotkey.js +105 -0
  207. package/dist/hooks/use-hotkey.js.map +1 -0
  208. package/dist/hooks/use-search-param-state.d.ts +83 -0
  209. package/dist/hooks/use-search-param-state.js +94 -0
  210. package/dist/hooks/use-search-param-state.js.map +1 -0
  211. package/dist/i18n/defaults.d.ts +17 -4
  212. package/dist/i18n/defaults.js +33 -1
  213. package/dist/i18n/defaults.js.map +1 -1
  214. package/dist/i18n/kit-labels.d.ts +47 -310
  215. package/dist/i18n/kit-labels.js +20 -5
  216. package/dist/i18n/kit-labels.js.map +1 -1
  217. package/dist/i18n/locales/de-CH-informal.d.ts +17 -4
  218. package/dist/i18n/locales/de-CH.d.ts +17 -4
  219. package/dist/i18n/locales/de-informal.d.ts +17 -4
  220. package/dist/i18n/locales/de.d.ts +17 -4
  221. package/dist/i18n/locales/de.js +165 -4
  222. package/dist/i18n/locales/de.js.map +1 -1
  223. package/dist/i18n/locales/es.d.ts +17 -4
  224. package/dist/i18n/locales/es.js +162 -4
  225. package/dist/i18n/locales/es.js.map +1 -1
  226. package/dist/i18n/locales/fr.d.ts +17 -4
  227. package/dist/i18n/locales/fr.js +162 -4
  228. package/dist/i18n/locales/fr.js.map +1 -1
  229. package/dist/i18n/locales/hu.d.ts +17 -4
  230. package/dist/i18n/locales/hu.js +162 -4
  231. package/dist/i18n/locales/hu.js.map +1 -1
  232. package/dist/i18n/locales/it.d.ts +17 -4
  233. package/dist/i18n/locales/it.js +162 -4
  234. package/dist/i18n/locales/it.js.map +1 -1
  235. package/dist/i18n/locales/zh.d.ts +17 -4
  236. package/dist/i18n/locales/zh.js +162 -4
  237. package/dist/i18n/locales/zh.js.map +1 -1
  238. package/dist/index.d.ts +45 -21
  239. package/dist/index.js +43 -1
  240. package/dist/index.js.map +1 -1
  241. package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
  242. package/dist/lib/clipping.d.ts +21 -5
  243. package/dist/lib/clipping.js +3 -0
  244. package/dist/lib/clipping.js.map +1 -1
  245. package/dist/lib/dates.d.ts +17 -1
  246. package/dist/lib/dates.js +19 -0
  247. package/dist/lib/dates.js.map +1 -1
  248. package/dist/lib/format.d.ts +144 -0
  249. package/dist/lib/format.js +149 -0
  250. package/dist/lib/format.js.map +1 -0
  251. package/dist/lib/qr-encode.d.ts +44 -0
  252. package/dist/lib/qr-encode.js +338 -0
  253. package/dist/lib/qr-encode.js.map +1 -0
  254. package/dist/rhf/fields.d.ts +241 -0
  255. package/dist/rhf/fields.js +601 -0
  256. package/dist/rhf/fields.js.map +1 -0
  257. package/dist/rhf/form.d.ts +50 -1
  258. package/dist/rhf/line-items.d.ts +31 -0
  259. package/dist/rhf/line-items.js +59 -0
  260. package/dist/rhf/line-items.js.map +1 -0
  261. package/dist/rhf/use-rhf-wizard-step.d.ts +22 -0
  262. package/dist/rhf/use-rhf-wizard-step.js +38 -0
  263. package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
  264. package/dist/rhf.d.ts +63 -1
  265. package/dist/rhf.js +3 -0
  266. package/dist/rhf.js.map +1 -1
  267. package/dist/shell/app-shell.d.ts +99 -2
  268. package/dist/shell/app-shell.js +58 -3
  269. package/dist/shell/app-shell.js.map +1 -1
  270. package/dist/shell/auth-layout.d.ts +74 -0
  271. package/dist/shell/auth-layout.js +97 -0
  272. package/dist/shell/auth-layout.js.map +1 -0
  273. package/dist/shell/top-bar-brand.d.ts +81 -0
  274. package/dist/shell/top-bar-brand.js +47 -0
  275. package/dist/shell/top-bar-brand.js.map +1 -0
  276. package/dist/shell/topbar-action-menu.d.ts +84 -3
  277. package/dist/shell/topbar-action-menu.js +115 -27
  278. package/dist/shell/topbar-action-menu.js.map +1 -1
  279. package/dist/shell.d.ts +58 -2
  280. package/dist/shell.js +2 -0
  281. package/dist/shell.js.map +1 -1
  282. package/dist/wizard/stepper-nav.d.ts +49 -1
  283. package/dist/wizard/wizard-context.d.ts +30 -1
  284. package/dist/wizard/wizard-context.js +23 -2
  285. package/dist/wizard/wizard-context.js.map +1 -1
  286. package/dist/wizard/wizard-step.d.ts +20 -5
  287. package/dist/wizard/wizard-step.js +16 -3
  288. package/dist/wizard/wizard-step.js.map +1 -1
  289. package/dist/wizard.d.ts +51 -3
  290. package/package.json +1 -1
  291. package/src/chart.ts +4 -0
  292. package/src/components/account-settings-labels.ts +190 -0
  293. package/src/components/account-settings.tsx +110 -63
  294. package/src/components/alert-banner.tsx +36 -7
  295. package/src/components/amount-input.tsx +13 -1
  296. package/src/components/authed-image.tsx +190 -0
  297. package/src/components/breadcrumbs.tsx +8 -2
  298. package/src/components/bulk-action-bar.tsx +163 -57
  299. package/src/components/button-group.tsx +129 -4
  300. package/src/components/calendar-heatmap.tsx +599 -0
  301. package/src/components/chip.tsx +8 -4
  302. package/src/components/choice-card.tsx +208 -1
  303. package/src/components/combobox-core.tsx +5 -0
  304. package/src/components/combobox.tsx +31 -9
  305. package/src/components/copy-button.tsx +3 -2
  306. package/src/components/data-table-cells.tsx +119 -0
  307. package/src/components/data-table-filters.ts +77 -0
  308. package/src/components/data-table-labels.ts +23 -0
  309. package/src/components/data-table.tsx +231 -13
  310. package/src/components/date-picker.tsx +19 -3
  311. package/src/components/description-list.tsx +52 -3
  312. package/src/components/entity-combobox.tsx +18 -3
  313. package/src/components/error-boundary.tsx +216 -0
  314. package/src/components/field.tsx +188 -0
  315. package/src/components/floating-panel.tsx +380 -16
  316. package/src/components/form-actions.tsx +191 -0
  317. package/src/components/image-grid.tsx +181 -0
  318. package/src/components/lightbox.tsx +374 -0
  319. package/src/components/line-items.tsx +385 -0
  320. package/src/components/list.tsx +93 -18
  321. package/src/components/loading-state.tsx +47 -0
  322. package/src/components/menu-item.tsx +29 -3
  323. package/src/components/mini-calendar.tsx +317 -57
  324. package/src/components/modal.tsx +61 -3
  325. package/src/components/month-picker.tsx +181 -26
  326. package/src/components/multi-entity-combobox.tsx +19 -3
  327. package/src/components/nav-pills.tsx +152 -0
  328. package/src/components/number-field.tsx +15 -10
  329. package/src/components/number-input.tsx +16 -1
  330. package/src/components/passkeys-setting.tsx +331 -0
  331. package/src/components/picker-sheet.tsx +7 -1
  332. package/src/components/pie-chart-labels.ts +32 -0
  333. package/src/components/pie-chart.tsx +479 -0
  334. package/src/components/progress-bar.tsx +241 -13
  335. package/src/components/qr-code.tsx +83 -0
  336. package/src/components/search-field.tsx +20 -4
  337. package/src/components/series-chart.tsx +21 -0
  338. package/src/components/signed-amount.tsx +286 -0
  339. package/src/components/skeleton.tsx +21 -3
  340. package/src/components/stat-tile.tsx +8 -2
  341. package/src/components/status-dot.tsx +10 -2
  342. package/src/components/table.tsx +186 -23
  343. package/src/components/text-link.tsx +248 -0
  344. package/src/components/text.tsx +9 -1
  345. package/src/components/toggle-group.tsx +106 -13
  346. package/src/components/toggle-legend.tsx +87 -2
  347. package/src/components/tooltip.tsx +17 -3
  348. package/src/components/ui.tsx +572 -44
  349. package/src/components/use-table-state.ts +265 -33
  350. package/src/components/user-avatar.tsx +57 -3
  351. package/src/data-table.ts +10 -0
  352. package/src/feedback/feedback-dialog.tsx +42 -3
  353. package/src/feedback/feedback-thread.tsx +434 -0
  354. package/src/feedback.ts +1 -0
  355. package/src/hooks/use-authed-src.ts +161 -0
  356. package/src/hooks/use-hotkey.ts +172 -0
  357. package/src/hooks/use-search-param-state.ts +187 -0
  358. package/src/i18n/defaults.ts +32 -0
  359. package/src/i18n/kit-labels.tsx +104 -8
  360. package/src/i18n/locales/de.ts +162 -0
  361. package/src/i18n/locales/es.ts +159 -0
  362. package/src/i18n/locales/fr.ts +162 -0
  363. package/src/i18n/locales/hu.ts +160 -0
  364. package/src/i18n/locales/it.ts +160 -0
  365. package/src/i18n/locales/zh.ts +159 -0
  366. package/src/index.ts +64 -0
  367. package/src/lib/clipping.ts +22 -4
  368. package/src/lib/dates.ts +37 -0
  369. package/src/lib/format.ts +325 -0
  370. package/src/lib/qr-encode.ts +420 -0
  371. package/src/rhf/fields.tsx +1054 -0
  372. package/src/rhf/line-items.tsx +130 -0
  373. package/src/rhf/use-rhf-wizard-step.ts +113 -0
  374. package/src/rhf.ts +8 -0
  375. package/src/shell/app-shell.tsx +147 -2
  376. package/src/shell/auth-layout.tsx +190 -0
  377. package/src/shell/top-bar-brand.tsx +76 -0
  378. package/src/shell/topbar-action-menu.tsx +260 -49
  379. package/src/shell.ts +2 -0
  380. package/src/wizard/wizard-context.tsx +50 -1
  381. package/src/wizard/wizard-step.tsx +43 -6
  382. package/tokens.css +12 -0
@@ -2,7 +2,7 @@ import { useId } from "react";
2
2
  import type { ComponentPropsWithoutRef, KeyboardEvent, ReactElement, ReactNode } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { horizontalStep } from "../lib/direction";
5
- import { FIELD_INVALID, FloatingField } from "./ui";
5
+ import { FIELD_INVALID, FloatingField, Label } from "./ui";
6
6
 
7
7
  export interface ToggleOption<T extends string> {
8
8
  value: T;
@@ -67,6 +67,19 @@ export interface ToggleGroupBaseProps<T extends string>
67
67
  * group's own box is dropped: two nested borders read as a control in a control.
68
68
  */
69
69
  label?: ReactNode;
70
+ /**
71
+ * Where `label` goes. `"field"` (default): the field chrome described under `label`.
72
+ * `"above"`: the kit's {@link Label} over the bare group — the shape of a {@link Field}
73
+ * — with `hint` beside the label and `error` under the group, for a form that sets
74
+ * its labels above its fields (kastlan's international-rent-calculator.tsx, whose DE
75
+ * cap pair sits in a `Field` column between two labelled-above inputs, where the
76
+ * chrome's inner label would be the only one of its kind).
77
+ *
78
+ * Inside a `Field`, pass no `label` at all and spread the render-prop instead —
79
+ * `{(ids, { labelId }) => <ToggleGroup {...ids} aria-labelledby={labelId} … />}` —
80
+ * so the Field's label names the group and its hint and error describe it.
81
+ */
82
+ labelPlacement?: "field" | "above";
70
83
  /** A {@link FieldHint} on the label line, as on a labelled {@link Select}. Only with
71
84
  * `label`. */
72
85
  hint?: ReactNode;
@@ -76,11 +89,25 @@ export interface ToggleGroupBaseProps<T extends string>
76
89
  error?: ReactNode;
77
90
  }
78
91
 
92
+ /**
93
+ * A line under the group saying what the CHOSEN option means — lenkbank's ToggleField
94
+ * `hint` (features/gear/common.tsx:97): not help behind a "?" but a caption, and one
95
+ * that changes as the choice does. Pass a function of the value for that; it is
96
+ * attached with `aria-describedby`, and a function caption is also a polite live
97
+ * region, because a description is read when the group is entered and not again when
98
+ * an arrow key changes the choice underneath it.
99
+ *
100
+ * In muted 11px text under the field (or the bare group), above an `error`.
101
+ */
102
+ type ToggleGroupCaption<V> = ReactNode | ((value: V) => ReactNode);
103
+
79
104
  /** The group as it has always been: one option is always the answer. */
80
105
  export interface ToggleGroupRequiredProps<T extends string> extends ToggleGroupBaseProps<T> {
81
106
  allowEmpty?: false;
82
107
  value: T;
83
108
  onChange: (value: T) => void;
109
+ /** See {@link ToggleGroupCaption}. */
110
+ caption?: ToggleGroupCaption<T>;
84
111
  }
85
112
 
86
113
  /**
@@ -97,6 +124,8 @@ export interface ToggleGroupClearableProps<T extends string> extends ToggleGroup
97
124
  allowEmpty: true;
98
125
  value: T | null;
99
126
  onChange: (value: T | null) => void;
127
+ /** See {@link ToggleGroupCaption}. `null` while nothing is chosen. */
128
+ caption?: ToggleGroupCaption<T | null>;
100
129
  }
101
130
 
102
131
  /** `allowEmpty` picks the shape, so `onChange` is typed `(T) => void` unless the group
@@ -127,6 +156,7 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
127
156
  disabled = false,
128
157
  size = "md",
129
158
  label,
159
+ labelPlacement = "field",
130
160
  hint,
131
161
  error,
132
162
  "aria-label": ariaLabelAttr,
@@ -134,11 +164,21 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
134
164
  } = props;
135
165
  const labelId = useId();
136
166
  const errorId = useId();
137
- const field = label !== undefined && label !== null && label !== false && label !== "";
138
- const hasError = field && error !== undefined && error !== null && error !== false && error !== "";
167
+ const labelled = label !== undefined && label !== null && label !== false && label !== "";
168
+ // `field` is the chrome; a label placed above keeps the bare group's own box.
169
+ const field = labelled && labelPlacement === "field";
170
+ const above = labelled && labelPlacement === "above";
171
+ const hasError = labelled && error !== undefined && error !== null && error !== false && error !== "";
139
172
  // Taken off the rest so neither reaches the DOM; `props` keeps them paired, which is
140
173
  // what lets the `onChange` below be called with `null` only in the mode that allows it.
141
- const { allowEmpty: _allowEmpty, onChange: _onChange, ...rest } = restWithMode;
174
+ const { allowEmpty: _allowEmpty, onChange: _onChange, caption, ...rest } = restWithMode;
175
+ // Invalid from outside too: a `Field` hands the bare group `aria-invalid`, and the
176
+ // border has to say what the attribute says.
177
+ const outsideInvalid = rest["aria-invalid"] === true || rest["aria-invalid"] === "true";
178
+ const captionId = useId();
179
+ const captionIsLive = typeof caption === "function";
180
+ const captionNode = captionIsLive ? (caption as (v: T | null) => ReactNode)(value) : caption;
181
+ const hasCaption = captionNode !== undefined && captionNode !== null && captionNode !== false && captionNode !== "";
142
182
  const choose = (next: T) => {
143
183
  if (props.allowEmpty) props.onChange(next === value ? null : next);
144
184
  else props.onChange(next);
@@ -182,10 +222,10 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
182
222
  // The DOM spelling wins; `ariaLabel` is the fallback for the call sites that
183
223
  // have not moved yet.
184
224
  aria-label={ariaLabelAttr ?? ariaLabel}
185
- aria-labelledby={field && ariaLabelAttr === undefined && ariaLabel === undefined ? labelId : rest["aria-labelledby"]}
225
+ aria-labelledby={labelled && ariaLabelAttr === undefined && ariaLabel === undefined ? labelId : rest["aria-labelledby"]}
186
226
  aria-invalid={hasError || rest["aria-invalid"] || undefined}
187
227
  aria-describedby={
188
- hasError ? (rest["aria-describedby"] ? `${rest["aria-describedby"]} ${errorId}` : errorId) : rest["aria-describedby"]
228
+ [rest["aria-describedby"], hasCaption && captionId, hasError && errorId].filter(Boolean).join(" ") || undefined
189
229
  }
190
230
  // `aria-disabled` on the group as well as `disabled` on each button: a radio
191
231
  // group is what the user is being refused, and a screen reader announcing
@@ -209,10 +249,12 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
209
249
  // package does; `cursor-not-allowed` is on the buttons, which is what a
210
250
  // pointer is actually over.
211
251
  disabled && "opacity-60",
252
+ // The bare group (no chrome to paint) wears the invalid border itself.
253
+ !field && (outsideInvalid || (above && hasError)) && FIELD_INVALID,
212
254
  // Inside the field's chrome the group is only a row of segments: no border, no
213
255
  // surface, no padding of its own, and the field (not the group) is what dims.
214
256
  field && "border-0 bg-transparent p-0 shadow-none opacity-100",
215
- !field && className,
257
+ !labelled && className,
216
258
  )}
217
259
  >
218
260
  {options.map((opt, index) => {
@@ -273,7 +315,61 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
273
315
  })}
274
316
  </div>
275
317
  );
276
- if (!field) return group;
318
+ // Rendered whenever the caption is — `aria-live` must be on the element BEFORE its
319
+ // text changes, or the change is not announced — but empty (no height, no margin)
320
+ // when there is nothing to say, so a function caption that returns null for some
321
+ // options leaves no gap. Empty rather than `hidden`: some readers do not announce
322
+ // text that appears inside an element coming back from `display: none`.
323
+ const captionEl =
324
+ hasCaption || captionIsLive ? (
325
+ <p
326
+ id={captionId}
327
+ aria-live={captionIsLive ? "polite" : undefined}
328
+ className={cn("text-[11px] leading-snug text-[var(--text-muted)]", hasCaption && "mt-1")}
329
+ >
330
+ {hasCaption ? captionNode : null}
331
+ </p>
332
+ ) : null;
333
+ const errorEl = hasError ? (
334
+ <p id={errorId} className="mt-1 text-[11px] leading-tight text-[var(--danger)]">
335
+ {error}
336
+ </p>
337
+ ) : null;
338
+ if (above) {
339
+ // `relative` so a caller's `sr-only` label cannot escape (sr-only-containment).
340
+ return (
341
+ <div className={cn("relative grid min-w-0 gap-1.5", className)}>
342
+ <div className="flex items-center gap-1">
343
+ {/* A `<label>` with no `htmlFor`: a group is not labelable, so it is named
344
+ by `aria-labelledby` on the group; the element keeps the Field look. */}
345
+ <Label
346
+ id={labelId}
347
+ disabled={disabled}
348
+ data-error={hasError || undefined}
349
+ className="data-[error=true]:text-[var(--danger)]"
350
+ >
351
+ {label}
352
+ </Label>
353
+ {hint}
354
+ </div>
355
+ <div className="min-w-0">
356
+ {group}
357
+ {captionEl}
358
+ {errorEl}
359
+ </div>
360
+ </div>
361
+ );
362
+ }
363
+ if (!field) {
364
+ if (!captionEl) return group;
365
+ // The group keeps its `className`, as without a caption; the wrapper only stacks.
366
+ return (
367
+ <div className="min-w-0">
368
+ {group}
369
+ {captionEl}
370
+ </div>
371
+ );
372
+ }
277
373
  return (
278
374
  // `h-full` + `flex-1`: the chrome fills a grid or stretched flex row, which is what
279
375
  // levels it with a select beside it whatever the browser makes of the select.
@@ -294,11 +390,8 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
294
390
  {group}
295
391
  </div>
296
392
  </FloatingField>
297
- {hasError && (
298
- <p id={errorId} className="mt-1 text-[11px] leading-tight text-[var(--danger)]">
299
- {error}
300
- </p>
301
- )}
393
+ {captionEl}
394
+ {errorEl}
302
395
  </div>
303
396
  );
304
397
  }
@@ -68,14 +68,21 @@ export interface LegendEntry {
68
68
  /**
69
69
  * What the entry's mark looks like. `swatch` — a filled square, the default — says
70
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.
71
+ * once and tell them apart by colour and by dash. `dot` is a round mark, for a
72
+ * MARKER on the chart (a point, an event) rather than a series.
72
73
  */
73
- marker?: "swatch" | "stroke";
74
+ marker?: "swatch" | "stroke" | "dot";
74
75
  /** Which of {@link STROKE_PATTERNS}, when the marker is a stroke — the same index
75
76
  * `SeriesChartSeries.dash` takes, so a legend cannot promise a dot-dash the plot
76
77
  * draws dashed. Or the same custom `stroke-dasharray` string the series draws with
77
78
  * (keksdose's `"4 3"`). */
78
79
  dash?: number | string;
80
+ /**
81
+ * A mark of the caller's own, drawn in place of {@link marker} — for a key whose
82
+ * mark is not a colour at all (a fading bar for "open-ended", a hairline for
83
+ * "today"). Decorative: it is wrapped `aria-hidden`, and the label is the name.
84
+ */
85
+ icon?: ReactNode;
79
86
  }
80
87
 
81
88
  export interface ToggleLegendProps {
@@ -163,6 +170,65 @@ export function ToggleLegend({
163
170
  );
164
171
  }
165
172
 
173
+ export interface StaticLegendProps {
174
+ entries: LegendEntry[];
175
+ /** See {@link ToggleLegendProps.orientation}. */
176
+ orientation?: "horizontal" | "vertical";
177
+ /** The list's accessible name. Default: the `seriesChart.legend` label. */
178
+ "aria-label"?: string;
179
+ className?: string;
180
+ /** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */
181
+ labels?: Partial<SeriesChartLabels>;
182
+ }
183
+
184
+ /**
185
+ * A legend that is a KEY, not a control: the same entries and marks as
186
+ * {@link ToggleLegend} — swatch, stroke (with the chart's own dash), dot, or a mark of
187
+ * the caller's own — but nothing to press.
188
+ *
189
+ * A separate component rather than `ToggleLegend` without `onToggle`, because the two
190
+ * are different things to a screen reader: a group of switches announces a pressed
191
+ * state on every entry, a key is a LIST, read as "list, 4 items" and walked with the
192
+ * list keys, and costs no tab stops. The apps drew this by hand under the charts that
193
+ * say what a colour or a dash means without letting you switch it (kastlan's lease
194
+ * timeline, keksdose's cash buffer).
195
+ *
196
+ * Unlike `ToggleLegend` it draws a single entry: a key with one line still says what
197
+ * the one mark on the chart means.
198
+ */
199
+ export function StaticLegend({
200
+ entries,
201
+ orientation = "horizontal",
202
+ "aria-label": ariaLabel,
203
+ className,
204
+ labels: labelsProp,
205
+ }: StaticLegendProps) {
206
+ const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
207
+ if (entries.length === 0) return null;
208
+ return (
209
+ <ul
210
+ aria-label={ariaLabel ?? labels.legend}
211
+ className={cn(
212
+ "m-0 flex list-none gap-x-3 gap-y-1 p-0",
213
+ orientation === "vertical"
214
+ ? "flex-col items-start justify-center"
215
+ : "mt-2 flex-wrap items-center",
216
+ className,
217
+ )}
218
+ >
219
+ {entries.map((entry) => (
220
+ <li
221
+ key={entry.key}
222
+ className="flex items-center gap-1.5 text-[11px] text-[var(--text-secondary)]"
223
+ >
224
+ <LegendMark entry={entry} off={false} />
225
+ {entry.label}
226
+ </li>
227
+ ))}
228
+ </ul>
229
+ );
230
+ }
231
+
166
232
  /**
167
233
  * The column a vertical legend stands in: beside the charts, on their horizontal
168
234
  * centre line. `h-full` + `justify-center`, so it centres in a grid cell or a flex row
@@ -205,6 +271,25 @@ export function LegendGroup({ title, children }: { title: ReactNode; children: R
205
271
  * and the line used to take its own 0.35 on top — 0.35 × 0.35, about 12 %, a mark
206
272
  * gone rather than dimmed, while the square beside it read at the full 35 %. */
207
273
  function LegendMark({ entry, off }: { entry: LegendEntry; off: boolean }) {
274
+ if (entry.icon != null) {
275
+ return (
276
+ <span aria-hidden className="inline-flex shrink-0 items-center">
277
+ {entry.icon}
278
+ </span>
279
+ );
280
+ }
281
+ if (entry.marker === "dot") {
282
+ return (
283
+ <span
284
+ aria-hidden
285
+ className="size-2 shrink-0 rounded-full"
286
+ style={{
287
+ backgroundColor: off ? "transparent" : entry.color,
288
+ boxShadow: `inset 0 0 0 1.5px ${entry.color}`,
289
+ }}
290
+ />
291
+ );
292
+ }
208
293
  if (entry.marker === "stroke") {
209
294
  return (
210
295
  <svg aria-hidden viewBox="0 0 20 2" className="h-0.5 w-5 shrink-0 overflow-visible">
@@ -17,6 +17,13 @@ import { useAnchoredRect, type AnchorRect } from "../hooks/use-anchored-rect";
17
17
  import { dirOf, type Direction } from "../lib/direction";
18
18
  import { hasClippingAncestor } from "../lib/clipping";
19
19
 
20
+ /** The attribute that marks an element as a clipping container for {@link Tooltip}'s
21
+ * auto-portal, whatever its computed `overflow` — `<div data-clips>` or
22
+ * `<div {...{ [CLIPS_ATTRIBUTE]: "" }}>`. Put it on an app's own scroller so a test
23
+ * environment without stylesheets (jsdom) portals the same tooltips the browser
24
+ * does. DataTable's body and Table's wrapper already carry it. */
25
+ export { CLIPS_ATTRIBUTE } from "../lib/clipping";
26
+
20
27
  /** Where the bubble sits. `start` / `end` follow the reading direction — `end` is the
21
28
  * right in LTR and the left in RTL — and are what a layout that mirrors should ask
22
29
  * for. `left` / `right` stay physical, for a bubble tied to something that does not
@@ -75,8 +82,8 @@ export interface TooltipProps extends ComponentPropsWithoutRef<"span"> {
75
82
  /**
76
83
  * Where the bubble lives. Left out (the default since 0.10.0), the tooltip decides for
77
84
  * itself: the bubble stays next to the trigger unless an ancestor clips or scrolls
78
- * (`overflow` other than `visible`), in which case it is portalled — see "Inside a
79
- * scroll container" below. `true` always portals, `false` never does; both are exactly
85
+ * (`overflow` other than `visible`, or the {@link CLIPS_ATTRIBUTE} marker), in which
86
+ * case it is portalled — see "Inside a scroll container" below. `true` always portals, `false` never does; both are exactly
80
87
  * what they were before the default existed.
81
88
  */
82
89
  portal?: boolean;
@@ -144,7 +151,14 @@ type TooltipVariantProps = Omit<TooltipProps, "side" | "portal"> & { side: Toolt
144
151
  * being one: every tooltip in a table, a drawer or a scrolling card had to remember it,
145
152
  * and the ones that forgot were only found by someone scrolling sideways. So with
146
153
  * `portal` left out, the tooltip looks for a clipping ancestor itself — any element
147
- * between it and `<body>` whose computed `overflow-x` / `overflow-y` is not `visible`.
154
+ * between it and `<body>` whose computed `overflow-x` / `overflow-y` is not `visible`,
155
+ * or that carries {@link CLIPS_ATTRIBUTE} (`data-clips`). The marker is what makes the
156
+ * answer the same under test: jsdom computes no Tailwind, so there every scroller
157
+ * reads `visible` and a table-cell tooltip used to stay in place — its always-mounted
158
+ * bubble then repeated the label in the cell's accessible name and `textContent`
159
+ * ("CheckingChecking"), and apps pinned `portal` to stop it. The kit's own scrollers
160
+ * are marked, so a tooltip in a DataTable or Table cell portals in jsdom exactly as it
161
+ * does in the browser.
148
162
  * It looks at MOUNT, not only on open: dev#488's phantom scroll is caused by a bubble
149
163
  * nobody opened, so a check that waited for the hover would find the damage already
150
164
  * done. It looks again on every open, for a container that started scrolling after