@keyfunc/keybricks-ds 1.0.4 → 2.3.1

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 (381) hide show
  1. package/DEPLOY.md +14 -5
  2. package/DESIGN.md +245 -59
  3. package/INTEGRATION.md +243 -49
  4. package/SKILL.md +13 -11
  5. package/_adherence.oxlintrc.json +183 -70
  6. package/_scripts/build-index.mjs +24 -4
  7. package/_scripts/build-tokens.mjs +5 -2
  8. package/assets/icons/icon-index.js +1 -1
  9. package/assets/icons/icon-index.json +1 -1
  10. package/components/_cards/accordion.card.html +2 -2
  11. package/components/_cards/action-chip.card.html +11 -9
  12. package/components/_cards/activity.card.html +3 -3
  13. package/components/_cards/alert.card.html +3 -3
  14. package/components/_cards/app-chrome.card.html +4 -4
  15. package/components/_cards/banner.card.html +1 -1
  16. package/components/_cards/breadcrumb.card.html +5 -5
  17. package/components/_cards/button-1-base.card.html +23 -26
  18. package/components/_cards/button-2-states.card.html +27 -25
  19. package/components/_cards/button-3-sizes-icons.card.html +20 -19
  20. package/components/_cards/button-4-group.card.html +14 -31
  21. package/components/_cards/canvas-zoom-controls.card.html +1 -1
  22. package/components/_cards/cards-indicators.card.html +2 -2
  23. package/components/_cards/charts-1-sparkline.card.html +5 -5
  24. package/components/_cards/charts-10-gauge.card.html +5 -5
  25. package/components/_cards/charts-11-funnel.card.html +3 -3
  26. package/components/_cards/charts-12-treemap.card.html +3 -3
  27. package/components/_cards/charts-14-radar.card.html +3 -3
  28. package/components/_cards/charts-15-distribution.card.html +2 -2
  29. package/components/_cards/charts-16-pareto.card.html +3 -3
  30. package/components/_cards/charts-17-candlestick.card.html +3 -3
  31. package/components/_cards/charts-18-sankey.card.html +3 -3
  32. package/components/_cards/charts-19-chord.card.html +3 -3
  33. package/components/_cards/charts-2-line.card.html +4 -4
  34. package/components/_cards/charts-20-sunburst.card.html +3 -3
  35. package/components/_cards/charts-21-worldmap.card.html +4 -4
  36. package/components/_cards/charts-22-gantt.card.html +3 -3
  37. package/components/_cards/charts-23-bar3d.card.html +3 -3
  38. package/components/_cards/charts-24-pie3d.card.html +3 -3
  39. package/components/_cards/charts-25-surface3d.card.html +3 -3
  40. package/components/_cards/charts-26-scatter3d.card.html +3 -3
  41. package/components/_cards/charts-27-ribbon3d.card.html +3 -3
  42. package/components/_cards/charts-3-bar.card.html +7 -7
  43. package/components/_cards/charts-4-barlist.card.html +3 -3
  44. package/components/_cards/charts-5-donut.card.html +4 -4
  45. package/components/_cards/charts-6-scatter.card.html +3 -3
  46. package/components/_cards/charts-7-heatmap.card.html +3 -3
  47. package/components/_cards/charts-8-waterfall.card.html +3 -3
  48. package/components/_cards/charts-9-bullet.card.html +3 -3
  49. package/components/_cards/charts-99-fullpage.card.html +5 -5
  50. package/components/_cards/checkbox.card.html +9 -6
  51. package/components/_cards/codeblock.card.html +2 -2
  52. package/components/_cards/combobox.card.html +6 -3
  53. package/components/_cards/context-menu.card.html +6 -3
  54. package/components/_cards/date-picker.card.html +10 -4
  55. package/components/_cards/diffvalue.card.html +3 -3
  56. package/components/_cards/divider.card.html +4 -4
  57. package/components/_cards/drawer.card.html +4 -4
  58. package/components/_cards/dropdown-menu.card.html +7 -7
  59. package/components/_cards/dropzone.card.html +5 -3
  60. package/components/_cards/empty-state.card.html +6 -6
  61. package/components/_cards/filter-bar.card.html +5 -5
  62. package/components/_cards/icon-button.card.html +14 -14
  63. package/components/_cards/icons-1-core.card.html +4 -4
  64. package/components/_cards/icons-2-feather.card.html +4 -4
  65. package/components/_cards/icons-3-unicons.card.html +4 -4
  66. package/components/_cards/input.card.html +5 -2
  67. package/components/_cards/kbd.card.html +2 -2
  68. package/components/_cards/keyvalue.card.html +2 -2
  69. package/components/_cards/listrow.card.html +2 -2
  70. package/components/_cards/loading.card.html +3 -3
  71. package/components/_cards/log-viewer.card.html +3 -3
  72. package/components/_cards/modal.card.html +9 -9
  73. package/components/_cards/multi-select.card.html +6 -3
  74. package/components/_cards/number-stepper.card.html +9 -5
  75. package/components/_cards/pagination.card.html +6 -3
  76. package/components/_cards/people.card.html +4 -4
  77. package/components/_cards/pipeline-node.card.html +8 -8
  78. package/components/_cards/pipeline-palette.card.html +5 -5
  79. package/components/_cards/radio.card.html +12 -7
  80. package/components/_cards/search.card.html +6 -3
  81. package/components/_cards/segmented-control.card.html +11 -10
  82. package/components/_cards/select.card.html +8 -4
  83. package/components/_cards/slider.card.html +2 -2
  84. package/components/_cards/smart-table.card.html +3 -3
  85. package/components/_cards/spreadsheet.card.html +3 -3
  86. package/components/_cards/stepper.card.html +3 -3
  87. package/components/_cards/table.card.html +165 -0
  88. package/components/_cards/tabs.card.html +6 -3
  89. package/components/_cards/tag.card.html +6 -3
  90. package/components/_cards/textarea.card.html +2 -2
  91. package/components/_cards/timeline.card.html +3 -3
  92. package/components/_cards/toast.card.html +10 -8
  93. package/components/_cards/toggles.card.html +15 -10
  94. package/components/_cards/tooltip.card.html +7 -7
  95. package/components/_cards/tree.card.html +3 -3
  96. package/components/_shared/card-kit.css +25 -0
  97. package/components/_shared/card-kit.js +50 -0
  98. package/components/_shared/src-buttons.js +1 -1
  99. package/components/_shared/src-charts.js +1 -1
  100. package/components/_shared/src-data.js +1 -1
  101. package/components/_shared/src-display.js +1 -1
  102. package/components/_shared/src-feedback.js +1 -1
  103. package/components/_shared/src-forms.js +1 -1
  104. package/components/_shared/src-icons.js +1 -1
  105. package/components/_shared/src-navigation.js +1 -1
  106. package/components/_shared/src-pipeline.js +1 -1
  107. package/components/_shared/src-tabs.js +1 -1
  108. package/components/_shared/view-source.js +3 -3
  109. package/components/_vue/index.mjs +16 -5
  110. package/components/buttons/ActionChip.d.ts +3 -1
  111. package/components/buttons/ActionChip.jsx +4 -4
  112. package/components/buttons/ActionChip.vue +4 -4
  113. package/components/buttons/Button.d.ts +5 -2
  114. package/components/buttons/Button.jsx +15 -18
  115. package/components/buttons/Button.vue +17 -18
  116. package/components/buttons/ButtonGroup.vue +1 -1
  117. package/components/buttons/IconButton.d.ts +2 -1
  118. package/components/buttons/IconButton.jsx +13 -9
  119. package/components/buttons/IconButton.vue +12 -9
  120. package/components/buttons/buttons.prompt.md +6 -5
  121. package/components/charts/Bar3D.jsx +2 -2
  122. package/components/charts/Bar3D.vue +3 -3
  123. package/components/charts/BarChart.jsx +4 -4
  124. package/components/charts/BarChart.vue +6 -6
  125. package/components/charts/BarList.jsx +4 -4
  126. package/components/charts/BarList.vue +4 -4
  127. package/components/charts/BoxPlot.jsx +1 -1
  128. package/components/charts/BoxPlot.vue +3 -3
  129. package/components/charts/BulletChart.jsx +6 -6
  130. package/components/charts/BulletChart.vue +6 -6
  131. package/components/charts/CandlestickChart.jsx +2 -2
  132. package/components/charts/CandlestickChart.vue +5 -5
  133. package/components/charts/ChartKit.jsx +8 -7
  134. package/components/charts/ChartLegend.vue +16 -0
  135. package/components/charts/ChartTooltip.vue +16 -0
  136. package/components/charts/ChordChart.jsx +2 -2
  137. package/components/charts/ChordChart.vue +3 -3
  138. package/components/charts/DonutChart.jsx +1 -1
  139. package/components/charts/DonutChart.vue +2 -2
  140. package/components/charts/FunnelChart.jsx +3 -3
  141. package/components/charts/FunnelChart.vue +4 -4
  142. package/components/charts/GanttChart.jsx +2 -2
  143. package/components/charts/GanttChart.vue +3 -3
  144. package/components/charts/GaugeChart.jsx +2 -2
  145. package/components/charts/GaugeChart.vue +2 -2
  146. package/components/charts/HeatmapChart.jsx +2 -2
  147. package/components/charts/HeatmapChart.vue +2 -2
  148. package/components/charts/Histogram.jsx +2 -2
  149. package/components/charts/Histogram.vue +4 -4
  150. package/components/charts/LineChart.jsx +1 -1
  151. package/components/charts/LineChart.vue +4 -4
  152. package/components/charts/ParetoChart.jsx +3 -3
  153. package/components/charts/ParetoChart.vue +5 -5
  154. package/components/charts/Pie3D.jsx +1 -1
  155. package/components/charts/Pie3D.vue +2 -2
  156. package/components/charts/PlotGrid.vue +18 -0
  157. package/components/charts/RadarChart.jsx +1 -1
  158. package/components/charts/RadarChart.vue +2 -2
  159. package/components/charts/Ribbon3D.jsx +2 -2
  160. package/components/charts/Ribbon3D.vue +3 -3
  161. package/components/charts/SankeyChart.jsx +2 -2
  162. package/components/charts/SankeyChart.vue +3 -3
  163. package/components/charts/Scatter3D.jsx +1 -1
  164. package/components/charts/Scatter3D.vue +2 -2
  165. package/components/charts/ScatterChart.jsx +3 -3
  166. package/components/charts/ScatterChart.vue +5 -5
  167. package/components/charts/Sparkline.jsx +1 -1
  168. package/components/charts/Sparkline.vue +1 -1
  169. package/components/charts/SunburstChart.jsx +1 -1
  170. package/components/charts/SunburstChart.vue +2 -2
  171. package/components/charts/Surface3D.jsx +2 -2
  172. package/components/charts/Surface3D.vue +3 -3
  173. package/components/charts/TreemapChart.jsx +2 -2
  174. package/components/charts/TreemapChart.vue +3 -3
  175. package/components/charts/WaterfallChart.jsx +2 -2
  176. package/components/charts/WaterfallChart.vue +6 -5
  177. package/components/charts/WorldMap.jsx +4 -4
  178. package/components/charts/WorldMap.vue +5 -5
  179. package/components/charts/XLabels.vue +11 -0
  180. package/components/data/CodeBlock.jsx +1 -1
  181. package/components/data/CodeBlock.vue +1 -1
  182. package/components/data/DiffValue.d.ts +1 -0
  183. package/components/data/DiffValue.jsx +1 -1
  184. package/components/data/DiffValue.vue +1 -1
  185. package/components/data/FilterBar.d.ts +2 -0
  186. package/components/data/FilterBar.jsx +10 -9
  187. package/components/data/FilterBar.vue +9 -8
  188. package/components/data/KeyValue.d.ts +1 -1
  189. package/components/data/KeyValue.jsx +4 -4
  190. package/components/data/KeyValue.vue +4 -4
  191. package/components/data/ListRow.jsx +2 -2
  192. package/components/data/ListRow.vue +2 -2
  193. package/components/data/Pagination.d.ts +5 -1
  194. package/components/data/Pagination.jsx +16 -12
  195. package/components/data/Pagination.vue +14 -9
  196. package/components/data/SmartTable.d.ts +4 -1
  197. package/components/data/SmartTable.jsx +16 -14
  198. package/components/data/SmartTable.vue +26 -23
  199. package/components/data/Spreadsheet.d.ts +2 -0
  200. package/components/data/Spreadsheet.jsx +16 -15
  201. package/components/data/Spreadsheet.vue +19 -18
  202. package/components/data/Table.d.ts +101 -0
  203. package/components/data/Table.jsx +223 -0
  204. package/components/data/Table.vue +153 -0
  205. package/components/data/data.prompt.md +3 -3
  206. package/components/display/Avatar.jsx +2 -2
  207. package/components/display/Avatar.vue +3 -2
  208. package/components/display/AvatarGroup.vue +3 -2
  209. package/components/display/Badge.d.ts +4 -1
  210. package/components/display/Badge.jsx +10 -5
  211. package/components/display/Badge.vue +1 -1
  212. package/components/display/Divider.vue +3 -3
  213. package/components/display/Donut.vue +3 -2
  214. package/components/display/IconTile.vue +10 -4
  215. package/components/display/Kbd.d.ts +1 -1
  216. package/components/display/Kbd.jsx +1 -1
  217. package/components/display/Kbd.vue +2 -2
  218. package/components/display/ProgressBar.jsx +1 -1
  219. package/components/display/ProgressBar.vue +1 -1
  220. package/components/display/StatCard.vue +1 -1
  221. package/components/display/Tag.d.ts +3 -1
  222. package/components/display/Tag.jsx +4 -4
  223. package/components/display/Tag.vue +4 -4
  224. package/components/feedback/Accordion.jsx +2 -2
  225. package/components/feedback/Accordion.vue +2 -2
  226. package/components/feedback/ActivityItem.d.ts +3 -1
  227. package/components/feedback/ActivityItem.jsx +6 -6
  228. package/components/feedback/ActivityItem.vue +5 -4
  229. package/components/feedback/Alert.jsx +4 -4
  230. package/components/feedback/Alert.vue +3 -3
  231. package/components/feedback/Banner.jsx +5 -5
  232. package/components/feedback/Banner.vue +5 -5
  233. package/components/feedback/ContextMenu.d.ts +3 -1
  234. package/components/feedback/ContextMenu.jsx +5 -5
  235. package/components/feedback/ContextMenu.vue +5 -5
  236. package/components/feedback/DotActivity.vue +3 -3
  237. package/components/feedback/Drawer.d.ts +2 -0
  238. package/components/feedback/Drawer.jsx +6 -6
  239. package/components/feedback/Drawer.vue +22 -15
  240. package/components/feedback/EmptyState.vue +2 -2
  241. package/components/feedback/LogViewer.vue +1 -1
  242. package/components/feedback/Modal.d.ts +2 -0
  243. package/components/feedback/Modal.jsx +12 -11
  244. package/components/feedback/Modal.vue +10 -8
  245. package/components/feedback/Skeleton.vue +2 -1
  246. package/components/feedback/Spinner.vue +2 -1
  247. package/components/feedback/Stepper.vue +1 -1
  248. package/components/feedback/Timeline.jsx +1 -1
  249. package/components/feedback/Timeline.vue +3 -3
  250. package/components/feedback/Toast.vue +4 -4
  251. package/components/feedback/ToastStack.vue +11 -0
  252. package/components/feedback/Tooltip.vue +19 -0
  253. package/components/feedback/feedback.prompt.md +4 -4
  254. package/components/forms/Checkbox.d.ts +3 -2
  255. package/components/forms/Checkbox.jsx +7 -7
  256. package/components/forms/Checkbox.vue +8 -8
  257. package/components/forms/Combobox.d.ts +2 -0
  258. package/components/forms/Combobox.jsx +12 -10
  259. package/components/forms/Combobox.vue +11 -10
  260. package/components/forms/DatePicker.d.ts +4 -0
  261. package/components/forms/DatePicker.jsx +16 -12
  262. package/components/forms/DatePicker.vue +15 -11
  263. package/components/forms/Dropzone.jsx +2 -2
  264. package/components/forms/Dropzone.vue +5 -5
  265. package/components/forms/Field.d.ts +5 -1
  266. package/components/forms/Field.jsx +4 -4
  267. package/components/forms/Field.vue +4 -4
  268. package/components/forms/Input.d.ts +5 -1
  269. package/components/forms/Input.jsx +8 -8
  270. package/components/forms/Input.vue +3 -3
  271. package/components/forms/MultiSelect.d.ts +2 -0
  272. package/components/forms/MultiSelect.jsx +13 -11
  273. package/components/forms/MultiSelect.vue +12 -11
  274. package/components/forms/NumberStepper.d.ts +3 -1
  275. package/components/forms/NumberStepper.jsx +7 -7
  276. package/components/forms/NumberStepper.vue +8 -7
  277. package/components/forms/Radio.d.ts +5 -3
  278. package/components/forms/Radio.jsx +13 -15
  279. package/components/forms/Radio.vue +9 -10
  280. package/components/forms/RadioGroup.vue +5 -5
  281. package/components/forms/SearchInput.d.ts +3 -1
  282. package/components/forms/SearchInput.jsx +6 -6
  283. package/components/forms/SearchInput.vue +7 -6
  284. package/components/forms/Select.d.ts +3 -2
  285. package/components/forms/Select.jsx +6 -7
  286. package/components/forms/Select.vue +6 -6
  287. package/components/forms/Slider.jsx +3 -3
  288. package/components/forms/Slider.vue +3 -3
  289. package/components/forms/Switch.d.ts +2 -2
  290. package/components/forms/Switch.jsx +20 -22
  291. package/components/forms/Switch.vue +22 -18
  292. package/components/forms/Textarea.vue +5 -5
  293. package/components/forms/forms.prompt.md +1 -1
  294. package/components/forms/input-base.js +2 -1
  295. package/components/icons/Icon.jsx +17 -1
  296. package/components/icons/Icon.prompt.md +2 -0
  297. package/components/icons/Icon.vue +1 -1
  298. package/components/icons/icon-paths.js +15 -0
  299. package/components/index.d.ts +1 -2
  300. package/components/index.js +4 -5
  301. package/components/navigation/Breadcrumb.jsx +6 -10
  302. package/components/navigation/Breadcrumb.vue +6 -12
  303. package/components/navigation/DocTree.jsx +3 -3
  304. package/components/navigation/DocTree.vue +3 -3
  305. package/components/navigation/PageHeading.vue +1 -1
  306. package/components/navigation/PageToolbar.vue +7 -6
  307. package/components/navigation/Sidebar.jsx +4 -23
  308. package/components/navigation/Sidebar.vue +5 -10
  309. package/components/navigation/ThemeToggle.vue +2 -1
  310. package/components/navigation/TopBar.d.ts +3 -1
  311. package/components/navigation/TopBar.jsx +6 -6
  312. package/components/navigation/Tree.jsx +5 -5
  313. package/components/navigation/Tree.vue +13 -8
  314. package/components/navigation/nav-model.js +23 -0
  315. package/components/navigation/theme-store.js +9 -0
  316. package/components/pipeline/ConfigPanel.d.ts +2 -0
  317. package/components/pipeline/ConfigPanel.jsx +9 -9
  318. package/components/pipeline/ConfigPanel.vue +9 -9
  319. package/components/pipeline/DataPreview.d.ts +3 -1
  320. package/components/pipeline/DataPreview.jsx +7 -7
  321. package/components/pipeline/DataPreview.vue +7 -7
  322. package/components/pipeline/NodeCard.jsx +3 -3
  323. package/components/pipeline/NodeCard.vue +3 -3
  324. package/components/pipeline/PaletteItem.vue +2 -2
  325. package/components/pipeline/PaletteListItem.vue +1 -1
  326. package/components/pipeline/PalettePanel.jsx +2 -2
  327. package/components/pipeline/PalettePanel.vue +1 -1
  328. package/components/pipeline/Port.vue +15 -0
  329. package/components/pipeline/PortLabel.vue +15 -0
  330. package/components/pipeline/ZoomControls.vue +1 -1
  331. package/components/pipeline/port-skin.js +13 -0
  332. package/components/px.js +4 -0
  333. package/components/tabs/PanelTabs.vue +5 -4
  334. package/components/tabs/PillTabs.vue +5 -4
  335. package/components/tabs/SegmentedControl.d.ts +2 -2
  336. package/components/tabs/SegmentedControl.jsx +7 -7
  337. package/components/tabs/SegmentedControl.vue +7 -7
  338. package/components/tabs/Tabs.d.ts +5 -3
  339. package/components/tabs/Tabs.jsx +15 -15
  340. package/components/tabs/Tabs.vue +5 -5
  341. package/components/tabs/ViewToggle.d.ts +3 -1
  342. package/components/tabs/ViewToggle.jsx +5 -5
  343. package/components/tabs/ViewToggle.vue +5 -5
  344. package/components/tabs/tabs.prompt.md +1 -1
  345. package/keybricks.css +4 -0
  346. package/package.json +6 -3
  347. package/readme.md +91 -122
  348. package/readme.zh-CN.md +121 -0
  349. package/tokens/alpha.css +3 -1
  350. package/tokens/charts.css +20 -17
  351. package/tokens/colors.css +3 -3
  352. package/tokens/dark.css +3 -3
  353. package/tokens/hover.css +32 -0
  354. package/tokens/interaction.css +3 -1
  355. package/tokens/layout.css +31 -7
  356. package/tokens/tokens.d.ts +58 -0
  357. package/tokens/tokens.js +179 -92
  358. package/tokens/tokens.json +327 -114
  359. package/tokens/typography.css +45 -15
  360. package/components/_cards/brand-visual.card.html +0 -43
  361. package/components/_cards/datatable.card.html +0 -48
  362. package/components/_shared/src-brand.js +0 -1
  363. package/components/brand/BrandVisual.d.ts +0 -17
  364. package/components/brand/BrandVisual.jsx +0 -29
  365. package/components/brand/BrandVisual.vue +0 -34
  366. package/components/brand/_dir.card.html +0 -13
  367. package/components/brand/brandMotifs.js +0 -210
  368. package/components/buttons/_dir.card.html +0 -13
  369. package/components/charts/ChartKit.vue +0 -47
  370. package/components/charts/_dir.card.html +0 -7
  371. package/components/data/DataTable.d.ts +0 -28
  372. package/components/data/DataTable.jsx +0 -32
  373. package/components/data/DataTable.vue +0 -25
  374. package/components/data/_dir.card.html +0 -13
  375. package/components/display/_dir.card.html +0 -13
  376. package/components/feedback/_dir.card.html +0 -13
  377. package/components/forms/_dir.card.html +0 -13
  378. package/components/icons/_dir.card.html +0 -13
  379. package/components/navigation/_dir.card.html +0 -13
  380. package/components/pipeline/_dir.card.html +0 -13
  381. package/components/tabs/_dir.card.html +0 -13
@@ -1,7 +1,11 @@
1
1
  import React from "react";
2
2
  import { Icon } from "../icons/Icon.jsx";
3
3
 
4
- const btnBase = { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6, fontFamily: "var(--font-sans)", fontWeight: "var(--weight-regular)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", border: "1px solid transparent", cursor: "pointer", whiteSpace: "nowrap", boxSizing: "border-box", background: "transparent", transition: "var(--t-hover)" };
4
+ /* Every dimension comes from the size tier the button sits in (tokens/layout.css): height, text, icon and padding
5
+ switch together when a region declares data-size="sm" | "md" | "lg". A `size` prop only stamps that attribute on
6
+ the button itself — the default is to inherit, so siblings in one bar can never disagree. */
7
+ const btnBase = { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6, height: "var(--control-h)", padding: "0 var(--control-px)", borderRadius: "var(--radius-md)", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-regular)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", border: "1px solid transparent", cursor: "pointer", whiteSpace: "nowrap", boxSizing: "border-box", background: "transparent", transition: "var(--t-hover)" };
8
+ const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
5
9
 
6
10
  const variants = {
7
11
  primary: { background: "var(--action-primary)", color: "var(--on-accent)" },
@@ -16,7 +20,7 @@ const variants = {
16
20
  const hovers = {
17
21
  primary: "var(--action-primary-hover)", dark: "var(--action-dark-hover)",
18
22
  outline: "var(--bg-hover)", ghost: "var(--bg-hover)", "primary-soft": "var(--soft-hover)",
19
- danger: "var(--danger-text)", "danger-soft": "var(--danger-bg)",
23
+ danger: "var(--danger-text)", "danger-soft": "var(--danger-soft-hover)",
20
24
  };
21
25
  /** Press (mouse-down) fills — one step past hover. */
22
26
  const presses = {
@@ -26,20 +30,13 @@ const presses = {
26
30
  };
27
31
  /** Variants that change text color (+ underline) on hover instead of a background wash. */
28
32
  const colorHoverVariants = { text: "var(--text-heading)" };
29
- const sizes = {
30
- sm: { height: 30, padding: "6px 12px", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", letterSpacing: 0, borderRadius: "var(--radius-md)" },
31
- md: { height: 32, padding: "6px 12px", borderRadius: "var(--radius-md)" },
32
- lg: { height: 35.5, padding: "8px 16px", fontWeight: "var(--weight-medium)", borderRadius: "var(--radius-lg)" },
33
- };
34
- const iconOnlyPad = { sm: 0, md: 0, lg: 0 };
35
- const iconOnlyBox = { sm: 30, md: 32, lg: 36 };
36
33
 
37
34
  function Spinner({ size, color }) {
38
- return <span style={{ display: "inline-block", width: size, height: size, borderRadius: "var(--radius-full)", border: "2px solid currentColor", borderTopColor: "transparent", opacity: .9, color, animation: "kb-spin .7s linear infinite", flexShrink: 0 }} />;
35
+ return <span style={{ display: "inline-block", width: size, height: size, ...(size ? null : tierIcon), borderRadius: "var(--radius-full)", border: "2px solid currentColor", borderTopColor: "transparent", opacity: .9, color, animation: "kb-spin .7s linear infinite", flexShrink: 0 }} />;
39
36
  }
40
37
 
41
- /** Keybricks button. variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text; size: sm | md | lg. Interaction states: hover (one step darker) · press (two steps, set via mouse-down) · keyboard focus ring · loading · disabled. */
42
- export function Button({ variant = "primary", size = "md", icon, iconSize, iconPosition = "left", medium, disabled, loading, fullWidth, iconOnly, label, pressed, focused, children, style, onClick, ...rest }) {
38
+ /** Keybricks button. variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text; size: sm 28 | md 32 | lg 40 — omit it to inherit the region's tier (the default). Interaction states: hover (one step darker) · press (two steps, set via mouse-down) · keyboard focus ring · loading · disabled. */
39
+ export function Button({ variant = "primary", size, icon, iconSize, iconPosition = "left", medium, disabled, loading, fullWidth, iconOnly, label, pressed, focused, children, style, onClick, ...rest }) {
43
40
  const [hover, setHover] = React.useState(false);
44
41
  const [press, setPress] = React.useState(false);
45
42
  const [focus, setFocus] = React.useState(false);
@@ -47,10 +44,10 @@ export function Button({ variant = "primary", size = "md", icon, iconSize, iconP
47
44
  const isPressed = press || pressed;
48
45
  const isFocused = focus || focused;
49
46
  const s = {
50
- ...btnBase, ...sizes[size], ...variants[variant],
47
+ ...btnBase, ...variants[variant],
51
48
  ...(medium ? { fontWeight: "var(--weight-medium)" } : null),
52
49
  ...(fullWidth ? { width: "100%" } : null),
53
- ...(iconOnly ? { width: iconOnlyBox[size], padding: iconOnlyPad[size], gap: 0 } : null),
50
+ ...(iconOnly ? { width: "var(--control-h)", padding: 0, gap: 0 } : null),
54
51
  ...(busy ? { opacity: .55, cursor: "default" } : null),
55
52
  ...style,
56
53
  };
@@ -65,11 +62,11 @@ export function Button({ variant = "primary", size = "md", icon, iconSize, iconP
65
62
  }
66
63
  if (isFocused) s.boxShadow = "var(--ring-focus)";
67
64
  }
68
- const iSize = iconSize || (size === "sm" ? 12 : 14);
69
- const leading = loading ? <Spinner size={iSize} color={s.color} /> : (icon && iconPosition === "left" ? <Icon name={icon} size={iSize} /> : null);
70
- const trailing = !loading && icon && iconPosition === "right" ? <Icon name={icon} size={iSize} /> : null;
65
+ const iconStyle = iconSize ? null : tierIcon;
66
+ const leading = loading ? <Spinner size={iconSize} color={s.color} /> : (icon && iconPosition === "left" ? <Icon name={icon} size={iconSize || 16} style={iconStyle} /> : null);
67
+ const trailing = !loading && icon && iconPosition === "right" ? <Icon name={icon} size={iconSize || 16} style={iconStyle} /> : null;
71
68
  return (
72
- <button type="button" data-press="own" style={s} disabled={busy} aria-label={iconOnly ? label : undefined} onClick={onClick}
69
+ <button type="button" data-press="own" data-size={size} style={s} disabled={busy} aria-label={iconOnly ? label : undefined} onClick={onClick}
73
70
  onMouseEnter={() => setHover(true)} onMouseLeave={() => { setHover(false); setPress(false); }}
74
71
  onMouseDown={() => setPress(true)} onMouseUp={() => setPress(false)}
75
72
  onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") setPress(true); }} onKeyUp={() => setPress(false)}
@@ -1,8 +1,13 @@
1
1
  <script setup>
2
+ import { px } from '../px.js'
2
3
  import { computed, ref } from 'vue'
3
4
  import Icon from '../icons/Icon.vue'
4
5
 
5
- const btnBase = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6, fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-regular)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', border: '1px solid transparent', cursor: 'pointer', whiteSpace: 'nowrap', boxSizing: 'border-box', background: 'transparent', transition: 'var(--t-hover)' }
6
+ // Every dimension comes from the size tier the button sits in (tokens/layout.css): height, text, icon and padding
7
+ // switch together when a region declares data-size="sm" | "md" | "lg". The `size` prop only stamps that attribute on
8
+ // the button itself — the default is to inherit, so siblings in one bar can never disagree.
9
+ const btnBase = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '6px', height: 'var(--control-h)', padding: '0 var(--control-px)', borderRadius: 'var(--radius-md)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-regular)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', border: '1px solid transparent', cursor: 'pointer', whiteSpace: 'nowrap', boxSizing: 'border-box', background: 'transparent', transition: 'var(--t-hover)' }
10
+ const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
6
11
  const variants = {
7
12
  primary: { background: 'var(--action-primary)', color: 'var(--on-accent)' },
8
13
  dark: { background: 'var(--action-dark)', color: 'var(--action-dark-fg)' },
@@ -13,20 +18,13 @@ const variants = {
13
18
  'danger-soft': { background: 'var(--danger-bg)', color: 'var(--danger-text)' },
14
19
  text: { color: 'var(--text-secondary)', padding: '0', height: 'auto' },
15
20
  }
16
- const hovers = { primary: 'var(--action-primary-hover)', dark: 'var(--action-dark-hover)', outline: 'var(--bg-hover)', ghost: 'var(--bg-hover)', 'primary-soft': 'var(--soft-hover)', danger: 'var(--danger-text)', 'danger-soft': 'var(--danger-bg)' }
21
+ const hovers = { primary: 'var(--action-primary-hover)', dark: 'var(--action-dark-hover)', outline: 'var(--bg-hover)', ghost: 'var(--bg-hover)', 'primary-soft': 'var(--soft-hover)', danger: 'var(--danger-text)', 'danger-soft': 'var(--danger-soft-hover)' }
17
22
  const presses = { primary: 'var(--action-primary-active)', dark: 'var(--action-dark-active)', outline: 'var(--bg-press)', ghost: 'var(--bg-press)', 'primary-soft': 'var(--soft-active)', danger: 'var(--danger-active)', 'danger-soft': 'var(--danger-soft-active)' }
18
23
  const colorHoverVariants = { text: 'var(--text-heading)' }
19
- const sizes = {
20
- sm: { height: 30, padding: '6px 12px', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', letterSpacing: 0, borderRadius: 'var(--radius-md)' },
21
- md: { height: 32, padding: '6px 12px', borderRadius: 'var(--radius-md)' },
22
- lg: { height: 35.5, padding: '8px 16px', fontWeight: 'var(--weight-medium)', borderRadius: 'var(--radius-lg)' },
23
- }
24
- const iconOnlyPad = { sm: 0, md: 0, lg: 0 }
25
- const iconOnlyBox = { sm: 30, md: 32, lg: 36 }
26
24
 
27
- // Button — variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text; size: sm | md | lg.
25
+ // Button — variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text; size: sm 28 | md 32 | lg 40 — omit it to inherit the region's tier (the default).
28
26
  const props = defineProps({
29
- variant: { type: String, default: 'primary' }, size: { type: String, default: 'md' },
27
+ variant: { type: String, default: 'primary' }, size: String,
30
28
  icon: String, iconSize: Number, iconPosition: { type: String, default: 'left' },
31
29
  medium: Boolean, disabled: Boolean, loading: Boolean, fullWidth: Boolean, iconOnly: Boolean,
32
30
  label: String, pressed: Boolean, focused: Boolean, style: Object,
@@ -37,10 +35,10 @@ const busy = computed(() => props.disabled || props.loading)
37
35
  const isPressed = computed(() => press.value || props.pressed)
38
36
  const isFocused = computed(() => focus.value || props.focused)
39
37
  const s = computed(() => {
40
- const base = { ...btnBase, ...sizes[props.size], ...variants[props.variant],
38
+ const base = { ...btnBase, ...variants[props.variant],
41
39
  ...(props.medium ? { fontWeight: 'var(--weight-medium)' } : null),
42
40
  ...(props.fullWidth ? { width: '100%' } : null),
43
- ...(props.iconOnly ? { width: iconOnlyBox[props.size], padding: iconOnlyPad[props.size], gap: 0 } : null),
41
+ ...(props.iconOnly ? { width: 'var(--control-h)', padding: 0, gap: 0 } : null),
44
42
  ...(busy.value ? { opacity: .55, cursor: 'default' } : null), ...props.style }
45
43
  if (!busy.value) {
46
44
  if (colorHoverVariants[props.variant]) {
@@ -52,16 +50,17 @@ const s = computed(() => {
52
50
  }
53
51
  return base
54
52
  })
55
- const iSize = computed(() => props.iconSize || (props.size === 'sm' ? 12 : 14))
53
+ const iSize = computed(() => props.iconSize || 16)
54
+ const iconStyle = computed(() => props.iconSize ? null : tierIcon)
56
55
  </script>
57
56
  <template>
58
- <button type="button" data-press="own" :style="s" :disabled="busy" :aria-label="iconOnly ? label : undefined"
57
+ <button type="button" data-press="own" :data-size="size" :style="s" :disabled="busy" :aria-label="iconOnly ? label : undefined"
59
58
  @click="$emit('click')" @mouseenter="hover = true" @mouseleave="hover = false; press = false"
60
59
  @mousedown="press = true" @mouseup="press = false" @keydown.enter="press = true" @keydown.space="press = true" @keyup="press = false"
61
60
  @focus="focus = $event.target.matches(':focus-visible')" @blur="focus = false">
62
- <span v-if="loading" :style="{ display: 'inline-block', width: iSize, height: iSize, borderRadius: 'var(--radius-full)', border: '2px solid currentColor', borderTopColor: 'transparent', opacity: .9, animation: 'kb-spin .7s linear infinite', flexShrink: 0 }" />
63
- <Icon v-else-if="icon && iconPosition === 'left'" :name="icon" :size="iSize" />
61
+ <span v-if="loading" :style="{ display: 'inline-block', width: px(iSize), height: px(iSize), ...iconStyle, borderRadius: 'var(--radius-full)', border: '2px solid currentColor', borderTopColor: 'transparent', opacity: .9, animation: 'kb-spin .7s linear infinite', flexShrink: 0 }" />
62
+ <Icon v-else-if="icon && iconPosition === 'left'" :name="icon" :size="iSize" :style="iconStyle" />
64
63
  <slot v-if="!iconOnly" />
65
- <Icon v-if="!loading && icon && iconPosition === 'right'" :name="icon" :size="iSize" />
64
+ <Icon v-if="!loading && icon && iconPosition === 'right'" :name="icon" :size="iSize" :style="iconStyle" />
66
65
  </button>
67
66
  </template>
@@ -10,7 +10,7 @@ const items = computed(() => slots.default ? slots.default() : [])
10
10
  <component :is="child" :style="{ borderRadius: 0,
11
11
  ...(i === 0 ? { borderTopLeftRadius: 'var(--radius-md)', borderBottomLeftRadius: 'var(--radius-md)' } : null),
12
12
  ...(i === items.length - 1 ? { borderTopRightRadius: 'var(--radius-md)', borderBottomRightRadius: 'var(--radius-md)' } : null),
13
- marginLeft: i === 0 ? 0 : -1 }" />
13
+ marginLeft: i === 0 ? 0 : '-1px' }" />
14
14
  </template>
15
15
  </div>
16
16
  </template>
@@ -3,7 +3,8 @@ import * as React from "react";
3
3
  export interface IconButtonProps {
4
4
  /** Icon glyph name */
5
5
  icon: string;
6
- /** xs 22/r8 · sm 26/r4 (view toggle) · md 28/r8 (toolbar) · lg 34/r8 (top bar) */
6
+ /** Size tier: a square of sm 28 · md 32 · lg 40 with a 14/16/16 icon. Omit to inherit the enclosing region's tier
7
+ * (the default). "xs" is a deprecated alias of "sm". */
7
8
  size?: "xs" | "sm" | "md" | "lg";
8
9
  /** Pressed/selected state (#f5f5f5 wash) */
9
10
  active?: boolean;
@@ -1,28 +1,32 @@
1
1
  import React from "react";
2
2
  import { Icon } from "../icons/Icon.jsx";
3
3
 
4
- const ibSizes = { xs: { box: 22, icon: 14, radius: "var(--radius-md)" }, sm: { box: 26, icon: 14, radius: "var(--radius-sm)" }, md: { box: 28, icon: 16, radius: "var(--radius-md)" }, lg: { box: 34, icon: 18, radius: "var(--radius-md)" } };
4
+ /* A square of the region's control height, icon at the tier's icon size (tokens/layout.css). `size` stamps
5
+ data-size on the button; omit it to inherit — the default. "xs" is kept as an alias of "sm" for 1.x callers. */
6
+ const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
5
7
 
6
- /** Square ghost icon button (toolbar/back/bell/view toggles). size: xs 22 | sm 26 | md 28 | lg 34. danger tints red; loading shows a spinner and disables interaction. States: hover · press · keyboard focus ring · active (persistent toggle-on) · disabled. */
7
- export function IconButton({ icon, size = "md", active, muted, danger, disabled, loading, label, pressed, focused, style, onClick, ...rest }) {
8
+ /** Square ghost icon button (toolbar/back/bell/view toggles). size: sm 28 | md 32 | lg 40 — omit to inherit the region's tier. danger tints red; loading shows a spinner and disables interaction. States: hover · press · keyboard focus ring · active (persistent toggle-on) · disabled. */
9
+ export function IconButton({ icon, size, active, muted, danger, disabled, loading, label, pressed, focused, style, onClick, ...rest }) {
8
10
  const [hover, setHover] = React.useState(false);
9
11
  const [press, setPress] = React.useState(false);
10
12
  const [focus, setFocus] = React.useState(false);
11
- const t = ibSizes[size];
13
+ const tier = size === "xs" ? "sm" : size;
12
14
  const busy = disabled || loading;
13
15
  const isPressed = (press || pressed) && !busy;
14
16
  const color = danger ? "var(--danger)" : muted ? "var(--text-muted)" : "var(--text-body)";
15
- const bg = isPressed ? (danger ? "var(--danger-soft-active)" : "var(--bg-press)")
17
+ // hover / press are alpha overlays, not solid steps: the button sits on white rows, grey table headers and grey bars alike,
18
+ // and an overlay darkens whatever is underneath by the same amount (a solid --bg-hover vanishes on a --bg-table-header).
19
+ const bg = isPressed ? (danger ? "var(--danger-soft-active)" : "var(--overlay-press)")
16
20
  : active ? "var(--bg-active)"
17
- : hover && !busy ? (danger ? "var(--danger-bg)" : "var(--bg-hover)") : "transparent";
21
+ : hover && !busy ? (danger ? "var(--danger-bg)" : "var(--overlay-hover)") : "transparent";
18
22
  return (
19
- <button type="button" data-press="own" aria-label={label} disabled={busy} onClick={onClick}
23
+ <button type="button" data-press="own" data-size={tier} aria-label={label} disabled={busy} onClick={onClick}
20
24
  onMouseEnter={() => setHover(true)} onMouseLeave={() => { setHover(false); setPress(false); }}
21
25
  onMouseDown={() => setPress(true)} onMouseUp={() => setPress(false)}
22
26
  onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") setPress(true); }} onKeyUp={() => setPress(false)}
23
27
  onFocus={(e) => { try { setFocus(e.target.matches(":focus-visible")); } catch (err) { setFocus(true); } }} onBlur={() => setFocus(false)}
24
- style={{ width: t.box, height: t.box, borderRadius: t.radius, border: "none", cursor: busy ? "default" : "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", background: bg, color, opacity: busy ? .5 : 1, boxShadow: (focus || focused) && !busy ? "var(--ring-focus)" : "none", transition: "var(--t-hover)", padding: 0, flexShrink: 0, ...style }} {...rest}>
25
- {loading ? <span style={{ display: "inline-block", width: t.icon, height: t.icon, borderRadius: "var(--radius-full)", border: "2px solid currentColor", borderTopColor: "transparent", animation: "kb-spin .7s linear infinite" }} /> : <Icon name={icon} size={t.icon} />}
28
+ style={{ width: "var(--control-h)", height: "var(--control-h)", borderRadius: "var(--radius-md)", border: "none", cursor: busy ? "default" : "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", background: bg, color, opacity: busy ? .5 : 1, boxShadow: (focus || focused) && !busy ? "var(--ring-focus)" : "none", transition: "var(--t-hover)", padding: 0, flexShrink: 0, ...style }} {...rest}>
29
+ {loading ? <span style={{ display: "inline-block", ...tierIcon, borderRadius: "var(--radius-full)", border: "2px solid currentColor", borderTopColor: "transparent", animation: "kb-spin .7s linear infinite" }} /> : <Icon name={icon} size={16} style={tierIcon} />}
26
30
  </button>
27
31
  );
28
32
  }
@@ -1,21 +1,24 @@
1
1
  <script setup>
2
2
  import { ref, computed } from 'vue'
3
3
  import Icon from '../icons/Icon.vue'
4
- const ibSizes = { xs: { box: 22, icon: 14, radius: 'var(--radius-md)' }, sm: { box: 26, icon: 14, radius: 'var(--radius-sm)' }, md: { box: 28, icon: 16, radius: 'var(--radius-md)' }, lg: { box: 34, icon: 18, radius: 'var(--radius-md)' } }
5
- // Square ghost icon button (toolbar/back/bell/view toggles). size: xs 22 | sm 26 | md 28 | lg 34.
6
- const props = defineProps({ icon: String, size: { type: String, default: 'md' }, active: Boolean, muted: Boolean, danger: Boolean, disabled: Boolean, loading: Boolean, label: String, pressed: Boolean, focused: Boolean, style: Object })
4
+ // A square of the region's control height, icon at the tier's icon size (tokens/layout.css). `size` stamps data-size on
5
+ // the button; omit it to inherit — the default. "xs" is kept as an alias of "sm" for 1.x callers.
6
+ const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
7
+ // Square ghost icon button (toolbar/back/bell/view toggles). size: sm 28 | md 32 | lg 40 — omit to inherit the region's tier.
8
+ const props = defineProps({ icon: String, size: String, active: Boolean, muted: Boolean, danger: Boolean, disabled: Boolean, loading: Boolean, label: String, pressed: Boolean, focused: Boolean, style: Object })
7
9
  defineEmits(['click'])
8
10
  const hover = ref(false), press = ref(false), focus = ref(false)
9
- const t = computed(() => ibSizes[props.size])
11
+ const tier = computed(() => props.size === 'xs' ? 'sm' : props.size)
10
12
  const busy = computed(() => props.disabled || props.loading)
11
13
  const isPressed = computed(() => (press.value || props.pressed) && !busy.value)
12
14
  const color = computed(() => props.danger ? 'var(--danger)' : props.muted ? 'var(--text-muted)' : 'var(--text-body)')
13
- const bg = computed(() => isPressed.value ? (props.danger ? 'var(--danger-soft-active)' : 'var(--bg-press)') : props.active ? 'var(--bg-active)' : (hover.value && !busy.value ? (props.danger ? 'var(--danger-bg)' : 'var(--bg-hover)') : 'transparent'))
14
- const s = computed(() => ({ width: t.value.box, height: t.value.box, borderRadius: t.value.radius, border: 'none', cursor: busy.value ? 'default' : 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: bg.value, color: color.value, opacity: busy.value ? .5 : 1, boxShadow: (focus.value || props.focused) && !busy.value ? 'var(--ring-focus)' : 'none', transition: 'var(--t-hover)', padding: 0, flexShrink: 0, ...props.style }))
15
+ // hover / press are alpha overlays so the step is the same on white rows, grey table headers and grey bars
16
+ const bg = computed(() => isPressed.value ? (props.danger ? 'var(--danger-soft-active)' : 'var(--overlay-press)') : props.active ? 'var(--bg-active)' : (hover.value && !busy.value ? (props.danger ? 'var(--danger-bg)' : 'var(--overlay-hover)') : 'transparent'))
17
+ const s = computed(() => ({ width: 'var(--control-h)', height: 'var(--control-h)', borderRadius: 'var(--radius-md)', border: 'none', cursor: busy.value ? 'default' : 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: bg.value, color: color.value, opacity: busy.value ? .5 : 1, boxShadow: (focus.value || props.focused) && !busy.value ? 'var(--ring-focus)' : 'none', transition: 'var(--t-hover)', padding: 0, flexShrink: 0, ...props.style }))
15
18
  </script>
16
19
  <template>
17
- <button type="button" data-press="own" :aria-label="label" :disabled="busy" @click="$emit('click')" @mouseenter="hover = true" @mouseleave="hover = false; press = false" @mousedown="press = true" @mouseup="press = false" @keydown.enter="press = true" @keydown.space="press = true" @keyup="press = false" @focus="focus = $event.target.matches(':focus-visible')" @blur="focus = false" :style="s">
18
- <span v-if="loading" :style="{ display: 'inline-block', width: t.icon, height: t.icon, borderRadius: 'var(--radius-full)', border: '2px solid currentColor', borderTopColor: 'transparent', animation: 'kb-spin .7s linear infinite' }" />
19
- <Icon v-else :name="icon" :size="t.icon" />
20
+ <button type="button" data-press="own" :data-size="tier" :aria-label="label" :disabled="busy" @click="$emit('click')" @mouseenter="hover = true" @mouseleave="hover = false; press = false" @mousedown="press = true" @mouseup="press = false" @keydown.enter="press = true" @keydown.space="press = true" @keyup="press = false" @focus="focus = $event.target.matches(':focus-visible')" @blur="focus = false" :style="s">
21
+ <span v-if="loading" :style="{ display: 'inline-block', ...tierIcon, borderRadius: 'var(--radius-full)', border: '2px solid currentColor', borderTopColor: 'transparent', animation: 'kb-spin .7s linear infinite' }" />
22
+ <Icon v-else :name="icon" :size="16" :style="tierIcon" />
20
23
  </button>
21
24
  </template>
@@ -1,19 +1,20 @@
1
- Buttons: `Button` (primary/dark/outline/ghost/primary-soft; sm/md/lg), `IconButton` (square ghost), `ActionChip` (tinted bulk actions).
1
+ Buttons: `Button` (primary/dark/outline/ghost/primary-soft), `IconButton` (square ghost), `ActionChip` (tinted bulk actions). Height, text and padding come from the region's size tier — `data-size="sm" | "md" | "lg"` = 24 / 32 / 36px — so leave `size` off and set the tier on the bar.
2
2
 
3
3
  ```jsx
4
4
  <Button icon="send">Submit</Button>
5
5
  <Button variant="dark" icon="sparkles">AI Assistant</Button>
6
6
  <Button variant="ghost" icon="play">Run Test</Button>
7
7
  <Button variant="text">Cancel</Button>
8
- <Button variant="primary-soft" icon="eye" size="sm">Data Preview</Button>
8
+ <Button variant="primary-soft" icon="eye">Data Preview</Button>
9
9
  <Button variant="danger" icon="trash-2">Delete Node</Button>
10
10
  <Button loading>Submit</Button>
11
11
  <Button icon="bell" iconOnly label="Notifications" variant="outline"/>
12
12
  <Button fullWidth icon="upload">Upload Configuration File</Button>
13
- <ButtonGroup><Button variant="outline" size="sm">Day</Button><Button variant="outline" size="sm">Week</Button></ButtonGroup>
14
- <IconButton icon="bell" size="lg"/>
13
+ <ButtonGroup><Button variant="outline">Day</Button><Button variant="outline">Week</Button></ButtonGroup>
14
+ <IconButton icon="bell"/>
15
+ <div data-size="sm"><Button icon="filter">Filter</Button><IconButton icon="x" label="Clear"/></div> // one tier for the whole bar
15
16
  <IconButton icon="trash-2" danger label="Delete"/>
16
17
  <ActionChip tone="success">Mark as New</ActionChip>
17
18
  ```
18
19
 
19
- Primary = the single blue CTA per view. Dark = brand-level actions (AI Assistant, Show). Ghost text buttons sit beside the CTA ("Back", "Run Test"). `text` = no background/border ever, underlines on hover — for tertiary actions like "Cancel"/"View all", never a page's main action. Danger/danger-soft = destructive actions. lg (r10, 500 weight) only in wizard footers. `loading` swaps the icon for a spinner and disables the button without changing its size; `iconOnly` needs a `label` for accessibility; `ButtonGroup` fuses adjacent buttons into one segmented control or a split button (main action + attached icon-only).
20
+ Primary = the single blue CTA per view. Dark = brand-level actions (AI Assistant, Show). Ghost = no box at rest, grey wash on hover — the default for actions in card and section headers, table toolbars and panel chrome ("Export", "Filter", "Run Test", "Back"); same height as outline so it lines up. `text` = bare label, underline on hover — links that happen to be buttons ("View all", "Clear filters"), never beside a boxed button and never a toolbar action. Emphasis is always the variant, never the size: a quieter action gets `ghost` or `text`, not `size="sm"`. Danger/danger-soft = destructive actions. lg (36px) comes from an lg region — dialogs and wizard footers; `Modal` sets it for you. A `size` prop on a single button is only for a control that stands alone, never to adjust one button inside a bar. `loading` swaps the icon for a spinner and disables the button without changing its size; `iconOnly` needs a `label` for accessibility; `ButtonGroup` fuses adjacent buttons into one segmented control or a split button (main action + attached icon-only).
@@ -53,7 +53,7 @@ export function Bar3D({ series = [], categories = [], width = 460, height = 300,
53
53
  {vTicks.map((t, i) => (
54
54
  <g key={"vt" + i}>
55
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>
56
+ <text x={t.p.x - 7} y={t.p.y + 3} textAnchor="end" style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", fill: "var(--chart-label)" }}>{t.v}</text>
57
57
  </g>
58
58
  ))}
59
59
  {categories.map((c, i) => {
@@ -63,7 +63,7 @@ export function Bar3D({ series = [], categories = [], width = 460, height = 300,
63
63
  const p0 = P(i / n - 0.5 + 0.5 / n, -0.46, zE);
64
64
  const ctr = P(0, -0.46, 0);
65
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>;
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: "var(--text-2xs)", fill: "var(--chart-label)" }}>{c}</text>;
67
67
  })}
68
68
  {drawn.map((b) => (
69
69
  <g key={b.key} opacity={hover == null || hover === b.key ? 1 : 0.45}
@@ -56,9 +56,9 @@ const g = computed(() => {
56
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
57
  <g v-for="(t,i) in g.vTicks" :key="'vt'+i">
58
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>
59
+ <text :x="t.p.x-7" :y="t.p.y+3" text-anchor="end" style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);fill:var(--chart-label)">{{ t.v }}</text>
60
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>
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:var(--text-2xs);fill:var(--chart-label)">{{ l.c }}</text>
62
62
  <g v-for="b in g.drawn" :key="b.key" :opacity="hover==null||hover===b.key?1:0.45"
63
63
  @mouseenter="interactive && (hover=b.key)" @mouseleave="interactive && (hover=null)"
64
64
  style="transition:opacity var(--dur-fast) var(--ease-out)">
@@ -67,7 +67,7 @@ const g = computed(() => {
67
67
  </svg>
68
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
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>
70
+ <div style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;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
71
  </div>
72
72
  <div style="display:flex;gap:14px;padding-top:6px;line-height:normal">
73
73
  <span v-for="(s,i) in series" :key="s.name" style="display:inline-flex;align-items:center;gap:6px">
@@ -92,11 +92,11 @@ export function BarChart({ series = [], categories = [], mode = "grouped", orien
92
92
  {ticks.map((t) => (
93
93
  <g key={t}>
94
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>
95
+ <text x={sv(t)} y={height - pad.b + 15} textAnchor="middle" style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", fill: "var(--chart-label)" }}>{pct ? t + "%" : yFormat(t)}</text>
96
96
  </g>
97
97
  ))}
98
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>
99
+ <text key={i} x={pad.l - 8} y={mid(i) + 3.5} textAnchor="end" style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-2xs)", fill: hover === i ? "var(--text-heading)" : "var(--chart-label)" }}>{c}</text>
100
100
  ))}
101
101
  </g>
102
102
  ) : null}
@@ -107,13 +107,13 @@ export function BarChart({ series = [], categories = [], mode = "grouped", orien
107
107
  {showValues ? rects.map((r) => (
108
108
  (horiz ? r.w : r.h) < 22 ? null : (
109
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>
110
+ textAnchor={horiz ? "end" : "middle"} style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", fill: "var(--on-accent)" }}>{fmtVal(r.s, r.i)}</text>
111
111
  )
112
112
  )) : null}
113
113
  {oScale ? <g>
114
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
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}
116
+ {axes ? oScale.ticks.map((t) => <text key={t} x={width - pad.r + 6} y={oScale.sy(t) + 3} style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", fill: "var(--chart-label)" }}>{overlayFormat(t)}</text>) : null}
117
117
  </g> : null}
118
118
  </svg>
119
119
  {hover != null && interactive ? (
@@ -89,8 +89,8 @@ function rowVal(s){
89
89
  <g v-for="t in g.ticks" :key="t">
90
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
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>
92
+ <text v-if="g.horiz" :x="g.sv(t)" :y="height-g.pad.b+15" text-anchor="middle" style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);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-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);fill:var(--chart-label)">{{ g.fmtAxis(t) }}</text>
94
94
  </g>
95
95
  <text v-for="(c,i) in categories" :key="'c'+i"
96
96
  :x="g.horiz ? g.pad.l-8 : g.mid(i)" :y="g.horiz ? g.mid(i)+3.5 : height-g.pad.b+16"
@@ -103,12 +103,12 @@ function rowVal(s){
103
103
  <template v-if="showValues">
104
104
  <text v-for="r in g.rects.filter(r=>(g.horiz?r.w:r.h)>=22)" :key="'v'+r.key"
105
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>
106
+ :text-anchor="g.horiz ? 'end' : 'middle'" style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);fill:var(--on-accent)">{{ r.text }}</text>
107
107
  </template>
108
108
  <template v-if="g.ov">
109
109
  <path :d="g.ov.path" fill="none" :stroke="g.ov.color" stroke-width="1.5"/>
110
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>
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-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);fill:var(--chart-label)">{{ overlayFormat(t) }}</text>
112
112
  </template>
113
113
  </svg>
114
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'}">
@@ -116,12 +116,12 @@ function rowVal(s){
116
116
  <div v-for="s in g.shown" :key="s.name" style="display:flex;align-items:center;gap:6px;padding:1px 0">
117
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
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>
119
+ <span style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading);margin-left:auto;padding-left:10px">{{ rowVal(s) }}</span>
120
120
  </div>
121
121
  <div v-if="overlay" style="display:flex;align-items:center;gap:6px;padding:1px 0">
122
122
  <span :style="{width:'7px',height:'7px',borderRadius:'2px',background:overlay.color||'var(--chart-total)'}"/>
123
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>
124
+ <span style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading);margin-left:auto;padding-left:10px">{{ overlayFormat(overlay.values[hover]) }}</span>
125
125
  </div>
126
126
  </div>
127
127
  <div v-if="legend && (series.length>1 || overlay)" style="display:flex;flex-wrap:wrap;gap:4px 14px;padding-top:8px;line-height:normal">
@@ -16,10 +16,10 @@ export function BarList({ rows = [], max, width = 360, valueFormat = fmtNum, sho
16
16
  onClick={onSelect ? () => onSelect(r) : undefined}
17
17
  style={{ opacity: hover == null || hover === r.label ? 1 : 0.5, cursor: onSelect ? "pointer" : "default", transition: "var(--t-hover)" }}>
18
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}
19
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", color: "var(--text-body)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.label}</span>
20
+ <span style={{ marginLeft: "auto", fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-xs)", color: "var(--text-heading)" }}>{valueFormat(r.value)}</span>
21
+ {hover === r.label && r.delta == null ? <span style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", color: "var(--text-muted)", paddingLeft: 6 }}>{((r.value / total) * 100).toFixed(1)}%</span> : null}
22
+ {r.delta != null ? <span style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", color: r.delta >= 0 ? "var(--chart-pos)" : "var(--chart-neg)", minWidth: 38, textAlign: "right" }}>{(r.delta >= 0 ? "+" : "") + r.delta.toFixed(1)}%</span> : null}
23
23
  </div>
24
24
  <div style={{ height: barHeight, borderRadius: "var(--radius-full)", background: showTrack ? "var(--chart-track)" : "transparent", overflow: "hidden" }}>
25
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)" }} />
@@ -12,10 +12,10 @@ const hover = ref(null)
12
12
  <div v-for="r in rows" :key="r.label" @mouseenter="interactive && (hover=r.label)" @mouseleave="interactive && (hover=null)"
13
13
  :style="{opacity:hover==null||hover===r.label?1:0.5,transition:'var(--t-hover)'}">
14
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>
15
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);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-sans);font-variant-numeric:tabular-nums;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-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);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-sans)', fontVariantNumeric: 'tabular-nums',fontSize:'var(--text-2xs-mono)',color:r.delta>=0?'var(--chart-pos)':'var(--chart-neg)',minWidth:'38px',textAlign:'right'}">{{ (r.delta>=0?'+':'')+r.delta.toFixed(1) }}%</span>
19
19
  </div>
20
20
  <div :style="{height:barHeight+'px',borderRadius:'var(--radius-full)',background:showTrack?'var(--chart-track)':'transparent',overflow:'hidden'}">
21
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)'}"/>
@@ -47,7 +47,7 @@ export function BoxPlot({ groups = [], width = 560, height = 260, axes = true, v
47
47
  <rect x={x - bw / 2} y={sy(b.q3)} width={bw} height={Math.max(1, sy(b.q1) - sy(b.q3))} rx="2" fill={c} fillOpacity="0.28" stroke={c} strokeWidth="1.2" />
48
48
  <line x1={x - bw / 2} x2={x + bw / 2} y1={sy(b.med)} y2={sy(b.med)} stroke={c} strokeWidth="2" />
49
49
  {(b.outliers || []).map((o, oi) => <circle key={oi} cx={x} cy={sy(o)} r="2.2" fill="none" stroke={c} strokeWidth="1" />)}
50
- <text x={x} y={height - pad.b + 16} textAnchor="middle" style={{ fontFamily: "var(--font-sans)", fontSize: 10, fill: "var(--chart-label)" }}>{b.label}</text>
50
+ <text x={x} y={height - pad.b + 16} textAnchor="middle" style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-2xs)", fill: "var(--chart-label)" }}>{b.label}</text>
51
51
  </g>
52
52
  );
53
53
  })}
@@ -34,7 +34,7 @@ function move(e){
34
34
  <template v-if="axes">
35
35
  <g v-for="t in g.ticks" :key="t">
36
36
  <line :x1="g.pad.l" :x2="width-g.pad.r" :y1="g.sy(t)" :y2="g.sy(t)" :stroke="t===0?'var(--chart-axis)':'var(--chart-grid)'" stroke-width="1"/>
37
- <text :x="g.pad.l-8" :y="g.sy(t)+3" text-anchor="end" style="font-family:var(--font-mono);font-size:10px;fill:var(--chart-label)">{{ valueFormat(t) }}</text>
37
+ <text :x="g.pad.l-8" :y="g.sy(t)+3" text-anchor="end" style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);fill:var(--chart-label)">{{ valueFormat(t) }}</text>
38
38
  </g>
39
39
  </template>
40
40
  <g v-for="(b,i) in g.boxes" :key="b.label" :opacity="hover==null||hover===i?1:0.45" style="transition:opacity var(--dur-fast) var(--ease-out)">
@@ -45,12 +45,12 @@ function move(e){
45
45
  <rect :x="b.x-b.bw/2" :y="g.sy(b.q3)" :width="b.bw" :height="Math.max(1,g.sy(b.q1)-g.sy(b.q3))" rx="2" :fill="b.color" fill-opacity="0.28" :stroke="b.color" stroke-width="1.2"/>
46
46
  <line :x1="b.x-b.bw/2" :x2="b.x+b.bw/2" :y1="g.sy(b.med)" :y2="g.sy(b.med)" :stroke="b.color" stroke-width="2"/>
47
47
  <circle v-for="(o,oi) in (b.outliers||[])" :key="oi" :cx="b.x" :cy="g.sy(o)" r="2.2" fill="none" :stroke="b.color" stroke-width="1"/>
48
- <text :x="b.x" :y="height-g.pad.b+16" text-anchor="middle" style="font-family:var(--font-sans);font-size:10px;fill:var(--chart-label)">{{ b.label }}</text>
48
+ <text :x="b.x" :y="height-g.pad.b+16" text-anchor="middle" style="font-family:var(--font-sans);font-size:var(--text-2xs);fill:var(--chart-label)">{{ b.label }}</text>
49
49
  </g>
50
50
  </svg>
51
51
  <div v-if="hover!=null && interactive" :style="{position:'absolute',left:g.boxes[hover].x+'px',top:(height/2-10)+'px',transform:g.boxes[hover].x>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'}">
52
52
  <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px">{{ g.boxes[hover].label }}</div>
53
- <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">
53
+ <div style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading)">
54
54
  {{ valueFormat(g.boxes[hover].lo) }} · Q1 {{ valueFormat(g.boxes[hover].q1) }} · med {{ valueFormat(g.boxes[hover].med) }} · Q3 {{ valueFormat(g.boxes[hover].q3) }} · {{ valueFormat(g.boxes[hover].hi) }}
55
55
  </div>
56
56
  </div>
@@ -20,15 +20,15 @@ export function BulletChart({ rows = [], width = 360, valueFormat = fmtNum, tone
20
20
  <div key={r.label} onMouseEnter={interactive ? () => setHover(r.label) : undefined} onMouseLeave={interactive ? () => setHover(null) : undefined}
21
21
  style={{ opacity: hover == null || on ? 1 : 0.55, transition: "var(--t-hover)" }}>
22
22
  <div style={{ display: "flex", alignItems: "baseline", paddingBottom: 4 }}>
23
- <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-body)" }}>{r.label}</span>
24
- <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-heading)" }}>{valueFormat(r.value)}</span>
25
- <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--text-muted)", paddingLeft: 6 }}>/ {valueFormat(r.target)}</span>
26
- {on ? <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: missed ? "var(--chart-neg)" : "var(--chart-pos)", paddingLeft: 8 }}>{(missed ? "" : "+") + (((r.value - r.target) / (r.target || 1)) * 100).toFixed(1)}%</span> : null}
23
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", color: "var(--text-body)" }}>{r.label}</span>
24
+ <span style={{ marginLeft: "auto", fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-xs)", color: "var(--text-heading)" }}>{valueFormat(r.value)}</span>
25
+ <span style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", color: "var(--text-muted)", paddingLeft: 6 }}>/ {valueFormat(r.target)}</span>
26
+ {on ? <span style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", color: missed ? "var(--chart-neg)" : "var(--chart-pos)", paddingLeft: 8 }}>{(missed ? "" : "+") + (((r.value - r.target) / (r.target || 1)) * 100).toFixed(1)}%</span> : null}
27
27
  </div>
28
28
  <div style={{ position: "relative", height: 12, borderRadius: 3, background: "var(--chart-track)", overflow: "hidden" }}>
29
29
  {r.ranges ? <>
30
- <div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: pc(r.ranges[0]), background: "var(--alpha-heavy, rgba(0,0,0,.12))", opacity: 0.5 }} />
31
- <div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: pc(r.ranges[1]), background: "var(--alpha-heavy, rgba(0,0,0,.12))", opacity: 0.25 }} />
30
+ <div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: pc(r.ranges[0]), background: "var(--neutral-alpha-12)", opacity: 0.5 }} />
31
+ <div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: pc(r.ranges[1]), background: "var(--neutral-alpha-12)", opacity: 0.25 }} />
32
32
  </> : null}
33
33
  <div style={{ position: "absolute", left: 0, top: 3, bottom: 3, width: pc(r.value), borderRadius: 2, background: bar, transition: "width var(--dur) var(--ease-out)" }} />
34
34
  <div style={{ position: "absolute", left: pc(r.target), top: 0, bottom: 0, width: 2, background: "var(--chart-total)" }} />
@@ -15,15 +15,15 @@ function geom(r){
15
15
  <div v-for="r in rows" :key="r.label" @mouseenter="interactive && (hover=r.label)" @mouseleave="interactive && (hover=null)"
16
16
  :style="{opacity:hover==null||hover===r.label?1:0.55,transition:'var(--t-hover)'}">
17
17
  <div style="display:flex;align-items:baseline;padding-bottom:4px">
18
- <span style="font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-body)">{{ r.label }}</span>
19
- <span style="margin-left:auto;font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">{{ valueFormat(r.value) }}</span>
20
- <span style="font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--text-muted);padding-left:6px">/ {{ valueFormat(r.target) }}</span>
21
- <span v-if="hover===r.label" :style="{fontFamily:'var(--font-mono)',fontSize:'var(--text-2xs)',paddingLeft:'8px',color:r.value<r.target?'var(--chart-neg)':'var(--chart-pos)'}">{{ (r.value<r.target?'':'+')+(((r.value-r.target)/(r.target||1))*100).toFixed(1) }}%</span>
18
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-body)">{{ r.label }}</span>
19
+ <span style="margin-left:auto;font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading)">{{ valueFormat(r.value) }}</span>
20
+ <span style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);color:var(--text-muted);padding-left:6px">/ {{ valueFormat(r.target) }}</span>
21
+ <span v-if="hover===r.label" :style="{fontFamily: 'var(--font-sans)', fontVariantNumeric: 'tabular-nums',fontSize:'var(--text-2xs-mono)',paddingLeft:'8px',color:r.value<r.target?'var(--chart-neg)':'var(--chart-pos)'}">{{ (r.value<r.target?'':'+')+(((r.value-r.target)/(r.target||1))*100).toFixed(1) }}%</span>
22
22
  </div>
23
23
  <div style="position:relative;height:12px;border-radius:3px;background:var(--chart-track);overflow:hidden">
24
24
  <template v-if="r.ranges">
25
- <div :style="{position:'absolute',left:0,top:0,bottom:0,width:geom(r).pc(r.ranges[0]),background:'rgba(0,0,0,.12)',opacity:.5}"/>
26
- <div :style="{position:'absolute',left:0,top:0,bottom:0,width:geom(r).pc(r.ranges[1]),background:'rgba(0,0,0,.12)',opacity:.25}"/>
25
+ <div :style="{position:'absolute',left:0,top:0,bottom:0,width:geom(r).pc(r.ranges[0]),background:'var(--neutral-alpha-12)',opacity:.5}"/>
26
+ <div :style="{position:'absolute',left:0,top:0,bottom:0,width:geom(r).pc(r.ranges[1]),background:'var(--neutral-alpha-12)',opacity:.25}"/>
27
27
  </template>
28
28
  <div :style="{position:'absolute',left:0,top:'3px',bottom:'3px',width:geom(r).pc(r.value),borderRadius:'2px',background:geom(r).bar,transition:'width var(--dur) var(--ease-out)'}"/>
29
29
  <div :style="{position:'absolute',left:geom(r).pc(r.target),top:0,bottom:0,width:'2px',background:'var(--chart-total)'}"/>
@@ -32,7 +32,7 @@ export function CandlestickChart({ candles = [], width = 620, height = 300, axes
32
32
  <svg width={width} height={height} onMouseMove={move} onMouseLeave={() => setHover(null)} role="img">
33
33
  {axes ? <PlotGrid w={width} h={height} pad={pad} ticks={ticks} sy={sy} xLabels={[]} sxLabel={() => 0} yFormat={valueFormat} /> : null}
34
34
  {hover != null ? <rect x={pad.l + slot * hover} y={pad.t} width={slot} height={height - pad.t - (axes ? 22 : 4)} fill="var(--bg-hover)" /> : null}
35
- {avg ? <path d={"M" + avg.map((v, i) => (v == null ? null : cx(i).toFixed(1) + " " + sy(v).toFixed(1))).filter(Boolean).join(" L")} fill="none" stroke="var(--series-8)" strokeWidth="1.3" /> : null}
35
+ {avg ? <path d={"M" + avg.map((v, i) => (v == null ? null : cx(i).toFixed(1) + " " + sy(v).toFixed(1))).filter(Boolean).join(" L")} fill="none" stroke="var(--chart-total)" strokeWidth="1.3" /> : null}
36
36
  {candles.map((c, i) => {
37
37
  const col = up(c) ? "var(--chart-pos)" : "var(--chart-neg)";
38
38
  const yO = sy(c.o), yC = sy(c.c);
@@ -48,7 +48,7 @@ export function CandlestickChart({ candles = [], width = 620, height = 300, axes
48
48
  })}
49
49
  {volume ? <line x1={pad.l} x2={width - pad.r} y1={height - (axes ? 22 : 4)} y2={height - (axes ? 22 : 4)} stroke="var(--chart-axis)" strokeWidth="1" /> : null}
50
50
  {axes ? candles.map((c, i) => (i % Math.ceil(candles.length / 6) ? null : (
51
- <text key={"t" + i} x={cx(i)} y={height - 6} textAnchor="middle" style={{ fontFamily: "var(--font-sans)", fontSize: 10, fill: "var(--chart-label)" }}>{c.t}</text>
51
+ <text key={"t" + i} x={cx(i)} y={height - 6} textAnchor="middle" style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-2xs)", fill: "var(--chart-label)" }}>{c.t}</text>
52
52
  ))) : null}
53
53
  </svg>
54
54
  {h && interactive ? (