@keyfunc/keybricks-ds 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/DEPLOY.md +204 -0
  2. package/DESIGN.md +269 -0
  3. package/INTEGRATION.md +417 -0
  4. package/SKILL.md +32 -0
  5. package/_adherence.oxlintrc.json +1469 -0
  6. package/_scripts/build-index.mjs +80 -0
  7. package/_scripts/build-tokens.mjs +135 -0
  8. package/assets/icons/icon-index.js +1 -0
  9. package/assets/icons/icon-index.json +1 -0
  10. package/assets/icons/unicons-index.js +1 -0
  11. package/assets/icons/unicons-index.json +1 -0
  12. package/assets/keybricks-logo.png +0 -0
  13. package/components/_cards/accordion.card.html +32 -0
  14. package/components/_cards/action-chip.card.html +22 -0
  15. package/components/_cards/activity.card.html +34 -0
  16. package/components/_cards/alert.card.html +32 -0
  17. package/components/_cards/app-chrome.card.html +36 -0
  18. package/components/_cards/banner.card.html +21 -0
  19. package/components/_cards/brand-visual.card.html +43 -0
  20. package/components/_cards/breadcrumb.card.html +23 -0
  21. package/components/_cards/button-1-base.card.html +39 -0
  22. package/components/_cards/button-2-states.card.html +56 -0
  23. package/components/_cards/button-3-sizes-icons.card.html +32 -0
  24. package/components/_cards/button-4-group.card.html +47 -0
  25. package/components/_cards/canvas-zoom-controls.card.html +19 -0
  26. package/components/_cards/cards-indicators.card.html +59 -0
  27. package/components/_cards/charts-1-sparkline.card.html +55 -0
  28. package/components/_cards/charts-10-gauge.card.html +31 -0
  29. package/components/_cards/charts-11-funnel.card.html +28 -0
  30. package/components/_cards/charts-12-treemap.card.html +23 -0
  31. package/components/_cards/charts-14-radar.card.html +31 -0
  32. package/components/_cards/charts-15-distribution.card.html +32 -0
  33. package/components/_cards/charts-16-pareto.card.html +25 -0
  34. package/components/_cards/charts-17-candlestick.card.html +34 -0
  35. package/components/_cards/charts-18-sankey.card.html +29 -0
  36. package/components/_cards/charts-19-chord.card.html +31 -0
  37. package/components/_cards/charts-2-line.card.html +46 -0
  38. package/components/_cards/charts-20-sunburst.card.html +38 -0
  39. package/components/_cards/charts-21-worldmap.card.html +33 -0
  40. package/components/_cards/charts-22-gantt.card.html +37 -0
  41. package/components/_cards/charts-23-bar3d.card.html +26 -0
  42. package/components/_cards/charts-24-pie3d.card.html +32 -0
  43. package/components/_cards/charts-25-surface3d.card.html +24 -0
  44. package/components/_cards/charts-26-scatter3d.card.html +24 -0
  45. package/components/_cards/charts-27-ribbon3d.card.html +23 -0
  46. package/components/_cards/charts-3-bar.card.html +71 -0
  47. package/components/_cards/charts-4-barlist.card.html +30 -0
  48. package/components/_cards/charts-5-donut.card.html +35 -0
  49. package/components/_cards/charts-6-scatter.card.html +26 -0
  50. package/components/_cards/charts-7-heatmap.card.html +28 -0
  51. package/components/_cards/charts-8-waterfall.card.html +25 -0
  52. package/components/_cards/charts-9-bullet.card.html +31 -0
  53. package/components/_cards/charts-99-fullpage.card.html +194 -0
  54. package/components/_cards/checkbox.card.html +34 -0
  55. package/components/_cards/codeblock.card.html +19 -0
  56. package/components/_cards/combobox.card.html +25 -0
  57. package/components/_cards/context-menu.card.html +21 -0
  58. package/components/_cards/datatable.card.html +48 -0
  59. package/components/_cards/date-picker.card.html +31 -0
  60. package/components/_cards/diffvalue.card.html +21 -0
  61. package/components/_cards/divider.card.html +24 -0
  62. package/components/_cards/drawer.card.html +30 -0
  63. package/components/_cards/dropdown-menu.card.html +38 -0
  64. package/components/_cards/dropzone.card.html +22 -0
  65. package/components/_cards/empty-state.card.html +29 -0
  66. package/components/_cards/filter-bar.card.html +34 -0
  67. package/components/_cards/icon-button.card.html +27 -0
  68. package/components/_cards/icons-1-core.card.html +49 -0
  69. package/components/_cards/icons-2-feather.card.html +39 -0
  70. package/components/_cards/icons-3-unicons.card.html +44 -0
  71. package/components/_cards/input.card.html +25 -0
  72. package/components/_cards/kbd.card.html +20 -0
  73. package/components/_cards/keyvalue.card.html +23 -0
  74. package/components/_cards/listrow.card.html +30 -0
  75. package/components/_cards/loading.card.html +33 -0
  76. package/components/_cards/log-viewer.card.html +23 -0
  77. package/components/_cards/modal.card.html +35 -0
  78. package/components/_cards/multi-select.card.html +26 -0
  79. package/components/_cards/number-stepper.card.html +24 -0
  80. package/components/_cards/pagination.card.html +25 -0
  81. package/components/_cards/people.card.html +46 -0
  82. package/components/_cards/pipeline-node.card.html +76 -0
  83. package/components/_cards/pipeline-palette.card.html +54 -0
  84. package/components/_cards/search.card.html +23 -0
  85. package/components/_cards/select.card.html +23 -0
  86. package/components/_cards/slider.card.html +25 -0
  87. package/components/_cards/smart-table.card.html +37 -0
  88. package/components/_cards/spreadsheet.card.html +31 -0
  89. package/components/_cards/stepper.card.html +25 -0
  90. package/components/_cards/tabs.card.html +25 -0
  91. package/components/_cards/tag.card.html +26 -0
  92. package/components/_cards/textarea.card.html +27 -0
  93. package/components/_cards/timeline.card.html +32 -0
  94. package/components/_cards/toast.card.html +33 -0
  95. package/components/_cards/toggles.card.html +68 -0
  96. package/components/_cards/tooltip.card.html +22 -0
  97. package/components/_cards/tree.card.html +150 -0
  98. package/components/_shared/frame-theme.js +16 -0
  99. package/components/_shared/src-brand.js +1 -0
  100. package/components/_shared/src-buttons.js +1 -0
  101. package/components/_shared/src-charts.js +1 -0
  102. package/components/_shared/src-data.js +1 -0
  103. package/components/_shared/src-display.js +1 -0
  104. package/components/_shared/src-feedback.js +1 -0
  105. package/components/_shared/src-forms.js +1 -0
  106. package/components/_shared/src-icons.js +1 -0
  107. package/components/_shared/src-navigation.js +1 -0
  108. package/components/_shared/src-pipeline.js +1 -0
  109. package/components/_shared/src-tabs.js +1 -0
  110. package/components/_shared/svg-download.js +19 -0
  111. package/components/_shared/view-source.js +185 -0
  112. package/components/_vue/index.mjs +114 -0
  113. package/components/brand/BrandVisual.d.ts +17 -0
  114. package/components/brand/BrandVisual.jsx +29 -0
  115. package/components/brand/BrandVisual.vue +34 -0
  116. package/components/brand/_dir.card.html +13 -0
  117. package/components/brand/brandMotifs.js +210 -0
  118. package/components/buttons/ActionChip.d.ts +10 -0
  119. package/components/buttons/ActionChip.jsx +20 -0
  120. package/components/buttons/ActionChip.vue +18 -0
  121. package/components/buttons/Button.d.ts +40 -0
  122. package/components/buttons/Button.jsx +101 -0
  123. package/components/buttons/Button.vue +87 -0
  124. package/components/buttons/IconButton.d.ts +26 -0
  125. package/components/buttons/IconButton.jsx +28 -0
  126. package/components/buttons/IconButton.vue +21 -0
  127. package/components/buttons/_dir.card.html +13 -0
  128. package/components/buttons/buttons.prompt.md +19 -0
  129. package/components/charts/Bar3D.d.ts +15 -0
  130. package/components/charts/Bar3D.jsx +93 -0
  131. package/components/charts/Bar3D.vue +79 -0
  132. package/components/charts/BarChart.d.ts +23 -0
  133. package/components/charts/BarChart.jsx +135 -0
  134. package/components/charts/BarChart.vue +139 -0
  135. package/components/charts/BarList.d.ts +13 -0
  136. package/components/charts/BarList.jsx +31 -0
  137. package/components/charts/BarList.vue +25 -0
  138. package/components/charts/BoxPlot.d.ts +18 -0
  139. package/components/charts/BoxPlot.jsx +62 -0
  140. package/components/charts/BoxPlot.vue +58 -0
  141. package/components/charts/BulletChart.d.ts +12 -0
  142. package/components/charts/BulletChart.jsx +41 -0
  143. package/components/charts/BulletChart.vue +33 -0
  144. package/components/charts/CandlestickChart.d.ts +15 -0
  145. package/components/charts/CandlestickChart.jsx +63 -0
  146. package/components/charts/CandlestickChart.vue +62 -0
  147. package/components/charts/ChartKit.d.ts +8 -0
  148. package/components/charts/ChartKit.jsx +76 -0
  149. package/components/charts/ChartKit.vue +47 -0
  150. package/components/charts/ChordChart.d.ts +13 -0
  151. package/components/charts/ChordChart.jsx +55 -0
  152. package/components/charts/ChordChart.vue +54 -0
  153. package/components/charts/DonutChart.d.ts +15 -0
  154. package/components/charts/DonutChart.jsx +73 -0
  155. package/components/charts/DonutChart.vue +61 -0
  156. package/components/charts/FunnelChart.d.ts +11 -0
  157. package/components/charts/FunnelChart.jsx +37 -0
  158. package/components/charts/FunnelChart.vue +29 -0
  159. package/components/charts/GanttChart.d.ts +20 -0
  160. package/components/charts/GanttChart.jsx +80 -0
  161. package/components/charts/GanttChart.vue +60 -0
  162. package/components/charts/GaugeChart.d.ts +13 -0
  163. package/components/charts/GaugeChart.jsx +33 -0
  164. package/components/charts/GaugeChart.vue +29 -0
  165. package/components/charts/HeatmapChart.d.ts +13 -0
  166. package/components/charts/HeatmapChart.jsx +62 -0
  167. package/components/charts/HeatmapChart.vue +38 -0
  168. package/components/charts/Histogram.d.ts +16 -0
  169. package/components/charts/Histogram.jsx +56 -0
  170. package/components/charts/Histogram.vue +53 -0
  171. package/components/charts/LineChart.d.ts +17 -0
  172. package/components/charts/LineChart.jsx +84 -0
  173. package/components/charts/LineChart.vue +86 -0
  174. package/components/charts/ParetoChart.d.ts +13 -0
  175. package/components/charts/ParetoChart.jsx +54 -0
  176. package/components/charts/ParetoChart.vue +55 -0
  177. package/components/charts/Pie3D.d.ts +17 -0
  178. package/components/charts/Pie3D.jsx +145 -0
  179. package/components/charts/Pie3D.vue +115 -0
  180. package/components/charts/RadarChart.d.ts +14 -0
  181. package/components/charts/RadarChart.jsx +79 -0
  182. package/components/charts/RadarChart.vue +73 -0
  183. package/components/charts/Ribbon3D.d.ts +13 -0
  184. package/components/charts/Ribbon3D.jsx +89 -0
  185. package/components/charts/Ribbon3D.vue +79 -0
  186. package/components/charts/SankeyChart.d.ts +15 -0
  187. package/components/charts/SankeyChart.jsx +59 -0
  188. package/components/charts/SankeyChart.vue +53 -0
  189. package/components/charts/Scatter3D.d.ts +12 -0
  190. package/components/charts/Scatter3D.jsx +69 -0
  191. package/components/charts/Scatter3D.vue +59 -0
  192. package/components/charts/ScatterChart.d.ts +12 -0
  193. package/components/charts/ScatterChart.jsx +53 -0
  194. package/components/charts/ScatterChart.vue +57 -0
  195. package/components/charts/Sparkline.d.ts +19 -0
  196. package/components/charts/Sparkline.jsx +63 -0
  197. package/components/charts/Sparkline.vue +54 -0
  198. package/components/charts/SunburstChart.d.ts +12 -0
  199. package/components/charts/SunburstChart.jsx +61 -0
  200. package/components/charts/SunburstChart.vue +60 -0
  201. package/components/charts/Surface3D.d.ts +13 -0
  202. package/components/charts/Surface3D.jsx +70 -0
  203. package/components/charts/Surface3D.vue +63 -0
  204. package/components/charts/TreemapChart.d.ts +11 -0
  205. package/components/charts/TreemapChart.jsx +35 -0
  206. package/components/charts/TreemapChart.vue +27 -0
  207. package/components/charts/WaterfallChart.d.ts +11 -0
  208. package/components/charts/WaterfallChart.jsx +59 -0
  209. package/components/charts/WaterfallChart.vue +46 -0
  210. package/components/charts/WorldMap.d.ts +21 -0
  211. package/components/charts/WorldMap.jsx +107 -0
  212. package/components/charts/WorldMap.vue +73 -0
  213. package/components/charts/_dir.card.html +7 -0
  214. package/components/charts/chart-3d.js +84 -0
  215. package/components/charts/chart-geometry.js +223 -0
  216. package/components/charts/charts.prompt.md +24 -0
  217. package/components/data/CodeBlock.d.ts +9 -0
  218. package/components/data/CodeBlock.jsx +19 -0
  219. package/components/data/CodeBlock.vue +22 -0
  220. package/components/data/DataTable.d.ts +28 -0
  221. package/components/data/DataTable.jsx +32 -0
  222. package/components/data/DataTable.vue +25 -0
  223. package/components/data/DiffValue.d.ts +11 -0
  224. package/components/data/DiffValue.jsx +15 -0
  225. package/components/data/DiffValue.vue +13 -0
  226. package/components/data/FilterBar.d.ts +16 -0
  227. package/components/data/FilterBar.jsx +22 -0
  228. package/components/data/FilterBar.vue +23 -0
  229. package/components/data/KeyValue.d.ts +16 -0
  230. package/components/data/KeyValue.jsx +25 -0
  231. package/components/data/KeyValue.vue +26 -0
  232. package/components/data/ListRow.d.ts +14 -0
  233. package/components/data/ListRow.jsx +25 -0
  234. package/components/data/ListRow.vue +25 -0
  235. package/components/data/Pagination.d.ts +12 -0
  236. package/components/data/Pagination.jsx +27 -0
  237. package/components/data/Pagination.vue +24 -0
  238. package/components/data/SmartTable.d.ts +30 -0
  239. package/components/data/SmartTable.jsx +73 -0
  240. package/components/data/SmartTable.vue +74 -0
  241. package/components/data/Spreadsheet.d.ts +33 -0
  242. package/components/data/Spreadsheet.jsx +110 -0
  243. package/components/data/Spreadsheet.vue +95 -0
  244. package/components/data/_dir.card.html +13 -0
  245. package/components/data/data.prompt.md +33 -0
  246. package/components/display/Avatar.d.ts +21 -0
  247. package/components/display/Avatar.jsx +31 -0
  248. package/components/display/Avatar.vue +34 -0
  249. package/components/display/Badge.d.ts +27 -0
  250. package/components/display/Badge.jsx +40 -0
  251. package/components/display/Badge.vue +47 -0
  252. package/components/display/Card.d.ts +27 -0
  253. package/components/display/Card.jsx +27 -0
  254. package/components/display/Card.vue +28 -0
  255. package/components/display/Divider.d.ts +8 -0
  256. package/components/display/Divider.jsx +14 -0
  257. package/components/display/Divider.vue +13 -0
  258. package/components/display/Kbd.d.ts +7 -0
  259. package/components/display/Kbd.jsx +12 -0
  260. package/components/display/Kbd.vue +9 -0
  261. package/components/display/ProgressBar.d.ts +42 -0
  262. package/components/display/ProgressBar.jsx +70 -0
  263. package/components/display/ProgressBar.vue +69 -0
  264. package/components/display/Tag.d.ts +9 -0
  265. package/components/display/Tag.jsx +15 -0
  266. package/components/display/Tag.vue +15 -0
  267. package/components/display/_dir.card.html +13 -0
  268. package/components/display/display.prompt.md +30 -0
  269. package/components/feedback/Accordion.d.ts +13 -0
  270. package/components/feedback/Accordion.jsx +21 -0
  271. package/components/feedback/Accordion.vue +22 -0
  272. package/components/feedback/ActivityItem.d.ts +25 -0
  273. package/components/feedback/ActivityItem.jsx +39 -0
  274. package/components/feedback/ActivityItem.vue +46 -0
  275. package/components/feedback/Alert.d.ts +36 -0
  276. package/components/feedback/Alert.jsx +48 -0
  277. package/components/feedback/Alert.vue +59 -0
  278. package/components/feedback/Banner.d.ts +15 -0
  279. package/components/feedback/Banner.jsx +25 -0
  280. package/components/feedback/Banner.vue +25 -0
  281. package/components/feedback/ContextMenu.d.ts +8 -0
  282. package/components/feedback/ContextMenu.jsx +18 -0
  283. package/components/feedback/ContextMenu.vue +19 -0
  284. package/components/feedback/Drawer.d.ts +18 -0
  285. package/components/feedback/Drawer.jsx +26 -0
  286. package/components/feedback/Drawer.vue +24 -0
  287. package/components/feedback/Menu.d.ts +10 -0
  288. package/components/feedback/Menu.jsx +24 -0
  289. package/components/feedback/Menu.vue +21 -0
  290. package/components/feedback/Modal.d.ts +38 -0
  291. package/components/feedback/Modal.jsx +69 -0
  292. package/components/feedback/Modal.vue +65 -0
  293. package/components/feedback/Stepper.d.ts +9 -0
  294. package/components/feedback/Stepper.jsx +24 -0
  295. package/components/feedback/Stepper.vue +18 -0
  296. package/components/feedback/Timeline.d.ts +15 -0
  297. package/components/feedback/Timeline.jsx +27 -0
  298. package/components/feedback/Timeline.vue +21 -0
  299. package/components/feedback/_dir.card.html +13 -0
  300. package/components/feedback/feedback.prompt.md +35 -0
  301. package/components/forms/Checkbox.d.ts +13 -0
  302. package/components/forms/Checkbox.jsx +17 -0
  303. package/components/forms/Checkbox.vue +18 -0
  304. package/components/forms/Combobox.d.ts +13 -0
  305. package/components/forms/Combobox.jsx +34 -0
  306. package/components/forms/Combobox.vue +35 -0
  307. package/components/forms/DatePicker.d.ts +12 -0
  308. package/components/forms/DatePicker.jsx +48 -0
  309. package/components/forms/DatePicker.vue +46 -0
  310. package/components/forms/Dropzone.d.ts +20 -0
  311. package/components/forms/Dropzone.jsx +36 -0
  312. package/components/forms/Dropzone.vue +28 -0
  313. package/components/forms/Field.d.ts +13 -0
  314. package/components/forms/Field.jsx +13 -0
  315. package/components/forms/Field.vue +12 -0
  316. package/components/forms/Input.d.ts +18 -0
  317. package/components/forms/Input.jsx +35 -0
  318. package/components/forms/Input.vue +38 -0
  319. package/components/forms/MultiSelect.d.ts +14 -0
  320. package/components/forms/MultiSelect.jsx +33 -0
  321. package/components/forms/MultiSelect.vue +36 -0
  322. package/components/forms/NumberStepper.d.ts +13 -0
  323. package/components/forms/NumberStepper.jsx +15 -0
  324. package/components/forms/NumberStepper.vue +17 -0
  325. package/components/forms/Radio.vue +16 -0
  326. package/components/forms/RadioGroup.vue +11 -0
  327. package/components/forms/SearchInput.d.ts +12 -0
  328. package/components/forms/SearchInput.jsx +17 -0
  329. package/components/forms/SearchInput.vue +14 -0
  330. package/components/forms/Select.d.ts +14 -0
  331. package/components/forms/Select.jsx +17 -0
  332. package/components/forms/Select.vue +16 -0
  333. package/components/forms/Slider.d.ts +15 -0
  334. package/components/forms/Slider.jsx +21 -0
  335. package/components/forms/Slider.vue +23 -0
  336. package/components/forms/Switch.d.ts +31 -0
  337. package/components/forms/Switch.jsx +40 -0
  338. package/components/forms/Switch.vue +16 -0
  339. package/components/forms/_dir.card.html +13 -0
  340. package/components/forms/forms.prompt.md +23 -0
  341. package/components/icons/Icon.d.ts +19 -0
  342. package/components/icons/Icon.jsx +93 -0
  343. package/components/icons/Icon.prompt.md +8 -0
  344. package/components/icons/Icon.vue +12 -0
  345. package/components/icons/Unicon.d.ts +12 -0
  346. package/components/icons/Unicon.jsx +16 -0
  347. package/components/icons/Unicon.vue +15 -0
  348. package/components/icons/_dir.card.html +13 -0
  349. package/components/icons/feather-paths.js +2 -0
  350. package/components/icons/icon-paths.js +73 -0
  351. package/components/index.d.ts +87 -0
  352. package/components/index.js +110 -0
  353. package/components/navigation/Breadcrumb.d.ts +24 -0
  354. package/components/navigation/Breadcrumb.jsx +120 -0
  355. package/components/navigation/Breadcrumb.vue +94 -0
  356. package/components/navigation/DocTree.d.ts +18 -0
  357. package/components/navigation/DocTree.jsx +37 -0
  358. package/components/navigation/DocTree.vue +25 -0
  359. package/components/navigation/Sidebar.d.ts +29 -0
  360. package/components/navigation/Sidebar.jsx +75 -0
  361. package/components/navigation/Sidebar.vue +54 -0
  362. package/components/navigation/TopBar.d.ts +27 -0
  363. package/components/navigation/TopBar.jsx +44 -0
  364. package/components/navigation/TopBar.vue +51 -0
  365. package/components/navigation/Tree.d.ts +83 -0
  366. package/components/navigation/Tree.jsx +347 -0
  367. package/components/navigation/Tree.vue +258 -0
  368. package/components/navigation/_dir.card.html +13 -0
  369. package/components/navigation/navigation.prompt.md +22 -0
  370. package/components/pipeline/ConfigPanel.d.ts +18 -0
  371. package/components/pipeline/ConfigPanel.jsx +30 -0
  372. package/components/pipeline/ConfigPanel.vue +32 -0
  373. package/components/pipeline/DataPreview.d.ts +14 -0
  374. package/components/pipeline/DataPreview.jsx +24 -0
  375. package/components/pipeline/DataPreview.vue +21 -0
  376. package/components/pipeline/NodeCard.d.ts +47 -0
  377. package/components/pipeline/NodeCard.jsx +97 -0
  378. package/components/pipeline/NodeCard.vue +75 -0
  379. package/components/pipeline/PaletteItem.vue +15 -0
  380. package/components/pipeline/PaletteListItem.vue +15 -0
  381. package/components/pipeline/PalettePanel.d.ts +20 -0
  382. package/components/pipeline/PalettePanel.jsx +74 -0
  383. package/components/pipeline/PalettePanel.vue +45 -0
  384. package/components/pipeline/PipelineCanvas.d.ts +25 -0
  385. package/components/pipeline/PipelineCanvas.jsx +94 -0
  386. package/components/pipeline/PipelineCanvas.vue +73 -0
  387. package/components/pipeline/ZoomControls.d.ts +9 -0
  388. package/components/pipeline/ZoomControls.jsx +15 -0
  389. package/components/pipeline/ZoomControls.vue +15 -0
  390. package/components/pipeline/_dir.card.html +13 -0
  391. package/components/pipeline/pipeline.prompt.md +22 -0
  392. package/components/tabs/SegmentedControl.d.ts +11 -0
  393. package/components/tabs/SegmentedControl.jsx +21 -0
  394. package/components/tabs/SegmentedControl.vue +15 -0
  395. package/components/tabs/Tabs.d.ts +13 -0
  396. package/components/tabs/Tabs.jsx +52 -0
  397. package/components/tabs/Tabs.vue +39 -0
  398. package/components/tabs/ViewToggle.d.ts +8 -0
  399. package/components/tabs/ViewToggle.jsx +12 -0
  400. package/components/tabs/ViewToggle.vue +12 -0
  401. package/components/tabs/_dir.card.html +13 -0
  402. package/components/tabs/tabs.prompt.md +14 -0
  403. package/package.json +76 -0
  404. package/readme.md +150 -0
  405. package/styles.css +10 -0
  406. package/tokens/alpha.css +27 -0
  407. package/tokens/charts.css +55 -0
  408. package/tokens/colors-extended.css +40 -0
  409. package/tokens/colors.css +39 -0
  410. package/tokens/dark.css +96 -0
  411. package/tokens/fonts.css +4 -0
  412. package/tokens/interaction.css +48 -0
  413. package/tokens/layout.css +30 -0
  414. package/tokens/motion.css +22 -0
  415. package/tokens/tokens.d.ts +680 -0
  416. package/tokens/tokens.js +1018 -0
  417. package/tokens/tokens.json +2928 -0
  418. package/tokens/typography.css +22 -0
@@ -0,0 +1,93 @@
1
+ import React from "react";
2
+ import { fmtNum, seriesColor, stack, niceTicks } from "./chart-geometry.js";
3
+ import { makeProj, cuboidFaces, shade, dragRotate, facePath } from "./chart-3d.js";
4
+ import { ChartTooltip, wrapStyle } from "./ChartKit.jsx";
5
+
6
+ const FACE_SHADE = { top: 0.22, front: 0, right: -0.22, left: -0.3, back: -0.1, bottom: -0.4 };
7
+
8
+ /**
9
+ * 3D bars — categories along x, series as depth rows (or stacked in place). Drag to orbit.
10
+ * mode "grouped" (one depth row per series) | "stacked" (segments pile up in one row).
11
+ */
12
+ export function Bar3D({ series = [], categories = [], width = 460, height = 300, yaw: yaw0 = 30, pitch: pitch0 = 26, mode = "grouped", gap = 0.35, interactive = true, valueFormat = fmtNum, style }) {
13
+ const [rot, setRot] = React.useState({ yaw: yaw0, pitch: pitch0 });
14
+ const [hover, setHover] = React.useState(null);
15
+ const n = categories.length || Math.max(...series.map((s) => s.values.length));
16
+ const m = mode === "grouped" ? series.length : 1;
17
+ const hi = mode === "stacked"
18
+ ? Math.max(...[...Array(n)].map((_, i) => series.reduce((t, s) => t + (s.values[i] || 0), 0)))
19
+ : Math.max(...series.flatMap((s) => s.values));
20
+ const bands = mode === "stacked" ? stack(series.map((s) => s.values)) : null;
21
+ const scale = Math.min(width, height) * 0.42;
22
+ const P = makeProj(width / 2, height / 2 + height * 0.06, scale, rot.yaw, rot.pitch);
23
+ const bw = (1 - gap) / n, bd = (1 - gap) / Math.max(m, 2);
24
+ const bars = [];
25
+ series.forEach((s, si) => s.values.forEach((v, i) => {
26
+ if (v == null) return;
27
+ const x0 = i / n - 0.5 + (gap / n) / 2, x1 = x0 + bw / (n / (n)) * (1 / 1) * (bw > 0 ? 1 : 1) * 1;
28
+ const cx0 = i / n - 0.5 + (1 / n) * (gap / 2), cx1 = cx0 + (1 / n) * (1 - gap);
29
+ const zi = mode === "grouped" ? si : 0;
30
+ const cz0 = zi / Math.max(m, 1) - 0.5 + (1 / Math.max(m, 2)) * (gap / 2), cz1 = cz0 + (1 / Math.max(m, 2)) * (1 - gap);
31
+ const y0 = mode === "stacked" ? (bands[si][i][0] / hi) : 0;
32
+ const y1 = mode === "stacked" ? (bands[si][i][1] / hi) : v / hi;
33
+ bars.push({ s, si, i, v, x0: cx0, x1: cx1, z0: cz0, z1: cz1, y0: (y0 - 0.5) * 0.9, y1: (y1 - 0.5) * 0.9 });
34
+ }));
35
+ // draw far-to-near: sort by projected center depth
36
+ const drawn = bars.map((b, key) => {
37
+ const faces = cuboidFaces(P, b.x0, b.x1, b.y0, b.y1, b.z0, b.z1);
38
+ const depth = faces.reduce((s2, f) => s2 + f.depth, 0) / faces.length;
39
+ const top = P((b.x0 + b.x1) / 2, b.y1, (b.z0 + b.z1) / 2);
40
+ return { ...b, key, faces, depth, top, color: b.s.color || seriesColor(b.si) };
41
+ }).sort((a, b2) => b2.depth - a.depth);
42
+ const base = [P(-0.5, -0.46, -0.5), P(0.5, -0.46, -0.5), P(0.5, -0.46, 0.5), P(-0.5, -0.46, 0.5)];
43
+ /* Vertical value axis with real ticks at the front-left corner. */
44
+ const vt = niceTicks(0, hi, 3);
45
+ const vTicks = vt.ticks.filter((t) => t <= hi * 1.001).map((t) => ({ p: P(-0.5, (t / hi - 0.5) * 0.9, 0.5), v: valueFormat(t) }));
46
+ const vAxis = [P(-0.5, -0.45, 0.5), P(-0.5, 0.45, 0.5)];
47
+ const handlers = interactive ? dragRotate(() => rot, setRot) : {};
48
+ return (
49
+ <div style={{ ...wrapStyle, ...style }}>
50
+ <svg width={width} height={height} {...handlers} role="img" style={{ cursor: interactive ? "grab" : "default", touchAction: "none" }}>
51
+ <path d={facePath(base)} fill="var(--bg-inset)" stroke="var(--chart-axis)" strokeWidth="1" />
52
+ <line x1={vAxis[0].x} y1={vAxis[0].y} x2={vAxis[1].x} y2={vAxis[1].y} stroke="var(--chart-guide)" strokeWidth="1" />
53
+ {vTicks.map((t, i) => (
54
+ <g key={"vt" + i}>
55
+ <line x1={t.p.x - 4} y1={t.p.y} x2={t.p.x} y2={t.p.y} stroke="var(--chart-guide)" strokeWidth="1" />
56
+ <text x={t.p.x - 7} y={t.p.y + 3} textAnchor="end" style={{ fontFamily: "var(--font-mono)", fontSize: 9, fill: "var(--chart-label)" }}>{t.v}</text>
57
+ </g>
58
+ ))}
59
+ {categories.map((c, i) => {
60
+ /* Labels follow whichever base edge currently faces the viewer (larger screen y),
61
+ sit outside it, and paint BEFORE the bars, so they never cover a column. */
62
+ const zE = P(0, -0.46, 0.5).y >= P(0, -0.46, -0.5).y ? 0.5 : -0.5;
63
+ const p0 = P(i / n - 0.5 + 0.5 / n, -0.46, zE);
64
+ const ctr = P(0, -0.46, 0);
65
+ const dx = p0.x - ctr.x, dy = p0.y - ctr.y, len = Math.hypot(dx, dy) || 1;
66
+ return <text key={"c" + i} x={p0.x + (dx / len) * 18} y={p0.y + (dy / len) * 18 + 3} textAnchor="middle" style={{ fontFamily: "var(--font-sans)", fontSize: 10, fill: "var(--chart-label)" }}>{c}</text>;
67
+ })}
68
+ {drawn.map((b) => (
69
+ <g key={b.key} opacity={hover == null || hover === b.key ? 1 : 0.45}
70
+ onMouseEnter={interactive ? () => setHover(b.key) : undefined} onMouseLeave={interactive ? () => setHover(null) : undefined}
71
+ style={{ transition: "opacity var(--dur-fast) var(--ease-out)" }}>
72
+ {b.faces.map((f, fi) => (
73
+ <path key={fi} d={f.d} fill={shade(b.color, FACE_SHADE[f.kind])} stroke="var(--bg-surface)" strokeWidth="0.4" />
74
+ ))}
75
+ </g>
76
+ ))}
77
+ </svg>
78
+ {hover != null && interactive ? (() => {
79
+ const b = drawn.find((x) => x.key === hover);
80
+ return <ChartTooltip x={b.top.x} y={b.top.y} flip={b.top.x > width * 0.6} title={categories[b.i]}
81
+ rows={[{ label: b.s.name, value: valueFormat(b.v), color: b.color }]} />;
82
+ })() : null}
83
+ <div style={{ display: "flex", gap: 14, paddingTop: 6, lineHeight: "normal" }}>
84
+ {series.map((s, i) => (
85
+ <span key={s.name} style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
86
+ <span style={{ width: 8, height: 8, borderRadius: 2, background: s.color || seriesColor(i) }} />
87
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", color: "var(--text-secondary)" }}>{s.name}</span>
88
+ </span>
89
+ ))}
90
+ </div>
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1,79 @@
1
+ <script setup>
2
+ import { ref, computed } from 'vue'
3
+ import { fmtNum, SERIES, stack, niceTicks } from './chart-geometry.js'
4
+ import { makeProj, cuboidFaces, shade, facePath } from './chart-3d.js'
5
+ // 3D cuboid bars — categories on x, series as depth rows or stacked; drag to orbit.
6
+ const FACE_SHADE = { top:0.22, front:0, right:-0.22, left:-0.3, back:-0.1, bottom:-0.4 }
7
+ const props = defineProps({ series:{type:Array,default:()=>[]}, categories:{type:Array,default:()=>[]}, mode:{type:String,default:'grouped'}, width:{type:Number,default:460}, height:{type:Number,default:300}, yaw:{type:Number,default:30}, pitch:{type:Number,default:26}, gap:{type:Number,default:0.35}, interactive:{type:Boolean,default:true}, valueFormat:{type:Function,default:fmtNum}, style:Object })
8
+ const rot = ref({ yaw: props.yaw, pitch: props.pitch })
9
+ const hover = ref(null)
10
+ function down(e){ e.currentTarget.__kbDrag = { x:e.clientX, y:e.clientY, ...rot.value }; e.currentTarget.setPointerCapture && e.currentTarget.setPointerCapture(e.pointerId) }
11
+ function move(e){ const s = e.currentTarget.__kbDrag; if(!s) return; rot.value = { yaw: s.yaw+(e.clientX-s.x)*0.45, pitch: Math.max(8, Math.min(80, s.pitch+(e.clientY-s.y)*0.35)) } }
12
+ function up(e){ e.currentTarget.__kbDrag = null }
13
+ const g = computed(() => {
14
+ const n = props.categories.length || Math.max(...props.series.map(s=>s.values.length))
15
+ const m = props.mode==='grouped' ? props.series.length : 1
16
+ const hi = props.mode==='stacked'
17
+ ? Math.max(...[...Array(n)].map((_,i)=>props.series.reduce((t,s)=>t+(s.values[i]||0),0)))
18
+ : Math.max(...props.series.flatMap(s=>s.values))
19
+ const bands = props.mode==='stacked' ? stack(props.series.map(s=>s.values)) : null
20
+ const scale = Math.min(props.width, props.height)*0.42
21
+ const P = makeProj(props.width/2, props.height/2 + props.height*0.06, scale, rot.value.yaw, rot.value.pitch)
22
+ const bars = []
23
+ props.series.forEach((s,si)=>s.values.forEach((v,i)=>{
24
+ if (v==null) return
25
+ const cx0 = i/n-0.5+(1/n)*(props.gap/2), cx1 = cx0+(1/n)*(1-props.gap)
26
+ const zi = props.mode==='grouped' ? si : 0
27
+ const cz0 = zi/Math.max(m,1)-0.5+(1/Math.max(m,2))*(props.gap/2), cz1 = cz0+(1/Math.max(m,2))*(1-props.gap)
28
+ const y0 = props.mode==='stacked' ? (bands[si][i][0]/hi) : 0
29
+ const y1 = props.mode==='stacked' ? (bands[si][i][1]/hi) : v/hi
30
+ bars.push({ s, si, i, v, x0:cx0, x1:cx1, z0:cz0, z1:cz1, y0:(y0-0.5)*0.9, y1:(y1-0.5)*0.9 })
31
+ }))
32
+ const drawn = bars.map((b,key)=>{
33
+ const faces = cuboidFaces(P, b.x0, b.x1, b.y0, b.y1, b.z0, b.z1)
34
+ return { ...b, key, faces, depth: faces.reduce((s2,f)=>s2+f.depth,0)/faces.length,
35
+ top: P((b.x0+b.x1)/2, b.y1, (b.z0+b.z1)/2), color: b.s.color || SERIES[b.si%SERIES.length] }
36
+ }).sort((a,b2)=>b2.depth-a.depth)
37
+ const base = [P(-0.5,-0.46,-0.5), P(0.5,-0.46,-0.5), P(0.5,-0.46,0.5), P(-0.5,-0.46,0.5)]
38
+ const vt = niceTicks(0, hi, 3)
39
+ const vTicks = vt.ticks.filter(t=>t<=hi*1.001).map(t=>({ p:P(-0.5,(t/hi-0.5)*0.9,0.5), v:props.valueFormat(t) }))
40
+ // Labels follow whichever base edge faces the viewer, outside it.
41
+ const zE = P(0,-0.46,0.5).y >= P(0,-0.46,-0.5).y ? 0.5 : -0.5
42
+ const ctr = P(0,-0.46,0)
43
+ const labels = props.categories.map((c,i)=>{
44
+ const p0 = P(i/n-0.5+0.5/n, -0.46, zE)
45
+ const dx = p0.x-ctr.x, dy = p0.y-ctr.y, len = Math.hypot(dx,dy)||1
46
+ return { c, x: p0.x+(dx/len)*18, y: p0.y+(dy/len)*18+3 }
47
+ })
48
+ return { drawn, basePath: facePath(base), vTicks, vAxis:[P(-0.5,-0.45,0.5), P(-0.5,0.45,0.5)], labels, FACE_SHADE }
49
+ })
50
+ </script>
51
+ <template>
52
+ <div :style="{position:'relative',display:'inline-block',lineHeight:0,...style}">
53
+ <svg :width="width" :height="height" @pointerdown="down" @pointermove="move" @pointerup="up" @pointerleave="up"
54
+ role="img" :style="{cursor:interactive?'grab':'default',touchAction:'none'}">
55
+ <path :d="g.basePath" fill="var(--bg-inset)" stroke="var(--chart-axis)" stroke-width="1"/>
56
+ <line :x1="g.vAxis[0].x" :y1="g.vAxis[0].y" :x2="g.vAxis[1].x" :y2="g.vAxis[1].y" stroke="var(--chart-guide)" stroke-width="1"/>
57
+ <g v-for="(t,i) in g.vTicks" :key="'vt'+i">
58
+ <line :x1="t.p.x-4" :y1="t.p.y" :x2="t.p.x" :y2="t.p.y" stroke="var(--chart-guide)" stroke-width="1"/>
59
+ <text :x="t.p.x-7" :y="t.p.y+3" text-anchor="end" style="font-family:var(--font-mono);font-size:9px;fill:var(--chart-label)">{{ t.v }}</text>
60
+ </g>
61
+ <text v-for="(l,i) in g.labels" :key="'c'+i" :x="l.x" :y="l.y" text-anchor="middle" style="font-family:var(--font-sans);font-size:10px;fill:var(--chart-label)">{{ l.c }}</text>
62
+ <g v-for="b in g.drawn" :key="b.key" :opacity="hover==null||hover===b.key?1:0.45"
63
+ @mouseenter="interactive && (hover=b.key)" @mouseleave="interactive && (hover=null)"
64
+ style="transition:opacity var(--dur-fast) var(--ease-out)">
65
+ <path v-for="(f,fi) in b.faces" :key="fi" :d="f.d" :fill="shade(b.color, g.FACE_SHADE[f.kind])" stroke="var(--bg-surface)" stroke-width="0.4"/>
66
+ </g>
67
+ </svg>
68
+ <div v-if="hover!=null && interactive" :style="{position:'absolute',left:g.drawn.find(b=>b.key===hover).top.x+'px',top:g.drawn.find(b=>b.key===hover).top.y+'px',transform:g.drawn.find(b=>b.key===hover).top.x>width*0.6?'translate(calc(-100% - 10px),-50%)':'translate(10px,-50%)',pointerEvents:'none',zIndex:5,background:'var(--bg-surface)',border:'1px solid var(--border-card)',borderRadius:'var(--radius-md)',boxShadow:'var(--shadow-menu)',padding:'7px 10px',lineHeight:'normal',whiteSpace:'nowrap'}">
69
+ <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px">{{ categories[g.drawn.find(b=>b.key===hover).i] }}</div>
70
+ <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">{{ g.drawn.find(b=>b.key===hover).s.name }} · {{ valueFormat(g.drawn.find(b=>b.key===hover).v) }}</div>
71
+ </div>
72
+ <div style="display:flex;gap:14px;padding-top:6px;line-height:normal">
73
+ <span v-for="(s,i) in series" :key="s.name" style="display:inline-flex;align-items:center;gap:6px">
74
+ <span :style="{width:'8px',height:'8px',borderRadius:'2px',background:s.color||SERIES[i%SERIES.length]}"/>
75
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)">{{ s.name }}</span>
76
+ </span>
77
+ </div>
78
+ </div>
79
+ </template>
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ export interface BarSeries { name: string; values: number[]; color?: string; }
3
+ export interface BarOverlay { name: string; values: number[]; color?: string; }
4
+ /** Vertical bars: grouped, stacked, or diverging (variance ±). overlay adds a line on a right-hand axis (combo). */
5
+ export interface BarChartProps {
6
+ series?: BarSeries[];
7
+ categories?: string[];
8
+ mode?: "grouped" | "stacked" | "stacked100" | "diverging";
9
+ /** Swap the axes — long category names, ranked comparisons */
10
+ orientation?: "vertical" | "horizontal";
11
+ /** Print the value inside each bar (skipped where it wouldn't fit) */
12
+ showValues?: boolean;
13
+ /** Corner radius on the outer end of a bar. Default 2 */
14
+ radius?: number;
15
+ /** Line on its own right axis, e.g. margin % over revenue bars */
16
+ overlay?: BarOverlay;
17
+ width?: number; height?: number;
18
+ axes?: boolean; legend?: boolean; interactive?: boolean;
19
+ yFormat?: (v: number) => string;
20
+ overlayFormat?: (v: number) => string;
21
+ style?: React.CSSProperties;
22
+ }
23
+ export declare function BarChart(props: BarChartProps): JSX.Element;
@@ -0,0 +1,135 @@
1
+ import React from "react";
2
+ import { fmtNum, extent, scaleLin, stack, niceTicks, seriesColor, linePath } from "./chart-geometry.js";
3
+ import { ChartLegend, ChartTooltip, PlotGrid, thinLabels, wrapStyle } from "./ChartKit.jsx";
4
+
5
+ /** Bar with only its outer end rounded — a stack must read as one bar, not stacked pills. */
6
+ function barPath(x, y, w, h, r, side) {
7
+ const rr = Math.max(0, Math.min(r, w / 2, h / 2));
8
+ if (!rr) return `M${x} ${y} h${w} v${h} h${-w} Z`;
9
+ if (side === "top") return `M${x} ${y + h} V${y + rr} a${rr} ${rr} 0 0 1 ${rr} ${-rr} h${w - rr * 2} a${rr} ${rr} 0 0 1 ${rr} ${rr} V${y + h} Z`;
10
+ if (side === "bottom") return `M${x} ${y} V${y + h - rr} a${rr} ${rr} 0 0 0 ${rr} ${rr} h${w - rr * 2} a${rr} ${rr} 0 0 0 ${rr} ${-rr} V${y} Z`;
11
+ if (side === "right") return `M${x} ${y} h${w - rr} a${rr} ${rr} 0 0 1 ${rr} ${rr} v${h - rr * 2} a${rr} ${rr} 0 0 1 ${-rr} ${rr} h${-(w - rr)} Z`;
12
+ if (side === "left") return `M${x + w} ${y} h${-(w - rr)} a${rr} ${rr} 0 0 0 ${-rr} ${rr} v${h - rr * 2} a${rr} ${rr} 0 0 0 ${rr} ${rr} h${w - rr} Z`;
13
+ return `M${x} ${y} h${w} v${h} h${-w} Z`;
14
+ }
15
+
16
+ /**
17
+ * Bar chart. series over shared categories; mode "grouped" | "stacked" | "stacked100"
18
+ * (share of each category) | "diverging" (single series, positive --chart-pos / negative
19
+ * --chart-neg — variance vs. plan). orientation="horizontal" swaps the axes for long
20
+ * category names and ranked comparisons. overlay draws a line on its own secondary axis
21
+ * (vertical only). Inside a stack only the outermost segment is rounded, so joins are flush.
22
+ */
23
+ export function BarChart({ series = [], categories = [], mode = "grouped", orientation = "vertical", overlay, width = 560, height = 240, axes = true, legend = true, interactive = true, showValues = false, radius = 2, yFormat = fmtNum, overlayFormat = (v) => fmtNum(v, { pct: true }), style }) {
24
+ const [hidden, setHidden] = React.useState([]);
25
+ const [hover, setHover] = React.useState(null);
26
+ const horiz = orientation === "horizontal";
27
+ const shown = series.filter((s) => !hidden.includes(s.name));
28
+ const n = categories.length || Math.max(...series.map((s) => s.values.length));
29
+ const pct = mode === "stacked100";
30
+ const stacked = mode === "stacked" || pct;
31
+
32
+ const norm = pct
33
+ ? shown.map((s) => s.values.map((v, i) => {
34
+ const tot = shown.reduce((t, o) => t + (o.values[i] || 0), 0) || 1;
35
+ return ((v || 0) / tot) * 100;
36
+ }))
37
+ : shown.map((s) => s.values);
38
+ const bands = stacked && shown.length ? stack(norm) : null;
39
+ const [lo0, hi0] = pct ? [0, 100] : stacked ? extent([(bands[bands.length - 1] || []).map((b) => b[1])]) : extent(norm);
40
+ const { ticks, lo, hi } = niceTicks(lo0, hi0, 4);
41
+ const labelPad = horiz ? Math.min(140, Math.max(...categories.map((c) => String(c).length)) * 6.4 + 12) : 44;
42
+ const pad = horiz
43
+ ? { l: axes ? labelPad : 4, r: 12, t: 6, b: axes ? 22 : 4 }
44
+ : { l: axes ? 44 : 4, r: overlay ? 40 : 8, t: 10, b: axes ? 24 : 4 };
45
+
46
+ const sv = horiz ? scaleLin(lo, hi, pad.l, width - pad.r) : scaleLin(lo, hi, height - pad.b, pad.t);
47
+ const band = ((horiz ? height - pad.t - pad.b : width - pad.l - pad.r) / n);
48
+ const mid = (i) => (horiz ? pad.t : pad.l) + band * i + band / 2;
49
+ const groupW = Math.min(band * 0.66, mode === "grouped" ? 20 * Math.max(1, shown.length) : 30);
50
+ const bw = mode === "grouped" ? groupW / Math.max(1, shown.length) : groupW;
51
+ const colorOf = (s) => s.color || seriesColor(series.indexOf(s));
52
+ const zero = sv(Math.max(lo, 0));
53
+
54
+ const oScale = overlay && !horiz ? (() => { const [ol, oh] = extent([overlay.values]); const t = niceTicks(ol, oh, 4); return { sy: scaleLin(t.lo, t.hi, height - pad.b, pad.t), ticks: t.ticks }; })() : null;
55
+ const move = (e) => {
56
+ if (!interactive) return;
57
+ const r = e.currentTarget.getBoundingClientRect();
58
+ const off = horiz ? e.clientY - r.top - pad.t : e.clientX - r.left - pad.l;
59
+ const i = Math.floor(off / band);
60
+ setHover(i >= 0 && i < n ? i : null);
61
+ };
62
+ const fmtVal = (s, i) => (pct ? norm[shown.indexOf(s)][i].toFixed(1) + "%" : yFormat(s.values[i]));
63
+
64
+ const rects = [];
65
+ shown.forEach((s, si) => (pct ? norm[si] : s.values).forEach((v, i) => {
66
+ if (v == null) return;
67
+ let fill = mode === "diverging" ? (v >= 0 ? "var(--chart-pos)" : "var(--chart-neg)") : colorOf(s);
68
+ const outer = !shown.slice(si + 1).some((o) => ((pct ? norm[shown.indexOf(o)][i] : o.values[i]) || 0) > 0);
69
+ let x, y, w, h, side;
70
+ if (stacked) {
71
+ const a = sv(bands[si][i][0]), b = sv(bands[si][i][1]);
72
+ if (horiz) { x = Math.min(a, b); w = Math.max(1, Math.abs(b - a)); y = mid(i) - bw / 2; h = bw; side = outer ? "right" : null; }
73
+ else { y = Math.min(a, b); h = Math.max(1, Math.abs(a - b)); x = mid(i) - bw / 2; w = bw; side = outer ? "top" : null; }
74
+ } else if (horiz) {
75
+ const e = sv(v);
76
+ x = Math.min(zero, e); w = Math.max(1, Math.abs(e - zero)); y = mid(i) - groupW / 2 + si * bw + 0.5; h = Math.max(1, bw - 1);
77
+ side = v >= 0 ? "right" : "left";
78
+ } else {
79
+ const e = sv(v);
80
+ y = Math.min(zero, e); h = Math.max(1, Math.abs(e - zero)); x = mid(i) - groupW / 2 + si * bw + 0.5; w = Math.max(1, bw - 1);
81
+ side = v >= 0 ? "top" : "bottom";
82
+ }
83
+ rects.push({ key: s.name + i, d: barPath(x, y, w, h, radius, side), fill, x, y, w, h, v, s, i });
84
+ }));
85
+
86
+ return (
87
+ <div style={{ ...wrapStyle, ...style }}>
88
+ <svg width={width} height={height} onMouseMove={move} onMouseLeave={() => setHover(null)} role="img">
89
+ {axes && !horiz ? <PlotGrid w={width} h={height} pad={pad} ticks={ticks} sy={sv} xLabels={thinLabels(categories)} sxLabel={mid} yFormat={pct ? (v) => v + "%" : yFormat} /> : null}
90
+ {axes && horiz ? (
91
+ <g>
92
+ {ticks.map((t) => (
93
+ <g key={t}>
94
+ <line x1={sv(t)} x2={sv(t)} y1={pad.t} y2={height - pad.b} stroke={t === 0 ? "var(--chart-axis)" : "var(--chart-grid)"} strokeWidth="1" />
95
+ <text x={sv(t)} y={height - pad.b + 15} textAnchor="middle" style={{ fontFamily: "var(--font-mono)", fontSize: 10, fill: "var(--chart-label)" }}>{pct ? t + "%" : yFormat(t)}</text>
96
+ </g>
97
+ ))}
98
+ {categories.map((c, i) => (
99
+ <text key={i} x={pad.l - 8} y={mid(i) + 3.5} textAnchor="end" style={{ fontFamily: "var(--font-sans)", fontSize: 10.5, fill: hover === i ? "var(--text-heading)" : "var(--chart-label)" }}>{c}</text>
100
+ ))}
101
+ </g>
102
+ ) : null}
103
+ {hover != null ? (horiz
104
+ ? <rect x={pad.l} y={pad.t + band * hover} width={width - pad.l - pad.r} height={band} fill="var(--bg-hover)" />
105
+ : <rect x={pad.l + band * hover} y={pad.t} width={band} height={height - pad.t - pad.b} fill="var(--bg-hover)" />) : null}
106
+ {rects.map((r) => <path key={r.key} d={r.d} fill={r.fill} opacity={hover == null || hover === r.i ? 1 : 0.55} style={{ transition: "opacity var(--dur-fast) var(--ease-out)" }} />)}
107
+ {showValues ? rects.map((r) => (
108
+ (horiz ? r.w : r.h) < 22 ? null : (
109
+ <text key={"v" + r.key} x={horiz ? r.x + r.w - 5 : r.x + r.w / 2} y={horiz ? r.y + r.h / 2 + 3.5 : r.y + 12}
110
+ textAnchor={horiz ? "end" : "middle"} style={{ fontFamily: "var(--font-mono)", fontSize: 10, fill: "var(--on-accent)" }}>{fmtVal(r.s, r.i)}</text>
111
+ )
112
+ )) : null}
113
+ {oScale ? <g>
114
+ <path d={linePath(overlay.values.map((v, i) => [mid(i), oScale.sy(v)]))} fill="none" stroke={overlay.color || "var(--chart-total)"} strokeWidth="1.5" />
115
+ {overlay.values.map((v, i) => <circle key={i} cx={mid(i)} cy={oScale.sy(v)} r="2.5" fill={overlay.color || "var(--chart-total)"} />)}
116
+ {axes ? oScale.ticks.map((t) => <text key={t} x={width - pad.r + 6} y={oScale.sy(t) + 3} style={{ fontFamily: "var(--font-mono)", fontSize: 10, fill: "var(--chart-label)" }}>{overlayFormat(t)}</text>) : null}
117
+ </g> : null}
118
+ </svg>
119
+ {hover != null && interactive ? (
120
+ <ChartTooltip
121
+ x={horiz ? Math.min(width - 20, pad.l + (width - pad.l - pad.r) * 0.5) : mid(hover)}
122
+ y={horiz ? pad.t + band * hover + band / 2 : height / 2 - 10}
123
+ flip={horiz ? true : mid(hover) > width * 0.62} title={categories[hover]}
124
+ rows={shown.map((s) => ({ label: s.name, value: fmtVal(s, hover), color: mode === "diverging" ? (s.values[hover] >= 0 ? "var(--chart-pos)" : "var(--chart-neg)") : colorOf(s) }))
125
+ .concat(overlay ? [{ label: overlay.name, value: overlayFormat(overlay.values[hover]), color: overlay.color || "var(--chart-total)" }] : [])} />
126
+ ) : null}
127
+ {legend && (series.length > 1 || overlay) ? (
128
+ <ChartLegend style={{ paddingTop: 8 }}
129
+ items={series.map((s) => ({ label: s.name, color: colorOf(s) })).concat(overlay ? [{ label: overlay.name, color: overlay.color || "var(--chart-total)" }] : [])}
130
+ hidden={hidden}
131
+ onToggle={(l) => series.some((s) => s.name === l) && setHidden(hidden.includes(l) ? hidden.filter((x) => x !== l) : hidden.length + 1 < series.length ? [...hidden, l] : hidden)} />
132
+ ) : null}
133
+ </div>
134
+ );
135
+ }
@@ -0,0 +1,139 @@
1
+ <script setup>
2
+ import { ref, computed } from 'vue'
3
+ import { fmtNum, extent, scaleLin, stack, niceTicks, SERIES, linePath } from './chart-geometry.js'
4
+ /**
5
+ * Bar chart. mode "grouped" | "stacked" | "stacked100" | "diverging";
6
+ * orientation="horizontal" swaps the axes. Only the outermost stack segment is rounded,
7
+ * so colour joins inside a stack stay flush. overlay = line on a secondary axis (vertical).
8
+ */
9
+ const props = defineProps({ series:{type:Array,default:()=>[]}, categories:{type:Array,default:()=>[]}, mode:{type:String,default:'grouped'}, orientation:{type:String,default:'vertical'}, overlay:Object, width:{type:Number,default:560}, height:{type:Number,default:240}, axes:{type:Boolean,default:true}, legend:{type:Boolean,default:true}, interactive:{type:Boolean,default:true}, showValues:Boolean, radius:{type:Number,default:2}, yFormat:{type:Function,default:fmtNum}, overlayFormat:{type:Function,default:(v)=>fmtNum(v,{pct:true})}, style:Object })
10
+ const hidden = ref([]); const hover = ref(null)
11
+ function barPath(x,y,w,h,r,side){
12
+ const rr = Math.max(0, Math.min(r, w/2, h/2))
13
+ if (!rr) return `M${x} ${y} h${w} v${h} h${-w} Z`
14
+ if (side==='top') return `M${x} ${y+h} V${y+rr} a${rr} ${rr} 0 0 1 ${rr} ${-rr} h${w-rr*2} a${rr} ${rr} 0 0 1 ${rr} ${rr} V${y+h} Z`
15
+ if (side==='bottom') return `M${x} ${y} V${y+h-rr} a${rr} ${rr} 0 0 0 ${rr} ${rr} h${w-rr*2} a${rr} ${rr} 0 0 0 ${rr} ${-rr} V${y} Z`
16
+ if (side==='right') return `M${x} ${y} h${w-rr} a${rr} ${rr} 0 0 1 ${rr} ${rr} v${h-rr*2} a${rr} ${rr} 0 0 1 ${-rr} ${rr} h${-(w-rr)} Z`
17
+ if (side==='left') return `M${x+w} ${y} h${-(w-rr)} a${rr} ${rr} 0 0 0 ${-rr} ${rr} v${h-rr*2} a${rr} ${rr} 0 0 0 ${rr} ${rr} h${w-rr} Z`
18
+ return `M${x} ${y} h${w} v${h} h${-w} Z`
19
+ }
20
+ const g = computed(() => {
21
+ const horiz = props.orientation === 'horizontal'
22
+ const shown = props.series.filter(s=>!hidden.value.includes(s.name))
23
+ const n = props.categories.length || Math.max(...props.series.map(s=>s.values.length))
24
+ const pct = props.mode === 'stacked100'
25
+ const stacked = props.mode === 'stacked' || pct
26
+ const norm = pct
27
+ ? shown.map(s=>s.values.map((v,i)=>{ const tot = shown.reduce((t,o)=>t+(o.values[i]||0),0)||1; return ((v||0)/tot)*100 }))
28
+ : shown.map(s=>s.values)
29
+ const bands = stacked && shown.length ? stack(norm) : null
30
+ const ext = pct ? [0,100] : (stacked ? extent([(bands[bands.length-1]||[]).map(b=>b[1])]) : extent(norm))
31
+ const { ticks, lo, hi } = niceTicks(ext[0], ext[1], 4)
32
+ const labelPad = horiz ? Math.min(140, Math.max(...props.categories.map(c=>String(c).length))*6.4+12) : 44
33
+ const pad = horiz ? { l: props.axes?labelPad:4, r:12, t:6, b: props.axes?22:4 }
34
+ : { l: props.axes?44:4, r: props.overlay?40:8, t:10, b: props.axes?24:4 }
35
+ const sv = horiz ? scaleLin(lo,hi,pad.l,props.width-pad.r) : scaleLin(lo,hi,props.height-pad.b,pad.t)
36
+ const band = (horiz ? props.height-pad.t-pad.b : props.width-pad.l-pad.r)/n
37
+ const mid = i => (horiz?pad.t:pad.l) + band*i + band/2
38
+ const groupW = Math.min(band*0.66, props.mode==='grouped' ? 20*Math.max(1,shown.length) : 30)
39
+ const bw = props.mode==='grouped' ? groupW/Math.max(1,shown.length) : groupW
40
+ const colorOf = s => s.color || SERIES[props.series.indexOf(s)%SERIES.length]
41
+ const zero = sv(Math.max(lo,0))
42
+ const rects = []
43
+ shown.forEach((s,si)=>(pct?norm[si]:s.values).forEach((v,i)=>{
44
+ if (v==null) return
45
+ const fill = props.mode==='diverging' ? (v>=0?'var(--chart-pos)':'var(--chart-neg)') : colorOf(s)
46
+ const outer = !shown.slice(si+1).some(o=>((pct?norm[shown.indexOf(o)][i]:o.values[i])||0)>0)
47
+ let x,y,w,h,side
48
+ if (stacked) {
49
+ const a = sv(bands[si][i][0]), b = sv(bands[si][i][1])
50
+ if (horiz) { x=Math.min(a,b); w=Math.max(1,Math.abs(b-a)); y=mid(i)-bw/2; h=bw; side=outer?'right':null }
51
+ else { y=Math.min(a,b); h=Math.max(1,Math.abs(a-b)); x=mid(i)-bw/2; w=bw; side=outer?'top':null }
52
+ } else if (horiz) {
53
+ const e = sv(v); x=Math.min(zero,e); w=Math.max(1,Math.abs(e-zero)); y=mid(i)-groupW/2+si*bw+0.5; h=Math.max(1,bw-1); side=v>=0?'right':'left'
54
+ } else {
55
+ const e = sv(v); y=Math.min(zero,e); h=Math.max(1,Math.abs(e-zero)); x=mid(i)-groupW/2+si*bw+0.5; w=Math.max(1,bw-1); side=v>=0?'top':'bottom'
56
+ }
57
+ rects.push({ key:s.name+i, d:barPath(x,y,w,h,props.radius,side), fill, x, y, w, h, s, i,
58
+ text: pct ? norm[si][i].toFixed(1)+'%' : props.yFormat(s.values[i]) })
59
+ }))
60
+ let ov = null
61
+ if (props.overlay && !horiz) {
62
+ const e2 = extent([props.overlay.values]); const t = niceTicks(e2[0],e2[1],4)
63
+ const osy = scaleLin(t.lo,t.hi,props.height-pad.b,pad.t)
64
+ ov = { path: linePath(props.overlay.values.map((v,i)=>[mid(i),osy(v)])), dots: props.overlay.values.map((v,i)=>[mid(i),osy(v)]), ticks:t.ticks, osy, color: props.overlay.color||'var(--chart-total)' }
65
+ }
66
+ return { horiz, shown, pct, stacked, ticks, sv, band, mid, pad, rects, ov, n, colorOf,
67
+ fmtAxis: pct ? (v=>v+'%') : props.yFormat }
68
+ })
69
+ function move(e){
70
+ if(!props.interactive) return
71
+ const r = e.currentTarget.getBoundingClientRect()
72
+ const off = g.value.horiz ? e.clientY-r.top-g.value.pad.t : e.clientX-r.left-g.value.pad.l
73
+ const i = Math.floor(off/g.value.band)
74
+ hover.value = i>=0 && i<g.value.n ? i : null
75
+ }
76
+ function toggle(name){
77
+ if (hidden.value.includes(name)) hidden.value = hidden.value.filter(x=>x!==name)
78
+ else if (hidden.value.length+1 < props.series.length) hidden.value = [...hidden.value, name]
79
+ }
80
+ function rowVal(s){
81
+ const i = hover.value
82
+ return g.value.pct ? (g.value.rects.find(r=>r.s===s && r.i===i)||{}).text : props.yFormat(s.values[i])
83
+ }
84
+ </script>
85
+ <template>
86
+ <div :style="{position:'relative',display:'inline-block',lineHeight:0,...style}">
87
+ <svg :width="width" :height="height" @mousemove="move" @mouseleave="hover=null" role="img">
88
+ <template v-if="axes">
89
+ <g v-for="t in g.ticks" :key="t">
90
+ <line v-if="g.horiz" :x1="g.sv(t)" :x2="g.sv(t)" :y1="g.pad.t" :y2="height-g.pad.b" :stroke="t===0?'var(--chart-axis)':'var(--chart-grid)'" stroke-width="1"/>
91
+ <line v-else :x1="g.pad.l" :x2="width-g.pad.r" :y1="g.sv(t)" :y2="g.sv(t)" :stroke="t===0?'var(--chart-axis)':'var(--chart-grid)'" stroke-width="1"/>
92
+ <text v-if="g.horiz" :x="g.sv(t)" :y="height-g.pad.b+15" text-anchor="middle" style="font-family:var(--font-mono);font-size:10px;fill:var(--chart-label)">{{ g.fmtAxis(t) }}</text>
93
+ <text v-else :x="g.pad.l-8" :y="g.sv(t)+3" text-anchor="end" style="font-family:var(--font-mono);font-size:10px;fill:var(--chart-label)">{{ g.fmtAxis(t) }}</text>
94
+ </g>
95
+ <text v-for="(c,i) in categories" :key="'c'+i"
96
+ :x="g.horiz ? g.pad.l-8 : g.mid(i)" :y="g.horiz ? g.mid(i)+3.5 : height-g.pad.b+16"
97
+ :text-anchor="g.horiz ? 'end' : 'middle'"
98
+ :style="{fontFamily:'var(--font-sans)',fontSize:g.horiz?'10.5px':'10px',fill:hover===i?'var(--text-heading)':'var(--chart-label)'}">{{ c }}</text>
99
+ </template>
100
+ <rect v-if="hover!=null && g.horiz" :x="g.pad.l" :y="g.pad.t+g.band*hover" :width="width-g.pad.l-g.pad.r" :height="g.band" fill="var(--bg-hover)"/>
101
+ <rect v-else-if="hover!=null" :x="g.pad.l+g.band*hover" :y="g.pad.t" :width="g.band" :height="height-g.pad.t-g.pad.b" fill="var(--bg-hover)"/>
102
+ <path v-for="r in g.rects" :key="r.key" :d="r.d" :fill="r.fill" :opacity="hover==null||hover===r.i?1:0.55" style="transition:opacity var(--dur-fast) var(--ease-out)"/>
103
+ <template v-if="showValues">
104
+ <text v-for="r in g.rects.filter(r=>(g.horiz?r.w:r.h)>=22)" :key="'v'+r.key"
105
+ :x="g.horiz ? r.x+r.w-5 : r.x+r.w/2" :y="g.horiz ? r.y+r.h/2+3.5 : r.y+12"
106
+ :text-anchor="g.horiz ? 'end' : 'middle'" style="font-family:var(--font-mono);font-size:10px;fill:var(--on-accent)">{{ r.text }}</text>
107
+ </template>
108
+ <template v-if="g.ov">
109
+ <path :d="g.ov.path" fill="none" :stroke="g.ov.color" stroke-width="1.5"/>
110
+ <circle v-for="(d,i) in g.ov.dots" :key="'o'+i" :cx="d[0]" :cy="d[1]" r="2.5" :fill="g.ov.color"/>
111
+ <text v-if="axes" v-for="t in g.ov.ticks" :key="'ot'+t" :x="width-g.pad.r+6" :y="g.ov.osy(t)+3" style="font-family:var(--font-mono);font-size:10px;fill:var(--chart-label)">{{ overlayFormat(t) }}</text>
112
+ </template>
113
+ </svg>
114
+ <div v-if="hover!=null && interactive" :style="{position:'absolute',left:(g.horiz ? Math.min(width-20, g.pad.l+(width-g.pad.l-g.pad.r)*0.5) : g.mid(hover))+'px',top:(g.horiz ? g.pad.t+g.band*hover+g.band/2 : height/2-10)+'px',transform:(g.horiz || g.mid(hover)>width*0.62)?'translate(calc(-100% - 10px),-50%)':'translate(10px,-50%)',pointerEvents:'none',zIndex:5,background:'var(--bg-surface)',border:'1px solid var(--border-card)',borderRadius:'var(--radius-md)',boxShadow:'var(--shadow-menu)',padding:'7px 10px',lineHeight:'normal',whiteSpace:'nowrap'}">
115
+ <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:4px">{{ categories[hover] }}</div>
116
+ <div v-for="s in g.shown" :key="s.name" style="display:flex;align-items:center;gap:6px;padding:1px 0">
117
+ <span :style="{width:'7px',height:'7px',borderRadius:'2px',background:mode==='diverging'?(s.values[hover]>=0?'var(--chart-pos)':'var(--chart-neg)'):g.colorOf(s),flexShrink:0}"/>
118
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)">{{ s.name }}</span>
119
+ <span style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading);margin-left:auto;padding-left:10px">{{ rowVal(s) }}</span>
120
+ </div>
121
+ <div v-if="overlay" style="display:flex;align-items:center;gap:6px;padding:1px 0">
122
+ <span :style="{width:'7px',height:'7px',borderRadius:'2px',background:overlay.color||'var(--chart-total)'}"/>
123
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)">{{ overlay.name }}</span>
124
+ <span style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading);margin-left:auto;padding-left:10px">{{ overlayFormat(overlay.values[hover]) }}</span>
125
+ </div>
126
+ </div>
127
+ <div v-if="legend && (series.length>1 || overlay)" style="display:flex;flex-wrap:wrap;gap:4px 14px;padding-top:8px;line-height:normal">
128
+ <span v-for="(s,i) in series" :key="s.name" role="button" tabindex="0" @click="toggle(s.name)"
129
+ :style="{display:'inline-flex',alignItems:'center',gap:'6px',cursor:'pointer',opacity:hidden.includes(s.name)?0.4:1}">
130
+ <span :style="{width:'8px',height:'8px',borderRadius:'2px',background:s.color||SERIES[i%SERIES.length]}"/>
131
+ <span :style="{fontFamily:'var(--font-sans)',fontSize:'var(--text-xs)',color:'var(--text-secondary)',textDecoration:hidden.includes(s.name)?'line-through':'none'}">{{ s.name }}</span>
132
+ </span>
133
+ <span v-if="overlay" style="display:inline-flex;align-items:center;gap:6px">
134
+ <span :style="{width:'8px',height:'8px',borderRadius:'2px',background:overlay.color||'var(--chart-total)'}"/>
135
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)">{{ overlay.name }}</span>
136
+ </span>
137
+ </div>
138
+ </div>
139
+ </template>
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ export interface BarListRow { label: string; value: number; color?: string; /** Signed % shown at the right */ delta?: number; }
3
+ /** Horizontal labeled bars — top-N rankings, budget consumption. */
4
+ export interface BarListProps {
5
+ rows?: BarListRow[];
6
+ /** Scale cap; defaults to the largest row */ max?: number;
7
+ width?: number; barHeight?: number; showTrack?: boolean;
8
+ valueFormat?: (v: number) => string;
9
+ interactive?: boolean;
10
+ onSelect?: (row: BarListRow) => void;
11
+ style?: React.CSSProperties;
12
+ }
13
+ export declare function BarList(props: BarListProps): JSX.Element;
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import { fmtNum } from "./chart-geometry.js";
3
+
4
+ /**
5
+ * Horizontal labeled bars — top-N rankings, budget consumption, category comparison.
6
+ * rows: [{label, value, color?, delta?}] — delta renders a signed % at the right.
7
+ */
8
+ export function BarList({ rows = [], max, width = 360, valueFormat = fmtNum, showTrack = true, barHeight = 8, interactive = true, onSelect, style }) {
9
+ const [hover, setHover] = React.useState(null);
10
+ const top = max != null ? max : Math.max(1, ...rows.map((r) => r.value));
11
+ const total = rows.reduce((s, r) => s + r.value, 0) || 1;
12
+ return (
13
+ <div style={{ display: "flex", flexDirection: "column", gap: 10, width, ...style }}>
14
+ {rows.map((r) => (
15
+ <div key={r.label} onMouseEnter={interactive ? () => setHover(r.label) : undefined} onMouseLeave={interactive ? () => setHover(null) : undefined}
16
+ onClick={onSelect ? () => onSelect(r) : undefined}
17
+ style={{ opacity: hover == null || hover === r.label ? 1 : 0.5, cursor: onSelect ? "pointer" : "default", transition: "var(--t-hover)" }}>
18
+ <div style={{ display: "flex", alignItems: "baseline", gap: 8, paddingBottom: 4 }}>
19
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-body)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.label}</span>
20
+ <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-heading)" }}>{valueFormat(r.value)}</span>
21
+ {hover === r.label && r.delta == null ? <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--text-muted)", paddingLeft: 6 }}>{((r.value / total) * 100).toFixed(1)}%</span> : null}
22
+ {r.delta != null ? <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: r.delta >= 0 ? "var(--chart-pos)" : "var(--chart-neg)", minWidth: 38, textAlign: "right" }}>{(r.delta >= 0 ? "+" : "") + r.delta.toFixed(1)}%</span> : null}
23
+ </div>
24
+ <div style={{ height: barHeight, borderRadius: "var(--radius-full)", background: showTrack ? "var(--chart-track)" : "transparent", overflow: "hidden" }}>
25
+ <div style={{ width: Math.max(1, (r.value / top) * 100) + "%", height: "100%", borderRadius: "var(--radius-full)", background: r.color || "var(--series-1)", filter: hover === r.label ? "brightness(1.08)" : "none", transition: "width var(--dur) var(--ease-out), filter var(--dur-fast) var(--ease-out)" }} />
26
+ </div>
27
+ </div>
28
+ ))}
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1,25 @@
1
+ <script setup>
2
+ import { computed, ref } from 'vue'
3
+ import { fmtNum } from './chart-geometry.js'
4
+ // Horizontal labeled bars — top-N rankings, budget consumption.
5
+ const props = defineProps({ rows:{type:Array,default:()=>[]}, max:Number, width:{type:Number,default:360}, barHeight:{type:Number,default:8}, showTrack:{type:Boolean,default:true}, valueFormat:{type:Function,default:fmtNum}, interactive:{type:Boolean,default:true}, style:Object })
6
+ const top = computed(() => props.max!=null ? props.max : Math.max(1,...props.rows.map(r=>r.value)))
7
+ const total = computed(() => props.rows.reduce((s,r)=>s+r.value,0)||1)
8
+ const hover = ref(null)
9
+ </script>
10
+ <template>
11
+ <div :style="{display:'flex',flexDirection:'column',gap:'10px',width:width+'px',...style}">
12
+ <div v-for="r in rows" :key="r.label" @mouseenter="interactive && (hover=r.label)" @mouseleave="interactive && (hover=null)"
13
+ :style="{opacity:hover==null||hover===r.label?1:0.5,transition:'var(--t-hover)'}">
14
+ <div style="display:flex;align-items:baseline;gap:8px;padding-bottom:4px">
15
+ <span style="font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-body);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ r.label }}</span>
16
+ <span style="margin-left:auto;font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">{{ valueFormat(r.value) }}</span>
17
+ <span v-if="hover===r.label && r.delta==null" style="font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--text-muted);padding-left:6px">{{ ((r.value/total)*100).toFixed(1) }}%</span>
18
+ <span v-if="r.delta!=null" :style="{fontFamily:'var(--font-mono)',fontSize:'var(--text-2xs)',color:r.delta>=0?'var(--chart-pos)':'var(--chart-neg)',minWidth:'38px',textAlign:'right'}">{{ (r.delta>=0?'+':'')+r.delta.toFixed(1) }}%</span>
19
+ </div>
20
+ <div :style="{height:barHeight+'px',borderRadius:'var(--radius-full)',background:showTrack?'var(--chart-track)':'transparent',overflow:'hidden'}">
21
+ <div :style="{width:Math.max(1,(r.value/top)*100)+'%',height:'100%',borderRadius:'var(--radius-full)',background:r.color||'var(--series-1)',filter:hover===r.label?'brightness(1.08)':'none',transition:'width var(--dur) var(--ease-out), filter var(--dur-fast) var(--ease-out)'}"/>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </template>
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ export interface BoxGroup {
3
+ label: string;
4
+ /** Raw values — summarized for you */ values?: number[];
5
+ q1?: number; med?: number; q3?: number; lo?: number; hi?: number; outliers?: number[];
6
+ color?: string;
7
+ }
8
+ /** Box & whisker with Tukey-fence outliers. */
9
+ export interface BoxPlotProps {
10
+ groups?: BoxGroup[];
11
+ width?: number; height?: number;
12
+ axes?: boolean; interactive?: boolean;
13
+ valueFormat?: (v: number) => string;
14
+ style?: React.CSSProperties;
15
+ }
16
+ export declare function BoxPlot(props: BoxPlotProps): JSX.Element;
17
+ /** Five-number summary helper (q1, med, q3, fences, outliers). */
18
+ export declare function summarize(values: number[]): { q1: number; med: number; q3: number; lo: number; hi: number; outliers: number[] };