@nanisoft/prism-ui 0.10.1 → 0.13.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 (334) hide show
  1. package/LICENSE +21 -21
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/blocks/app-shell-01/app-shell.js +4 -4
  4. package/dist/blocks/app-shell-01/index.d.ts +2 -2
  5. package/dist/blocks/app-shell-01/index.js +1 -1
  6. package/dist/blocks/auth-form-01/auth-form.d.ts +11 -1
  7. package/dist/blocks/auth-form-01/auth-form.d.ts.map +1 -1
  8. package/dist/blocks/auth-form-01/auth-form.js +11 -8
  9. package/dist/blocks/auth-form-01/index.d.ts +2 -2
  10. package/dist/blocks/auth-form-01/index.js +1 -1
  11. package/dist/blocks/cta-01/cta.d.ts +26 -21
  12. package/dist/blocks/cta-01/cta.d.ts.map +1 -1
  13. package/dist/blocks/cta-01/cta.js +2 -2
  14. package/dist/blocks/cta-01/index.d.ts +2 -2
  15. package/dist/blocks/cta-01/index.js +1 -1
  16. package/dist/blocks/data-table-01/data-table.js +7 -7
  17. package/dist/blocks/data-table-01/index.d.ts +2 -2
  18. package/dist/blocks/data-table-01/index.js +1 -1
  19. package/dist/blocks/empty-state-01/empty-state.d.ts +143 -0
  20. package/dist/blocks/empty-state-01/empty-state.d.ts.map +1 -0
  21. package/dist/blocks/empty-state-01/empty-state.js +104 -0
  22. package/dist/blocks/empty-state-01/index.d.ts +3 -0
  23. package/dist/blocks/empty-state-01/index.d.ts.map +1 -0
  24. package/dist/blocks/empty-state-01/index.js +1 -0
  25. package/dist/blocks/feature-grid-01/feature-grid.d.ts +28 -1
  26. package/dist/blocks/feature-grid-01/feature-grid.d.ts.map +1 -1
  27. package/dist/blocks/feature-grid-01/feature-grid.js +34 -3
  28. package/dist/blocks/feature-grid-01/index.d.ts +2 -2
  29. package/dist/blocks/feature-grid-01/index.js +1 -1
  30. package/dist/blocks/hero-01/hero.d.ts +49 -17
  31. package/dist/blocks/hero-01/hero.d.ts.map +1 -1
  32. package/dist/blocks/hero-01/hero.js +14 -9
  33. package/dist/blocks/hero-01/index.d.ts +2 -1
  34. package/dist/blocks/hero-01/index.d.ts.map +1 -1
  35. package/dist/blocks/hero-01/index.js +1 -1
  36. package/dist/blocks/index.d.ts +20 -20
  37. package/dist/blocks/index.js +19 -19
  38. package/dist/blocks/instrument-panel-01/index.d.ts +2 -2
  39. package/dist/blocks/instrument-panel-01/index.js +1 -1
  40. package/dist/blocks/instrument-panel-01/instrument-panel.js +1 -1
  41. package/dist/blocks/logo-strip-01/index.d.ts +2 -2
  42. package/dist/blocks/logo-strip-01/index.js +1 -1
  43. package/dist/blocks/logo-strip-01/logo-strip.d.ts +1 -1
  44. package/dist/blocks/logo-strip-01/logo-strip.js +2 -2
  45. package/dist/blocks/note-grid-01/index.d.ts +2 -2
  46. package/dist/blocks/note-grid-01/index.js +1 -1
  47. package/dist/blocks/note-grid-01/note-grid.d.ts +1 -1
  48. package/dist/blocks/note-grid-01/note-grid.js +1 -1
  49. package/dist/blocks/page-header-01/index.d.ts +2 -2
  50. package/dist/blocks/page-header-01/index.js +1 -1
  51. package/dist/blocks/page-header-01/page-header.d.ts +1 -1
  52. package/dist/blocks/page-header-01/page-header.js +4 -4
  53. package/dist/blocks/pricing-01/index.d.ts +1 -1
  54. package/dist/blocks/pricing-01/index.js +1 -1
  55. package/dist/blocks/pricing-01/pricing.d.ts +8 -1
  56. package/dist/blocks/pricing-01/pricing.d.ts.map +1 -1
  57. package/dist/blocks/pricing-01/pricing.js +15 -5
  58. package/dist/blocks/process-flow-01/index.d.ts +3 -0
  59. package/dist/blocks/process-flow-01/index.d.ts.map +1 -0
  60. package/dist/blocks/process-flow-01/index.js +1 -0
  61. package/dist/blocks/process-flow-01/process-flow.d.ts +118 -0
  62. package/dist/blocks/process-flow-01/process-flow.d.ts.map +1 -0
  63. package/dist/blocks/process-flow-01/process-flow.js +71 -0
  64. package/dist/blocks/process-rail-01/index.d.ts +2 -2
  65. package/dist/blocks/process-rail-01/index.js +1 -1
  66. package/dist/blocks/process-rail-01/process-rail.d.ts +1 -1
  67. package/dist/blocks/process-rail-01/process-rail.js +2 -2
  68. package/dist/blocks/product-grid-01/index.d.ts +2 -2
  69. package/dist/blocks/product-grid-01/index.js +1 -1
  70. package/dist/blocks/product-grid-01/product-grid.d.ts +37 -5
  71. package/dist/blocks/product-grid-01/product-grid.d.ts.map +1 -1
  72. package/dist/blocks/product-grid-01/product-grid.js +20 -6
  73. package/dist/blocks/run-console-01/index.d.ts +3 -0
  74. package/dist/blocks/run-console-01/index.d.ts.map +1 -0
  75. package/dist/blocks/run-console-01/index.js +1 -0
  76. package/dist/blocks/run-console-01/run-console.d.ts +111 -0
  77. package/dist/blocks/run-console-01/run-console.d.ts.map +1 -0
  78. package/dist/blocks/run-console-01/run-console.js +48 -0
  79. package/dist/blocks/settings-panel-01/index.d.ts +2 -2
  80. package/dist/blocks/settings-panel-01/index.js +1 -1
  81. package/dist/blocks/settings-panel-01/settings-panel.d.ts +11 -1
  82. package/dist/blocks/settings-panel-01/settings-panel.d.ts.map +1 -1
  83. package/dist/blocks/settings-panel-01/settings-panel.js +15 -10
  84. package/dist/blocks/site-footer/index.d.ts +2 -2
  85. package/dist/blocks/site-footer/index.js +1 -1
  86. package/dist/blocks/site-footer/site-footer.d.ts +1 -1
  87. package/dist/blocks/site-footer/site-footer.js +2 -2
  88. package/dist/blocks/site-header/index.d.ts +2 -2
  89. package/dist/blocks/site-header/index.js +1 -1
  90. package/dist/blocks/site-header/site-header.d.ts +2 -2
  91. package/dist/blocks/site-header/site-header.js +3 -3
  92. package/dist/blocks/site-navbar/index.d.ts +3 -0
  93. package/dist/blocks/site-navbar/index.d.ts.map +1 -0
  94. package/dist/blocks/site-navbar/index.js +1 -0
  95. package/dist/blocks/site-navbar/mobile-nav.d.ts +48 -0
  96. package/dist/blocks/site-navbar/mobile-nav.d.ts.map +1 -0
  97. package/dist/blocks/site-navbar/mobile-nav.js +35 -0
  98. package/dist/blocks/site-navbar/mode-toggle.d.ts +39 -0
  99. package/dist/blocks/site-navbar/mode-toggle.d.ts.map +1 -0
  100. package/dist/blocks/site-navbar/mode-toggle.js +39 -0
  101. package/dist/blocks/site-navbar/navbar-controls.d.ts +79 -0
  102. package/dist/blocks/site-navbar/navbar-controls.d.ts.map +1 -0
  103. package/dist/blocks/site-navbar/navbar-controls.js +40 -0
  104. package/dist/blocks/site-navbar/search-entry.d.ts +29 -0
  105. package/dist/blocks/site-navbar/search-entry.d.ts.map +1 -0
  106. package/dist/blocks/site-navbar/search-entry.js +52 -0
  107. package/dist/blocks/site-navbar/site-navbar.d.ts +212 -0
  108. package/dist/blocks/site-navbar/site-navbar.d.ts.map +1 -0
  109. package/dist/blocks/site-navbar/site-navbar.js +61 -0
  110. package/dist/blocks/site-navbar/sites-menu.d.ts +40 -0
  111. package/dist/blocks/site-navbar/sites-menu.d.ts.map +1 -0
  112. package/dist/blocks/site-navbar/sites-menu.js +48 -0
  113. package/dist/blocks/site-navbar/theme-menu.d.ts +47 -0
  114. package/dist/blocks/site-navbar/theme-menu.d.ts.map +1 -0
  115. package/dist/blocks/site-navbar/theme-menu.js +55 -0
  116. package/dist/blocks/stack-grid-01/index.d.ts +2 -2
  117. package/dist/blocks/stack-grid-01/index.js +1 -1
  118. package/dist/blocks/stack-grid-01/stack-grid.d.ts +42 -11
  119. package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -1
  120. package/dist/blocks/stack-grid-01/stack-grid.js +15 -7
  121. package/dist/blocks/stats-01/index.d.ts +9 -1
  122. package/dist/blocks/stats-01/index.d.ts.map +1 -1
  123. package/dist/blocks/stats-01/index.js +8 -1
  124. package/dist/blocks/stats-01/stats.d.ts +1 -1
  125. package/dist/blocks/stats-01/stats.js +2 -2
  126. package/dist/blocks/status-ledger-01/index.d.ts +2 -2
  127. package/dist/blocks/status-ledger-01/index.js +1 -1
  128. package/dist/blocks/status-ledger-01/status-ledger.d.ts +1 -1
  129. package/dist/blocks/status-ledger-01/status-ledger.js +2 -2
  130. package/dist/catalog.d.ts +1 -1
  131. package/dist/catalog.d.ts.map +1 -1
  132. package/dist/catalog.js +473 -1
  133. package/dist/components/index.d.ts +104 -66
  134. package/dist/components/index.d.ts.map +1 -1
  135. package/dist/components/index.js +74 -40
  136. package/dist/components/ui/accordion.js +1 -1
  137. package/dist/components/ui/alert-dialog.d.ts +119 -0
  138. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  139. package/dist/components/ui/alert-dialog.js +82 -0
  140. package/dist/components/ui/alert.js +1 -1
  141. package/dist/components/ui/aspect-ratio.d.ts +60 -0
  142. package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
  143. package/dist/components/ui/aspect-ratio.js +61 -0
  144. package/dist/components/ui/avatar.js +1 -1
  145. package/dist/components/ui/badge.js +1 -1
  146. package/dist/components/ui/breadcrumb.js +1 -1
  147. package/dist/components/ui/button-group.d.ts +68 -0
  148. package/dist/components/ui/button-group.d.ts.map +1 -0
  149. package/dist/components/ui/button-group.js +54 -0
  150. package/dist/components/ui/button.js +1 -1
  151. package/dist/components/ui/calendar.d.ts +158 -0
  152. package/dist/components/ui/calendar.d.ts.map +1 -0
  153. package/dist/components/ui/calendar.js +216 -0
  154. package/dist/components/ui/card.d.ts +18 -6
  155. package/dist/components/ui/card.d.ts.map +1 -1
  156. package/dist/components/ui/card.js +23 -8
  157. package/dist/components/ui/carousel.d.ts +118 -0
  158. package/dist/components/ui/carousel.d.ts.map +1 -0
  159. package/dist/components/ui/carousel.js +152 -0
  160. package/dist/components/ui/chart-frame.d.ts +216 -0
  161. package/dist/components/ui/chart-frame.d.ts.map +1 -0
  162. package/dist/components/ui/chart-frame.js +286 -0
  163. package/dist/components/ui/checkbox.js +1 -1
  164. package/dist/components/ui/collapsible.d.ts +97 -0
  165. package/dist/components/ui/collapsible.d.ts.map +1 -0
  166. package/dist/components/ui/collapsible.js +54 -0
  167. package/dist/components/ui/combobox.d.ts +141 -0
  168. package/dist/components/ui/combobox.d.ts.map +1 -0
  169. package/dist/components/ui/combobox.js +243 -0
  170. package/dist/components/ui/command-palette.d.ts +106 -0
  171. package/dist/components/ui/command-palette.d.ts.map +1 -0
  172. package/dist/components/ui/command-palette.js +176 -0
  173. package/dist/components/ui/command.d.ts +82 -0
  174. package/dist/components/ui/command.d.ts.map +1 -0
  175. package/dist/components/ui/command.js +37 -0
  176. package/dist/components/ui/context-menu.d.ts +151 -0
  177. package/dist/components/ui/context-menu.d.ts.map +1 -0
  178. package/dist/components/ui/context-menu.js +97 -0
  179. package/dist/components/ui/cta-link.js +1 -1
  180. package/dist/components/ui/date-picker.d.ts +111 -0
  181. package/dist/components/ui/date-picker.d.ts.map +1 -0
  182. package/dist/components/ui/date-picker.js +74 -0
  183. package/dist/components/ui/diagram.d.ts +84 -15
  184. package/dist/components/ui/diagram.d.ts.map +1 -1
  185. package/dist/components/ui/diagram.js +77 -7
  186. package/dist/components/ui/dialog.js +1 -1
  187. package/dist/components/ui/diff.d.ts +105 -0
  188. package/dist/components/ui/diff.d.ts.map +1 -0
  189. package/dist/components/ui/diff.js +113 -0
  190. package/dist/components/ui/dropdown-menu.d.ts +18 -0
  191. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  192. package/dist/components/ui/dropdown-menu.js +1 -1
  193. package/dist/components/ui/fact-list.js +1 -1
  194. package/dist/components/ui/field.js +1 -1
  195. package/dist/components/ui/form.d.ts +151 -0
  196. package/dist/components/ui/form.d.ts.map +1 -0
  197. package/dist/components/ui/form.js +73 -0
  198. package/dist/components/ui/hover-card.d.ts +105 -0
  199. package/dist/components/ui/hover-card.d.ts.map +1 -0
  200. package/dist/components/ui/hover-card.js +49 -0
  201. package/dist/components/ui/input-group.d.ts +99 -0
  202. package/dist/components/ui/input-group.d.ts.map +1 -0
  203. package/dist/components/ui/input-group.js +83 -0
  204. package/dist/components/ui/input.js +1 -1
  205. package/dist/components/ui/item.d.ts +113 -0
  206. package/dist/components/ui/item.d.ts.map +1 -0
  207. package/dist/components/ui/item.js +61 -0
  208. package/dist/components/ui/kbd.js +1 -1
  209. package/dist/components/ui/label.d.ts +58 -0
  210. package/dist/components/ui/label.d.ts.map +1 -0
  211. package/dist/components/ui/label.js +29 -0
  212. package/dist/components/ui/live-region.d.ts +9 -0
  213. package/dist/components/ui/live-region.d.ts.map +1 -1
  214. package/dist/components/ui/live-region.js +10 -2
  215. package/dist/components/ui/mark.js +1 -1
  216. package/dist/components/ui/menubar.d.ts +197 -0
  217. package/dist/components/ui/menubar.d.ts.map +1 -0
  218. package/dist/components/ui/menubar.js +109 -0
  219. package/dist/components/ui/meter.d.ts +121 -0
  220. package/dist/components/ui/meter.d.ts.map +1 -0
  221. package/dist/components/ui/meter.js +82 -0
  222. package/dist/components/ui/native-select.d.ts +72 -0
  223. package/dist/components/ui/native-select.d.ts.map +1 -0
  224. package/dist/components/ui/native-select.js +55 -0
  225. package/dist/components/ui/navigation-menu.d.ts +186 -0
  226. package/dist/components/ui/navigation-menu.d.ts.map +1 -0
  227. package/dist/components/ui/navigation-menu.js +94 -0
  228. package/dist/components/ui/number-field.d.ts +111 -0
  229. package/dist/components/ui/number-field.d.ts.map +1 -0
  230. package/dist/components/ui/number-field.js +84 -0
  231. package/dist/components/ui/one-time-code.d.ts +127 -0
  232. package/dist/components/ui/one-time-code.d.ts.map +1 -0
  233. package/dist/components/ui/one-time-code.js +57 -0
  234. package/dist/components/ui/pagination.js +1 -1
  235. package/dist/components/ui/popover.js +1 -1
  236. package/dist/components/ui/product-mark.d.ts +1 -1
  237. package/dist/components/ui/product-mark.js +1 -1
  238. package/dist/components/ui/product-switcher.d.ts +1 -1
  239. package/dist/components/ui/product-switcher.js +2 -2
  240. package/dist/components/ui/progress.js +1 -1
  241. package/dist/components/ui/prose.js +1 -1
  242. package/dist/components/ui/pulse-graph.js +1 -1
  243. package/dist/components/ui/pulse-series.js +1 -1
  244. package/dist/components/ui/radio-group.js +1 -1
  245. package/dist/components/ui/resizable.d.ts +129 -0
  246. package/dist/components/ui/resizable.d.ts.map +1 -0
  247. package/dist/components/ui/resizable.js +205 -0
  248. package/dist/components/ui/scroll-area.d.ts +97 -0
  249. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  250. package/dist/components/ui/scroll-area.js +68 -0
  251. package/dist/components/ui/search-dialog.d.ts +139 -0
  252. package/dist/components/ui/search-dialog.d.ts.map +1 -0
  253. package/dist/components/ui/search-dialog.js +178 -0
  254. package/dist/components/ui/section.d.ts +26 -0
  255. package/dist/components/ui/section.d.ts.map +1 -1
  256. package/dist/components/ui/section.js +32 -1
  257. package/dist/components/ui/select.js +1 -1
  258. package/dist/components/ui/separator.js +1 -1
  259. package/dist/components/ui/sheet.d.ts +103 -0
  260. package/dist/components/ui/sheet.d.ts.map +1 -0
  261. package/dist/components/ui/sheet.js +58 -0
  262. package/dist/components/ui/sidebar.d.ts +228 -0
  263. package/dist/components/ui/sidebar.d.ts.map +1 -0
  264. package/dist/components/ui/sidebar.js +172 -0
  265. package/dist/components/ui/signal-field.js +1 -1
  266. package/dist/components/ui/skeleton.js +1 -1
  267. package/dist/components/ui/slider.js +1 -1
  268. package/dist/components/ui/spinner.d.ts +79 -0
  269. package/dist/components/ui/spinner.d.ts.map +1 -0
  270. package/dist/components/ui/spinner.js +79 -0
  271. package/dist/components/ui/switch.js +1 -1
  272. package/dist/components/ui/table-sort.d.ts +97 -0
  273. package/dist/components/ui/table-sort.d.ts.map +1 -0
  274. package/dist/components/ui/table-sort.js +102 -0
  275. package/dist/components/ui/table.js +1 -1
  276. package/dist/components/ui/tabs.js +1 -1
  277. package/dist/components/ui/textarea.js +1 -1
  278. package/dist/components/ui/timeline.d.ts +106 -0
  279. package/dist/components/ui/timeline.d.ts.map +1 -0
  280. package/dist/components/ui/timeline.js +111 -0
  281. package/dist/components/ui/toast.d.ts +219 -0
  282. package/dist/components/ui/toast.d.ts.map +1 -0
  283. package/dist/components/ui/toast.js +270 -0
  284. package/dist/components/ui/toggle-group.d.ts +118 -0
  285. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  286. package/dist/components/ui/toggle-group.js +194 -0
  287. package/dist/components/ui/toggle.d.ts +73 -0
  288. package/dist/components/ui/toggle.d.ts.map +1 -0
  289. package/dist/components/ui/toggle.js +44 -0
  290. package/dist/components/ui/tooltip.js +1 -1
  291. package/dist/components/ui/tree.js +1 -1
  292. package/dist/components/ui/typography.js +1 -1
  293. package/dist/fonts/Inter-OFL.txt +93 -93
  294. package/dist/index.d.ts +72 -70
  295. package/dist/index.d.ts.map +1 -1
  296. package/dist/index.js +61 -60
  297. package/dist/lib/rank.d.ts +69 -0
  298. package/dist/lib/rank.d.ts.map +1 -0
  299. package/dist/lib/rank.js +77 -0
  300. package/dist/live/index.d.ts +22 -0
  301. package/dist/live/index.d.ts.map +1 -0
  302. package/dist/live/index.js +20 -0
  303. package/dist/live/run-stream-01/index.d.ts +3 -0
  304. package/dist/live/run-stream-01/index.d.ts.map +1 -0
  305. package/dist/live/run-stream-01/index.js +1 -0
  306. package/dist/live/run-stream-01/run-stream.d.ts +125 -0
  307. package/dist/live/run-stream-01/run-stream.d.ts.map +1 -0
  308. package/dist/live/run-stream-01/run-stream.js +111 -0
  309. package/dist/pages/auth-page/index.d.ts +15 -2
  310. package/dist/pages/auth-page/index.d.ts.map +1 -1
  311. package/dist/pages/auth-page/index.js +8 -4
  312. package/dist/pages/blog-post-page/index.js +5 -5
  313. package/dist/pages/dashboard-page/index.d.ts +4 -4
  314. package/dist/pages/dashboard-page/index.js +4 -4
  315. package/dist/pages/docs-shell/docs-shell.d.ts +37 -4
  316. package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
  317. package/dist/pages/docs-shell/docs-shell.js +107 -16
  318. package/dist/pages/docs-shell/index.d.ts +2 -2
  319. package/dist/pages/docs-shell/index.js +1 -1
  320. package/dist/pages/index.d.ts +14 -14
  321. package/dist/pages/index.js +7 -7
  322. package/dist/pages/marketing-page/index.d.ts +6 -6
  323. package/dist/pages/marketing-page/index.js +5 -5
  324. package/dist/pages/not-found-page/index.js +4 -4
  325. package/dist/pages/settings-page/index.d.ts +3 -3
  326. package/dist/pages/settings-page/index.js +4 -4
  327. package/dist/provider/index.d.ts +4 -4
  328. package/dist/provider/index.js +2 -2
  329. package/dist/provider/provider.d.ts +1 -1
  330. package/dist/provider/provider.js +1 -1
  331. package/dist/provider/theme-script.d.ts +1 -1
  332. package/dist/provider/theme-script.js +1 -1
  333. package/dist/styles.css +689 -0
  334. package/package.json +9 -1
@@ -0,0 +1,216 @@
1
+ import type { ComponentProps, ReactNode } from 'react';
2
+ /**
3
+ * The one of the five series roles a series is drawn in.
4
+ *
5
+ * These are the token names rather than a Prism-specific alias, and that is the
6
+ * whole contract: `chart-1` through `chart-5` is the only five-way colour set
7
+ * the token contract publishes, so a consumer who wants a particular series in a
8
+ * particular colour names the role and gets it in every pack and both modes. A
9
+ * wider type here would be a wider palette, and a wider palette is a second
10
+ * source of truth for something the tokens already own.
11
+ */
12
+ export type ChartColor = 'chart-1' | 'chart-2' | 'chart-3' | 'chart-4' | 'chart-5';
13
+ /**
14
+ * How the series are drawn.
15
+ *
16
+ * Two marks, and the split is about what the reader compares. A line is for a
17
+ * value that moves continuously across the categories, where the shape between
18
+ * two points is the point. A bar is for a value read against a baseline, where
19
+ * the length is the measurement and the gap between two bars means nothing. A
20
+ * consumer that draws a bar chart of weekly signups is drawing the right mark
21
+ * and saying the wrong thing; the bars invite a comparison of lengths that only
22
+ * means something when the value starts at zero.
23
+ */
24
+ export type ChartMark = 'line' | 'bar';
25
+ /** One series: a name, the role it is drawn in, and one value per category. */
26
+ export type ChartSeries = {
27
+ /**
28
+ * A stable key for the series.
29
+ *
30
+ * Required, because the same series is a line, a dot, a legend entry and a
31
+ * table column, and a reader whose focus is in the table must land in the
32
+ * column for the series they were reading. Keyed on the caller's own id rather
33
+ * than on the position in the array, so reordering the series does not move a
34
+ * reader's focus to a different series.
35
+ */
36
+ id: string;
37
+ /**
38
+ * The name of the series, in the caller's words.
39
+ *
40
+ * It appears in the legend, in the table's column head, and nowhere else. This
41
+ * is the string that makes a colour a name: a chart drawn in `chart-2` and
42
+ * `chart-5` is unreadable to anyone who cannot separate teal from red, and
43
+ * `chart-2` measures 2.49:1 against a light card, which is a second reason the
44
+ * legend is not optional here.
45
+ */
46
+ label: string;
47
+ /**
48
+ * The role this series is drawn in.
49
+ *
50
+ * The caller assigns it and the Component never does, because a series colour
51
+ * is a claim about what a thing is: the revenue line is the brand hue and the
52
+ * churn line is the alarming one, and a Component that picked by position
53
+ * would be making that claim on the caller's behalf and getting it wrong on
54
+ * every chart after the first.
55
+ */
56
+ color: ChartColor;
57
+ /**
58
+ * One value per category, in the order the categories are given.
59
+ *
60
+ * A value may be `null` for a gap, and a series shorter than the categories
61
+ * is padded with gaps rather than shifted: a value under the wrong category is
62
+ * a wrong answer that looks right, and it is the failure this shape is most
63
+ * likely to produce. A value longer than the categories is ignored, and it does
64
+ * not set the vertical scale either, because a value with no category has
65
+ * nowhere to be drawn.
66
+ */
67
+ values: readonly (number | null)[];
68
+ };
69
+ /** The props the ChartFrame accepts. */
70
+ export interface ChartFrameProps extends Omit<ComponentProps<'figure'>, 'children' | 'title'> {
71
+ /**
72
+ * The categories along the horizontal axis, in order. The caller's words.
73
+ *
74
+ * A month name, a release name, a region name, a build number. This is the
75
+ * array that makes a chart a claim about the caller's own domain rather than
76
+ * about a design system, which is why it is a prop and not a date range the
77
+ * Component generates: a chart that named its own months would be wrong in
78
+ * every product but the one it was written for.
79
+ */
80
+ categories: readonly string[];
81
+ /** The series, each with the role it is drawn in. */
82
+ series: readonly ChartSeries[];
83
+ /**
84
+ * The mark the series are drawn with. @defaultValue 'line'
85
+ *
86
+ * Every series in one chart takes the same mark. Mixing them is a chart about
87
+ * two different things, and a reader cannot tell which series the mark is
88
+ * making a claim about.
89
+ */
90
+ mark?: ChartMark;
91
+ /**
92
+ * The accessible name of the figure.
93
+ *
94
+ * Required rather than defaulted, because two charts on a dashboard are two
95
+ * figures a reader cannot tell apart, and because the name is the only thing
96
+ * that states what is being plotted. A figure named "Chart" is a Component
97
+ * shipping a sentence about a product it knows nothing about.
98
+ */
99
+ label: string;
100
+ /** The visible heading above the plot, when the surrounding layout has none. */
101
+ title?: ReactNode;
102
+ /**
103
+ * The words the data table is captioned with.
104
+ *
105
+ * Required, and separate from `label`, because the two are read at different
106
+ * points and mean different things: the name identifies the figure, the
107
+ * caption identifies the table a reader has just navigated into. Collapsing
108
+ * them would leave a caption that names the figure rather than the numbers.
109
+ */
110
+ tableCaption: ReactNode;
111
+ /**
112
+ * The head of the category column in the data table.
113
+ *
114
+ * Required for the same reason the series names are: the table's first column
115
+ * holds the caller's categories and a column of values with no head is a
116
+ * column of numbers a reader has to guess the meaning of. It is the word for
117
+ * whatever the categories are: "Month", "Region", "Build".
118
+ */
119
+ categoryHeading: string;
120
+ /**
121
+ * Whether the data table is seen as well as announced. @defaultValue 'visible'
122
+ *
123
+ * The table is always in the document either way. `hidden` moves it to
124
+ * `sr-only`, so it is still announced, still reachable by a screen reader's
125
+ * table navigation and still findable by the reader's own find-in-page; what
126
+ * changes is whether a sighted reader can see it. A consumer with a dense
127
+ * dashboard, where five visible tables under five plots is more surface than
128
+ * the page can carry, passes `hidden` and keeps the representation. A consumer
129
+ * whose readers are looking up exact figures leaves it visible, because a table
130
+ * nobody can see is a table a reader does not know to ask for.
131
+ */
132
+ table?: 'visible' | 'hidden';
133
+ /**
134
+ * The words for a value on the vertical axis, given the number.
135
+ *
136
+ * Defaults to the number itself, and deliberately not shared with the table:
137
+ * an axis has room for four characters and a table has room for a currency
138
+ * symbol, so the two jobs want different renderings and a consumer that
139
+ * compacts the axis wants the table to keep the exact figure. Passing `format`
140
+ * alone is the normal case and is exactly the split these two jobs want.
141
+ */
142
+ format?: (value: number) => string;
143
+ /**
144
+ * The words for a value in the data table, given the number.
145
+ *
146
+ * Defaults to the number itself, so a table of currency is a `tableFormat` away
147
+ * and a table of raw counts needs nothing.
148
+ */
149
+ tableFormat?: (value: number) => string;
150
+ /**
151
+ * What a reader is told when there is nothing to plot.
152
+ *
153
+ * An empty chart is a state a caller reaches on a first load, on a filter that
154
+ * matched nothing and on a range with no events in it, and an empty plot box
155
+ * with an axis and no marks reads as a broken chart rather than as an answer.
156
+ * The words are the caller's, because only the caller knows whether the answer
157
+ * is "no data yet" or "no results for this filter".
158
+ */
159
+ empty?: ReactNode;
160
+ /** Layout only, exactly as on every Component. */
161
+ className?: string;
162
+ }
163
+ /**
164
+ * A chart: the frame every chart in a product shares, and the numbers behind it.
165
+ *
166
+ * **One frame is the design.** Five charts that each draw their own axes are
167
+ * five sets of numbers that do not line up, and a dashboard where two plots
168
+ * disagree by six pixels looks broken in a way nobody can name, because the
169
+ * reader cannot see the six pixels: they can only see that the two charts do not
170
+ * agree. So the axis gutter, the field's height, the gridline spacing and the
171
+ * category row are all fixed here, and two charts in a row have the same plot box
172
+ * whatever their data says. The gutter in particular is a fixed width rather than
173
+ * a width that fits the longest label, because a gutter that grows with the
174
+ * numbers is the mechanism by which two charts stop aligning.
175
+ *
176
+ * **The colour is assigned by the caller and drawn from the token contract.**
177
+ * `chart-1` through `chart-5` arrive as the five semantic utilities, and there
178
+ * is no hex, no ramp step and no `rgb()` anywhere in this Component. A series
179
+ * colour is a claim about what a thing is, so the Component that knows nothing
180
+ * about the caller's product has no business choosing one.
181
+ *
182
+ * **The table is part of the frame, not a prop beside it, and that is the other
183
+ * half of the design.** A chart whose accessible representation is a sibling
184
+ * prop is a chart whose table a consumer wrote once and updated when the data
185
+ * changed, which is to say a chart whose table says last quarter. Here the marks
186
+ * and the cells are two renderings of one `series` array, so they cannot drift:
187
+ * there is no second copy to fall behind. The table is always in the document,
188
+ * and `table` decides whether it is also seen.
189
+ *
190
+ * **The legend is always drawn.** It is the only place the series-to-colour
191
+ * assignment is stated in words, and it is not optional because two of the five
192
+ * roles measure below 3:1 against a light card: `chart-2` at 2.49:1 and
193
+ * `chart-3` at 2.15:1 in the default pack. A mark in either is found by its
194
+ * position and its shape, not by its colour, so the name beside it is what makes
195
+ * it a series rather than a shape. Past about three series, hue is the only
196
+ * channel left and the table is the answer, which is a fact about the data and
197
+ * not about this Component.
198
+ *
199
+ * **It is a server Component.** No hook, no state, no effect and no event
200
+ * handler: the plot is arithmetic over props and the table is a table. A chart
201
+ * that animated its draw-in would be refused by this system's motion rules, and
202
+ * the one motion here is a bar resizing when its value changes, which is state
203
+ * feedback and is the only kind this system allows.
204
+ *
205
+ * **What this is not.** There is no scale beyond the one described above, no
206
+ * tick generation to choose, no hover tooltip, no crosshair, no zoom, no
207
+ * stacking, no curve interpolation, and no draw-in animation. A tooltip would be
208
+ * a hover affordance over a picture, and a picture a reader cannot query is the
209
+ * failure the table exists to prevent, so the exact value is answered by the
210
+ * table rather than by a surface that appears under a pointer. A consumer that
211
+ * needs any of those is reaching for a charting library, and this Component is
212
+ * the frame that library's marks should sit in rather than the library.
213
+ */
214
+ declare function ChartFrame({ categories, series, mark, label, title, tableCaption, categoryHeading, table, format, tableFormat, empty, className, ...props }: ChartFrameProps): import("react").JSX.Element;
215
+ export { ChartFrame };
216
+ //# sourceMappingURL=chart-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-frame.d.ts","sourceRoot":"","sources":["../../../src/components/ui/chart-frame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAatD;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAElF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,CAAA;AAEtC,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;;;OAQG;IACH,EAAE,EAAE,MAAM,CAAA;IACV;;;;;;;;OAQG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,KAAK,EAAE,UAAU,CAAA;IACjB;;;;;;;;;OASG;IACH,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAA;CACnC,CAAA;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAC3F;;;;;;;;OAQG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,qDAAqD;IACrD,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,CAAA;IACvB;;;;;;;OAOG;IACH,eAAe,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC5B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAkKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,iBAAS,UAAU,CAAC,EAClB,UAAU,EACV,MAAM,EACN,IAAa,EACb,KAAK,EACL,KAAK,EACL,YAAY,EACZ,eAAe,EACf,KAAiB,EACjB,MAAiB,EACjB,WAAsB,EACtB,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,+BAqTjB;AAED,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -0,0 +1,286 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from '../../lib/utils.js';
3
+ import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from './table.js';
4
+ /** The fill a series is drawn in, from the role the caller assigned it. */
5
+ const FILL = {
6
+ 'chart-1': 'bg-chart-1',
7
+ 'chart-2': 'bg-chart-2',
8
+ 'chart-3': 'bg-chart-3',
9
+ 'chart-4': 'bg-chart-4',
10
+ 'chart-5': 'bg-chart-5',
11
+ };
12
+ /** The stroke a line is drawn in, from the same role and no other. */
13
+ const STROKE = {
14
+ 'chart-1': 'stroke-chart-1',
15
+ 'chart-2': 'stroke-chart-2',
16
+ 'chart-3': 'stroke-chart-3',
17
+ 'chart-4': 'stroke-chart-4',
18
+ 'chart-5': 'stroke-chart-5',
19
+ };
20
+ /**
21
+ * Where a category label is nudged to sit, so a label never hangs off the plot.
22
+ *
23
+ * A label is centred on its category, which puts the first and the last half
24
+ * outside the field. The edge labels are pinned to the edge instead, so a line
25
+ * whose first and last points are on the boundary have their labels on it too.
26
+ * A bar's categories are column centres with room on both sides, so every label
27
+ * is centred.
28
+ */
29
+ const LABEL_ALIGN = {
30
+ first: 'translate-x-0',
31
+ last: '-translate-x-full',
32
+ middle: '-translate-x-1/2',
33
+ };
34
+ /**
35
+ * The step a gridline is drawn at: one, two, five or ten times a power of ten.
36
+ *
37
+ * The point is that the axis reads 0, 50, 100 rather than 0, 37, 74. A reader
38
+ * who has to do arithmetic to place a value on an axis is doing the chart's job,
39
+ * and a gridline at an arbitrary fraction of the range is the first thing that
40
+ * makes two charts look like they were built by two people.
41
+ */
42
+ function niceStep(rough) {
43
+ if (!Number.isFinite(rough) || rough <= 0)
44
+ return 1;
45
+ const magnitude = 10 ** Math.floor(Math.log10(rough));
46
+ const normalised = rough / magnitude;
47
+ const step = normalised <= 1 ? 1 : normalised <= 2 ? 2 : normalised <= 5 ? 5 : 10;
48
+ return step * magnitude;
49
+ }
50
+ /** A value that has been through a floor, a multiply and an add, back to a readable number. */
51
+ function tidy(value) {
52
+ return Number(value.toFixed(10));
53
+ }
54
+ /**
55
+ * The vertical scale, derived from the values that have somewhere to be drawn.
56
+ *
57
+ * **The floor is zero, and that is a claim about bars rather than about lines.**
58
+ * A bar's length is only a measurement when it starts at a shared baseline, so
59
+ * the scale always includes zero. The cost is that a line whose values sit
60
+ * between 900 and 1,000 is drawn almost flat, and the answer to that is a scale
61
+ * the caller chooses, not a floor this Component drops: a reader who needs to
62
+ * see the shape of a narrow range reads the table, which carries the numbers at
63
+ * full precision.
64
+ *
65
+ * **A negative value gets a real region below the baseline** rather than a
66
+ * zero-height bar. A profit-and-loss chart that drew a loss as nothing is a
67
+ * chart that is confidently wrong, and the cost of the region is one more
68
+ * gridline. Zero is always one of the lines, because both ends are multiples of
69
+ * the step, so the baseline is always on the axis and always labelled.
70
+ *
71
+ * **A series whose values are all the same number, or all zero, has no range to
72
+ * divide by.** It is given one step of scale so the field has a top and a
73
+ * bottom and every mark inside it is a position rather than a NaN.
74
+ */
75
+ function scaleOf(values) {
76
+ let lowest = 0;
77
+ let highest = 0;
78
+ for (const value of values) {
79
+ if (!Number.isFinite(value))
80
+ continue;
81
+ if (value < lowest)
82
+ lowest = value;
83
+ if (value > highest)
84
+ highest = value;
85
+ }
86
+ const step = niceStep((highest - lowest) / 4);
87
+ const first = Math.floor(lowest / step) * step;
88
+ // Ceiled rather than rounded, because a rounded count can leave the largest
89
+ // value above the top gridline, and a mark drawn above its own axis is a mark
90
+ // off the plot. The epsilon absorbs the float error that would otherwise add a
91
+ // whole extra gridline to an axis that already lands on one exactly.
92
+ const count = Math.max(1, Math.ceil((highest - first) / step - 1e-9));
93
+ const last = first + count * step;
94
+ const span = last - first;
95
+ const at = (value) => {
96
+ const clamped = Math.min(Math.max(value, first), last);
97
+ return ((last - clamped) / span) * 100;
98
+ };
99
+ return {
100
+ low: first,
101
+ high: last,
102
+ lines: Array.from({ length: count + 1 }, (_, index) => tidy(first + index * step)),
103
+ at,
104
+ // The bar spans between the value and the zero line, which are the same
105
+ // point when the value is zero. `at` counts from the top, so the top edge is
106
+ // whichever of the two is nearer the top and the height is the distance
107
+ // between them; a positive value therefore grows downward from its own top
108
+ // edge and a negative one hangs below the baseline.
109
+ bar: (value) => {
110
+ const here = at(value);
111
+ const baseline = at(0);
112
+ return { top: Math.min(here, baseline), height: Math.abs(here - baseline) };
113
+ },
114
+ };
115
+ }
116
+ /**
117
+ * Where a category sits on the horizontal axis, as a percentage of the field.
118
+ *
119
+ * A line's points run from the first category to the last, so the axis is the
120
+ * gap between them; a single category has no gap to divide by and sits in the
121
+ * middle, which is the only place a lone category can be read against anything.
122
+ * A bar's category is a column rather than a point, so its position is the
123
+ * column's centre, and that is the same reason a bar chart's first bar is not
124
+ * flush with the axis.
125
+ */
126
+ function categoryAt(index, total, mark) {
127
+ if (mark === 'bar')
128
+ return ((index + 0.5) / total) * 100;
129
+ if (total <= 1)
130
+ return 50;
131
+ return (index / (total - 1)) * 100;
132
+ }
133
+ /** Where a category label is nudged to sit, so no label hangs off the plot. */
134
+ function labelAlign(index, total, mark) {
135
+ if (mark === 'bar' || total < 2)
136
+ return LABEL_ALIGN.middle;
137
+ if (index === 0)
138
+ return LABEL_ALIGN.first;
139
+ if (index === total - 1)
140
+ return LABEL_ALIGN.last;
141
+ return LABEL_ALIGN.middle;
142
+ }
143
+ /** The default reading of a value: the number itself. */
144
+ function identity(value) {
145
+ return String(value);
146
+ }
147
+ /**
148
+ * A chart: the frame every chart in a product shares, and the numbers behind it.
149
+ *
150
+ * **One frame is the design.** Five charts that each draw their own axes are
151
+ * five sets of numbers that do not line up, and a dashboard where two plots
152
+ * disagree by six pixels looks broken in a way nobody can name, because the
153
+ * reader cannot see the six pixels: they can only see that the two charts do not
154
+ * agree. So the axis gutter, the field's height, the gridline spacing and the
155
+ * category row are all fixed here, and two charts in a row have the same plot box
156
+ * whatever their data says. The gutter in particular is a fixed width rather than
157
+ * a width that fits the longest label, because a gutter that grows with the
158
+ * numbers is the mechanism by which two charts stop aligning.
159
+ *
160
+ * **The colour is assigned by the caller and drawn from the token contract.**
161
+ * `chart-1` through `chart-5` arrive as the five semantic utilities, and there
162
+ * is no hex, no ramp step and no `rgb()` anywhere in this Component. A series
163
+ * colour is a claim about what a thing is, so the Component that knows nothing
164
+ * about the caller's product has no business choosing one.
165
+ *
166
+ * **The table is part of the frame, not a prop beside it, and that is the other
167
+ * half of the design.** A chart whose accessible representation is a sibling
168
+ * prop is a chart whose table a consumer wrote once and updated when the data
169
+ * changed, which is to say a chart whose table says last quarter. Here the marks
170
+ * and the cells are two renderings of one `series` array, so they cannot drift:
171
+ * there is no second copy to fall behind. The table is always in the document,
172
+ * and `table` decides whether it is also seen.
173
+ *
174
+ * **The legend is always drawn.** It is the only place the series-to-colour
175
+ * assignment is stated in words, and it is not optional because two of the five
176
+ * roles measure below 3:1 against a light card: `chart-2` at 2.49:1 and
177
+ * `chart-3` at 2.15:1 in the default pack. A mark in either is found by its
178
+ * position and its shape, not by its colour, so the name beside it is what makes
179
+ * it a series rather than a shape. Past about three series, hue is the only
180
+ * channel left and the table is the answer, which is a fact about the data and
181
+ * not about this Component.
182
+ *
183
+ * **It is a server Component.** No hook, no state, no effect and no event
184
+ * handler: the plot is arithmetic over props and the table is a table. A chart
185
+ * that animated its draw-in would be refused by this system's motion rules, and
186
+ * the one motion here is a bar resizing when its value changes, which is state
187
+ * feedback and is the only kind this system allows.
188
+ *
189
+ * **What this is not.** There is no scale beyond the one described above, no
190
+ * tick generation to choose, no hover tooltip, no crosshair, no zoom, no
191
+ * stacking, no curve interpolation, and no draw-in animation. A tooltip would be
192
+ * a hover affordance over a picture, and a picture a reader cannot query is the
193
+ * failure the table exists to prevent, so the exact value is answered by the
194
+ * table rather than by a surface that appears under a pointer. A consumer that
195
+ * needs any of those is reaching for a charting library, and this Component is
196
+ * the frame that library's marks should sit in rather than the library.
197
+ */
198
+ function ChartFrame({ categories, series, mark = 'line', label, title, tableCaption, categoryHeading, table = 'visible', format = identity, tableFormat = identity, empty, className, ...props }) {
199
+ // The scale is built from the values that have a category to be drawn against.
200
+ // A value past the end of the categories is neither drawn nor tabulated, and
201
+ // letting one into the scale would stretch the axis around a mark that is not
202
+ // in the picture, which is the quietest way a chart can be wrong.
203
+ const plotted = series.flatMap((entry) => categories
204
+ .map((_, index) => entry.values[index])
205
+ .filter((value) => typeof value === 'number' && Number.isFinite(value)));
206
+ const heading = title === undefined ? null : (_jsx("figcaption", { "data-slot": "chart-title", className: "text-sm font-medium", children: title }));
207
+ if (plotted.length === 0) {
208
+ // The empty state keeps the height of the field it replaces. A chart whose
209
+ // empty state is one line of text is a row on a dashboard that changes height
210
+ // when its data arrives, and the row that moves is the row the reader was
211
+ // looking at.
212
+ return (_jsxs("figure", { "data-slot": "chart-frame", "aria-label": label, className: cn('flex w-full flex-col gap-3', className), ...props, children: [heading, _jsx("div", { "data-slot": "chart-empty", className: "text-muted-foreground flex h-48 items-center justify-center rounded-md border border-dashed text-sm", children: empty ?? null })] }));
213
+ }
214
+ const scale = scaleOf(plotted);
215
+ const total = categories.length;
216
+ return (_jsxs("figure", { "data-slot": "chart-frame", "aria-label": label, className: cn('flex w-full flex-col gap-3', className), ...props, children: [heading, _jsxs("div", { "data-slot": "chart-plot", className: "flex flex-col gap-3", children: [_jsxs("div", { "data-slot": "chart-plot-row", className: "flex items-stretch gap-2", children: [_jsx("div", { "data-slot": "chart-axis-y", className: "relative w-14 shrink-0", children: scale.lines.map((value) => (_jsx("span", { "data-slot": "chart-tick", className: "text-muted-foreground absolute right-0 -translate-y-1/2 text-xs tabular-nums", style: { top: `${scale.at(value)}%` }, children: format(value) }, value))) }), _jsxs("div", { "data-slot": "chart-field", className: "relative h-48 flex-1", children: [scale.lines.map((value) => (_jsx("span", { "data-slot": "chart-gridline", "aria-hidden": "true", className: "border-border absolute inset-x-0 border-t", style: { top: `${scale.at(value)}%` } }, value))), mark === 'bar' ? (
217
+ /*
218
+ * One column per category, each holding one bar per series, so the
219
+ * series are side by side inside a category rather than stacked
220
+ * across the whole field. The column is a flex child, so a plot 600
221
+ * pixels wide and a plot 240 pixels wide divide the same way, which
222
+ * is the other half of the alignment claim.
223
+ */
224
+ _jsx("div", { "data-slot": "chart-columns", "aria-hidden": "true", className: "absolute inset-0 flex", children: categories.map((_, column) => (_jsx("div", { "data-slot": "chart-column", className: "relative flex-1", children: series.map((entry, seriesIndex) => {
225
+ const value = entry.values[column];
226
+ if (typeof value !== 'number' || !Number.isFinite(value))
227
+ return null;
228
+ const { top, height } = scale.bar(value);
229
+ const slot = 100 / series.length;
230
+ return (_jsx("span", { "data-slot": "chart-bar", "data-series": entry.id, "aria-hidden": "true", className: cn('absolute transition-[top,height] duration-base ease-out', value < 0 ? 'rounded-b-sm' : 'rounded-t-sm', FILL[entry.color]), style: {
231
+ // The one-pixel inset on each side is the gap between
232
+ // neighbouring series. A pixel rather than a
233
+ // percentage, because a percentage of a narrow column
234
+ // is less than a pixel, and a gap nobody can see is a
235
+ // grouping the colour has to carry on its own.
236
+ left: `calc(${seriesIndex * slot}% + 1px)`,
237
+ width: `calc(${slot}% - 2px)`,
238
+ top: `${top}%`,
239
+ height: `${height}%`,
240
+ } }, entry.id));
241
+ }) }, column))) })) : (series.map((entry) => {
242
+ const points = categories
243
+ .map((_, index) => ({ index, value: entry.values[index] }))
244
+ .filter((point) => typeof point.value === 'number' && Number.isFinite(point.value));
245
+ if (points.length === 0)
246
+ return null;
247
+ const place = (point) => ({
248
+ x: categoryAt(point.index, total, mark),
249
+ y: scale.at(point.value),
250
+ });
251
+ return (_jsxs("div", { "data-slot": "chart-line", "data-series": entry.id, className: "contents", children: [points.length > 1 ? (
252
+ /*
253
+ * The stroke is drawn as one `line` per pair of points, in
254
+ * percentages of the plot box, inside an `svg` with no
255
+ * `viewBox`. That is deliberate on both counts. No `viewBox`
256
+ * means no coordinate system to stretch, so a 2-pixel stroke
257
+ * is 2 pixels rather than 2 units in a box that was scaled
258
+ * unevenly, and it is why no `vector-effect` is needed
259
+ * either. Percentages mean a mark's position is a share of
260
+ * the plot box and not a number the caller has to agree on.
261
+ *
262
+ * One `line` per pair rather than a `polyline` is the
263
+ * other half, and it is a constraint rather than a
264
+ * preference: a Component here ships no reader-facing
265
+ * string, and a polyline's `points` is a list of coordinate
266
+ * pairs joined by a space, which is a word-shaped literal in
267
+ * a Component. Rounded caps on each segment meet at the
268
+ * joints as a 2-pixel circle, which is what a rounded line
269
+ * join draws anyway.
270
+ */
271
+ _jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 size-full overflow-visible", children: points.slice(1).map((point, index) => {
272
+ const from = place(points[index]);
273
+ const to = place(point);
274
+ return (_jsx("line", { x1: `${from.x}%`, y1: `${from.y}%`, x2: `${to.x}%`, y2: `${to.y}%`, className: STROKE[entry.color], strokeWidth: 2, strokeLinecap: "round" }, point.index));
275
+ }) })) : null, points.map((point) => (_jsx("span", { "data-slot": "chart-point", "data-series": entry.id, "aria-hidden": "true", className: cn('absolute size-2 -translate-x-1/2 -translate-y-1/2 rounded-full', FILL[entry.color]), style: {
276
+ left: `${categoryAt(point.index, total, mark)}%`,
277
+ top: `${scale.at(point.value)}%`,
278
+ } }, point.index)))] }, entry.id));
279
+ }))] })] }), _jsxs("div", { "data-slot": "chart-axis-x-row", className: "flex items-stretch gap-2", children: [_jsx("span", { "data-slot": "chart-axis-x-gutter", "aria-hidden": "true", className: "w-14 shrink-0" }), _jsx("div", { "data-slot": "chart-axis-x", className: "relative h-4 flex-1", children: categories.map((category, index) => (_jsx("span", { "data-slot": "chart-category", className: cn('text-muted-foreground absolute top-0 max-w-20 truncate text-xs', labelAlign(index, total, mark)), style: { left: `${categoryAt(index, total, mark)}%` }, children: category }, index))) })] })] }), _jsx("ul", { "data-slot": "chart-legend", className: "text-muted-foreground flex flex-wrap gap-x-4 gap-y-1 text-xs", children: series.map((entry) => (_jsxs("li", { "data-slot": "chart-legend-entry", className: "flex items-center gap-1.5", children: [_jsx("span", { "data-slot": "chart-swatch", "aria-hidden": "true", className: cn('size-2.5 shrink-0 rounded-sm', FILL[entry.color]) }), entry.label] }, entry.id))) }), _jsx("div", { "data-slot": "chart-table", className: table === 'visible' ? undefined : 'sr-only', children: _jsxs(Table, { children: [_jsx(TableCaption, { children: tableCaption }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-32", children: categoryHeading }), series.map((entry) => (_jsx(TableHead, { className: "text-right tabular-nums", children: entry.label }, entry.id)))] }) }), _jsx(TableBody, { children: categories.map((category, index) => (_jsxs(TableRow, { children: [_jsx(TableHead, { scope: "row", className: "font-medium", children: category }), series.map((entry) => {
280
+ const value = entry.values[index];
281
+ return (_jsx(TableCell, { className: "text-right tabular-nums", "data-series": entry.id, children: typeof value === 'number' && Number.isFinite(value)
282
+ ? tableFormat(value)
283
+ : null }, entry.id));
284
+ })] }, index))) })] }) })] }));
285
+ }
286
+ export { ChartFrame };
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';
4
4
  import { CheckIcon, MinusIcon } from 'lucide-react';
5
- import { cn } from '../../lib/utils';
5
+ import { cn } from '../../lib/utils.js';
6
6
  /**
7
7
  * A ticked or unticked choice, with an optional mixed state.
8
8
  *
@@ -0,0 +1,97 @@
1
+ import type { ComponentProps } from 'react';
2
+ /**
3
+ * The props the Collapsible root forwards to Base UI.
4
+ *
5
+ * Declared here rather than re-exported from Base UI so no upstream type
6
+ * crosses the package seam (ticket 07 section 6).
7
+ */
8
+ export interface CollapsibleProps extends Omit<ComponentProps<'div'>, 'onChange'> {
9
+ /** The controlled open state. */
10
+ open?: boolean;
11
+ /** The panel shown initially, for an uncontrolled collapsible. */
12
+ defaultOpen?: boolean;
13
+ /** Called when the panel opens or closes. */
14
+ onOpenChange?: (open: boolean) => void;
15
+ /** Whether the section ignores user interaction. @defaultValue false */
16
+ disabled?: boolean;
17
+ /**
18
+ * The stable id linking the trigger to the panel.
19
+ *
20
+ * A prop and not a generated one, because a section whose trigger and panel
21
+ * cannot both be found by name is hard to test, hard to restyle from a
22
+ * consumer's stylesheet, and impossible to wire an analytics event to without
23
+ * reaching into the DOM. It is the caller's own name for the section, so it is
24
+ * the caller's string.
25
+ */
26
+ id?: string;
27
+ }
28
+ /** The props CollapsibleTrigger forwards to Base UI. */
29
+ export interface CollapsibleTriggerProps extends ComponentProps<'button'> {
30
+ }
31
+ /**
32
+ * The props CollapsibleContent forwards to Base UI.
33
+ *
34
+ * Declared here rather than re-exported from Base UI so no upstream type
35
+ * crosses the package seam (ticket 07 section 6).
36
+ */
37
+ export interface CollapsibleContentProps extends ComponentProps<'div'> {
38
+ /**
39
+ * Whether the browser's page search may find and expand the closed panel.
40
+ *
41
+ * This is the decision a consumer would otherwise get wrong in the direction
42
+ * that loses content. A panel that unmounts when it closes cannot be found by
43
+ * the reader's own find-in-page, and the reader who searched for a word in a
44
+ * collapsed section is told the word is not on the page. Turning this on hides
45
+ * the panel with `hidden="until-found"` instead, so the browser can open it on
46
+ * a match and the reader lands on the text they searched for.
47
+ *
48
+ * @defaultValue false
49
+ */
50
+ hiddenUntilFound?: boolean;
51
+ /** Whether the closed panel stays in the DOM. @defaultValue false */
52
+ keepMounted?: boolean;
53
+ }
54
+ /**
55
+ * A section that shows and hides, in place.
56
+ *
57
+ * **A Collapsible is one section; an Accordion is a stack of them.** The
58
+ * difference is what a reader can do with the result. An accordion lets one
59
+ * section open by closing another, which is right for a set of peers and wrong
60
+ * for a page that needs two things visible at once. A collapsible keeps its own
61
+ * state and touches nothing else, so a reader can have three open, which is
62
+ * usually what a page of settings wants and an accordion forbids.
63
+ *
64
+ * **The trigger and the region are wired by the library, not by the caller.**
65
+ * A consumer who writes the two as siblings with their own `aria-expanded` gets a
66
+ * button that says "collapsed" while the panel is open, or a panel no button
67
+ * controls, and the failure is invisible until a reader meets it. Here the
68
+ * trigger is a real button carrying the expanded state and pointing at the
69
+ * panel, so Enter and Space open it and the announcement is right without the
70
+ * caller writing a line of it. The chevron turns on that same state rather than
71
+ * on a timer: something happened and the reader can see that it did.
72
+ *
73
+ * **A reader who collapses something must be able to find it again.** The
74
+ * trigger is always in the document and always carries its own words, so the way
75
+ * back never depends on the panel that was just closed, and a collapsed section
76
+ * never leaves a heading with nothing under it. Pass `hiddenUntilFound` on the
77
+ * content when the closed section holds text the reader may be searching for.
78
+ */
79
+ declare function Collapsible({ className, ...props }: CollapsibleProps): import("react").JSX.Element;
80
+ /**
81
+ * The button that shows and hides the panel.
82
+ *
83
+ * It is the way back, so it keeps its own words when the panel is closed, and it
84
+ * is a real button so Enter and Space work and so the reader can find it again
85
+ * with the keyboard after closing the section without their hands.
86
+ */
87
+ declare function CollapsibleTrigger({ className, children, ...props }: CollapsibleTriggerProps): import("react").JSX.Element;
88
+ /**
89
+ * The region the trigger shows and hides.
90
+ *
91
+ * The height transition is state feedback rather than decoration: the panel is
92
+ * opening or closing and the reader can watch it happen, which is the one motion
93
+ * this system allows on a surface like this.
94
+ */
95
+ declare function CollapsibleContent({ className, ...props }: CollapsibleContentProps): import("react").JSX.Element;
96
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
97
+ //# sourceMappingURL=collapsible.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collapsible.d.ts","sourceRoot":"","sources":["../../../src/components/ui/collapsible.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3C;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC/E,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,wDAAwD;AACxD,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,QAAQ,CAAC;CAAG;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,KAAK,CAAC;IACpE;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,qEAAqE;IACrE,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,+BAQ7D;AAED;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,+BAiBrF;AAED;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,+BAc3E;AAED,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAA"}