@keyfunc/keybricks-ds 2.1.1 → 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 (240) hide show
  1. package/DEPLOY.md +13 -4
  2. package/DESIGN.md +104 -19
  3. package/INTEGRATION.md +75 -20
  4. package/SKILL.md +11 -11
  5. package/_adherence.oxlintrc.json +28 -11
  6. package/_scripts/build-index.mjs +15 -4
  7. package/assets/icons/icon-index.js +1 -1
  8. package/assets/icons/icon-index.json +1 -1
  9. package/components/_cards/accordion.card.html +1 -1
  10. package/components/_cards/action-chip.card.html +11 -14
  11. package/components/_cards/activity.card.html +2 -2
  12. package/components/_cards/alert.card.html +2 -2
  13. package/components/_cards/app-chrome.card.html +1 -1
  14. package/components/_cards/banner.card.html +1 -1
  15. package/components/_cards/breadcrumb.card.html +4 -4
  16. package/components/_cards/button-1-base.card.html +23 -26
  17. package/components/_cards/button-2-states.card.html +14 -12
  18. package/components/_cards/button-3-sizes-icons.card.html +20 -19
  19. package/components/_cards/button-4-group.card.html +14 -31
  20. package/components/_cards/canvas-zoom-controls.card.html +1 -1
  21. package/components/_cards/cards-indicators.card.html +1 -1
  22. package/components/_cards/charts-1-sparkline.card.html +4 -4
  23. package/components/_cards/charts-10-gauge.card.html +2 -2
  24. package/components/_cards/charts-11-funnel.card.html +2 -2
  25. package/components/_cards/charts-12-treemap.card.html +2 -2
  26. package/components/_cards/charts-14-radar.card.html +3 -3
  27. package/components/_cards/charts-15-distribution.card.html +2 -2
  28. package/components/_cards/charts-16-pareto.card.html +2 -2
  29. package/components/_cards/charts-17-candlestick.card.html +2 -2
  30. package/components/_cards/charts-18-sankey.card.html +2 -2
  31. package/components/_cards/charts-19-chord.card.html +2 -2
  32. package/components/_cards/charts-2-line.card.html +3 -3
  33. package/components/_cards/charts-20-sunburst.card.html +2 -2
  34. package/components/_cards/charts-21-worldmap.card.html +3 -3
  35. package/components/_cards/charts-22-gantt.card.html +2 -2
  36. package/components/_cards/charts-23-bar3d.card.html +2 -2
  37. package/components/_cards/charts-24-pie3d.card.html +2 -2
  38. package/components/_cards/charts-25-surface3d.card.html +2 -2
  39. package/components/_cards/charts-26-scatter3d.card.html +2 -2
  40. package/components/_cards/charts-27-ribbon3d.card.html +2 -2
  41. package/components/_cards/charts-3-bar.card.html +6 -6
  42. package/components/_cards/charts-4-barlist.card.html +2 -2
  43. package/components/_cards/charts-5-donut.card.html +2 -2
  44. package/components/_cards/charts-6-scatter.card.html +2 -2
  45. package/components/_cards/charts-7-heatmap.card.html +2 -2
  46. package/components/_cards/charts-8-waterfall.card.html +2 -2
  47. package/components/_cards/charts-9-bullet.card.html +3 -3
  48. package/components/_cards/charts-99-fullpage.card.html +1 -1
  49. package/components/_cards/checkbox.card.html +6 -3
  50. package/components/_cards/codeblock.card.html +2 -2
  51. package/components/_cards/combobox.card.html +6 -4
  52. package/components/_cards/context-menu.card.html +6 -4
  53. package/components/_cards/date-picker.card.html +8 -5
  54. package/components/_cards/diffvalue.card.html +2 -2
  55. package/components/_cards/divider.card.html +2 -2
  56. package/components/_cards/drawer.card.html +3 -3
  57. package/components/_cards/dropdown-menu.card.html +2 -2
  58. package/components/_cards/dropzone.card.html +5 -3
  59. package/components/_cards/empty-state.card.html +2 -2
  60. package/components/_cards/filter-bar.card.html +1 -1
  61. package/components/_cards/icon-button.card.html +14 -14
  62. package/components/_cards/icons-1-core.card.html +4 -4
  63. package/components/_cards/icons-2-feather.card.html +4 -4
  64. package/components/_cards/icons-3-unicons.card.html +3 -3
  65. package/components/_cards/input.card.html +5 -5
  66. package/components/_cards/kbd.card.html +1 -1
  67. package/components/_cards/keyvalue.card.html +1 -1
  68. package/components/_cards/listrow.card.html +1 -1
  69. package/components/_cards/loading.card.html +2 -2
  70. package/components/_cards/log-viewer.card.html +2 -2
  71. package/components/_cards/modal.card.html +1 -1
  72. package/components/_cards/multi-select.card.html +6 -4
  73. package/components/_cards/number-stepper.card.html +9 -6
  74. package/components/_cards/pagination.card.html +6 -4
  75. package/components/_cards/people.card.html +2 -2
  76. package/components/_cards/pipeline-node.card.html +8 -8
  77. package/components/_cards/pipeline-palette.card.html +2 -2
  78. package/components/_cards/radio.card.html +12 -7
  79. package/components/_cards/search.card.html +6 -6
  80. package/components/_cards/segmented-control.card.html +11 -10
  81. package/components/_cards/select.card.html +7 -3
  82. package/components/_cards/slider.card.html +2 -2
  83. package/components/_cards/smart-table.card.html +3 -3
  84. package/components/_cards/spreadsheet.card.html +2 -2
  85. package/components/_cards/stepper.card.html +2 -2
  86. package/components/_cards/table.card.html +79 -17
  87. package/components/_cards/tabs.card.html +6 -4
  88. package/components/_cards/tag.card.html +6 -6
  89. package/components/_cards/textarea.card.html +1 -1
  90. package/components/_cards/timeline.card.html +3 -3
  91. package/components/_cards/toast.card.html +7 -5
  92. package/components/_cards/toggles.card.html +14 -9
  93. package/components/_cards/tooltip.card.html +3 -3
  94. package/components/_cards/tree.card.html +2 -2
  95. package/components/_shared/card-kit.css +25 -0
  96. package/components/_shared/card-kit.js +50 -0
  97. package/components/_shared/src-buttons.js +1 -1
  98. package/components/_shared/src-charts.js +1 -1
  99. package/components/_shared/src-data.js +1 -1
  100. package/components/_shared/src-display.js +1 -1
  101. package/components/_shared/src-feedback.js +1 -1
  102. package/components/_shared/src-forms.js +1 -1
  103. package/components/_shared/src-icons.js +1 -1
  104. package/components/_shared/src-navigation.js +1 -1
  105. package/components/_shared/src-tabs.js +1 -1
  106. package/components/_shared/view-source.js +3 -3
  107. package/components/_vue/index.mjs +7 -0
  108. package/components/buttons/ActionChip.d.ts +1 -1
  109. package/components/buttons/Button.d.ts +1 -1
  110. package/components/buttons/Button.jsx +1 -1
  111. package/components/buttons/Button.vue +1 -1
  112. package/components/buttons/IconButton.d.ts +1 -1
  113. package/components/buttons/IconButton.jsx +5 -3
  114. package/components/buttons/IconButton.vue +3 -2
  115. package/components/charts/Bar3D.jsx +1 -1
  116. package/components/charts/Bar3D.vue +2 -2
  117. package/components/charts/BarChart.jsx +3 -3
  118. package/components/charts/BarChart.vue +6 -6
  119. package/components/charts/BarList.jsx +3 -3
  120. package/components/charts/BarList.vue +3 -3
  121. package/components/charts/BoxPlot.vue +2 -2
  122. package/components/charts/BulletChart.jsx +5 -5
  123. package/components/charts/BulletChart.vue +5 -5
  124. package/components/charts/CandlestickChart.vue +3 -3
  125. package/components/charts/ChartKit.jsx +4 -3
  126. package/components/charts/ChartTooltip.vue +1 -1
  127. package/components/charts/ChordChart.jsx +1 -1
  128. package/components/charts/ChordChart.vue +2 -2
  129. package/components/charts/DonutChart.jsx +1 -1
  130. package/components/charts/DonutChart.vue +2 -2
  131. package/components/charts/FunnelChart.jsx +3 -3
  132. package/components/charts/FunnelChart.vue +4 -4
  133. package/components/charts/GanttChart.vue +1 -1
  134. package/components/charts/GaugeChart.jsx +2 -2
  135. package/components/charts/GaugeChart.vue +2 -2
  136. package/components/charts/HeatmapChart.jsx +1 -1
  137. package/components/charts/HeatmapChart.vue +1 -1
  138. package/components/charts/Histogram.jsx +2 -2
  139. package/components/charts/Histogram.vue +4 -4
  140. package/components/charts/LineChart.vue +2 -2
  141. package/components/charts/ParetoChart.jsx +1 -1
  142. package/components/charts/ParetoChart.vue +3 -3
  143. package/components/charts/Pie3D.jsx +1 -1
  144. package/components/charts/Pie3D.vue +2 -2
  145. package/components/charts/PlotGrid.vue +1 -1
  146. package/components/charts/RadarChart.vue +1 -1
  147. package/components/charts/Ribbon3D.jsx +1 -1
  148. package/components/charts/Ribbon3D.vue +2 -2
  149. package/components/charts/SankeyChart.jsx +1 -1
  150. package/components/charts/SankeyChart.vue +2 -2
  151. package/components/charts/Scatter3D.vue +1 -1
  152. package/components/charts/ScatterChart.jsx +1 -1
  153. package/components/charts/ScatterChart.vue +3 -3
  154. package/components/charts/Sparkline.jsx +1 -1
  155. package/components/charts/Sparkline.vue +1 -1
  156. package/components/charts/SunburstChart.vue +1 -1
  157. package/components/charts/Surface3D.jsx +1 -1
  158. package/components/charts/Surface3D.vue +2 -2
  159. package/components/charts/TreemapChart.jsx +1 -1
  160. package/components/charts/TreemapChart.vue +2 -2
  161. package/components/charts/WaterfallChart.jsx +1 -1
  162. package/components/charts/WaterfallChart.vue +5 -4
  163. package/components/charts/WorldMap.jsx +4 -4
  164. package/components/charts/WorldMap.vue +5 -5
  165. package/components/data/DiffValue.d.ts +1 -0
  166. package/components/data/DiffValue.jsx +1 -1
  167. package/components/data/DiffValue.vue +1 -1
  168. package/components/data/KeyValue.d.ts +1 -1
  169. package/components/data/KeyValue.jsx +2 -2
  170. package/components/data/KeyValue.vue +2 -2
  171. package/components/data/Pagination.jsx +2 -2
  172. package/components/data/Pagination.vue +1 -1
  173. package/components/data/SmartTable.jsx +5 -5
  174. package/components/data/SmartTable.vue +6 -6
  175. package/components/data/Spreadsheet.jsx +5 -5
  176. package/components/data/Spreadsheet.vue +6 -6
  177. package/components/data/Table.d.ts +58 -4
  178. package/components/data/Table.jsx +153 -15
  179. package/components/data/Table.vue +105 -15
  180. package/components/data/data.prompt.md +1 -1
  181. package/components/display/Avatar.jsx +2 -2
  182. package/components/display/Avatar.vue +1 -1
  183. package/components/display/AvatarGroup.vue +1 -1
  184. package/components/display/Badge.d.ts +4 -1
  185. package/components/display/Badge.jsx +10 -5
  186. package/components/display/IconTile.vue +9 -4
  187. package/components/display/Kbd.d.ts +1 -1
  188. package/components/display/Kbd.jsx +1 -1
  189. package/components/display/Kbd.vue +1 -1
  190. package/components/feedback/ActivityItem.jsx +1 -1
  191. package/components/feedback/Modal.jsx +1 -1
  192. package/components/feedback/Modal.vue +1 -1
  193. package/components/feedback/Timeline.vue +2 -2
  194. package/components/forms/Combobox.d.ts +1 -1
  195. package/components/forms/DatePicker.d.ts +1 -1
  196. package/components/forms/Input.d.ts +1 -1
  197. package/components/forms/Input.jsx +2 -2
  198. package/components/forms/Input.vue +1 -1
  199. package/components/forms/MultiSelect.d.ts +1 -1
  200. package/components/forms/NumberStepper.d.ts +1 -1
  201. package/components/forms/SearchInput.d.ts +1 -1
  202. package/components/forms/Select.d.ts +1 -1
  203. package/components/forms/Select.jsx +1 -1
  204. package/components/forms/Select.vue +1 -1
  205. package/components/forms/Slider.jsx +1 -1
  206. package/components/forms/Slider.vue +1 -1
  207. package/components/forms/Switch.jsx +3 -3
  208. package/components/forms/Switch.vue +11 -8
  209. package/components/forms/Textarea.vue +1 -1
  210. package/components/forms/input-base.js +1 -1
  211. package/components/icons/Icon.jsx +17 -1
  212. package/components/icons/Icon.prompt.md +2 -0
  213. package/components/icons/Icon.vue +1 -1
  214. package/components/icons/icon-paths.js +15 -0
  215. package/components/index.js +1 -1
  216. package/components/navigation/Breadcrumb.jsx +3 -7
  217. package/components/navigation/Breadcrumb.vue +2 -8
  218. package/components/navigation/Sidebar.jsx +3 -22
  219. package/components/navigation/Sidebar.vue +2 -7
  220. package/components/navigation/Tree.jsx +1 -1
  221. package/components/navigation/Tree.vue +9 -4
  222. package/components/navigation/nav-model.js +23 -0
  223. package/components/navigation/theme-store.js +9 -0
  224. package/components/tabs/PillTabs.vue +1 -1
  225. package/components/tabs/SegmentedControl.d.ts +1 -1
  226. package/components/tabs/SegmentedControl.jsx +1 -1
  227. package/components/tabs/SegmentedControl.vue +1 -1
  228. package/components/tabs/Tabs.jsx +1 -1
  229. package/components/tabs/ViewToggle.d.ts +1 -1
  230. package/package.json +2 -2
  231. package/readme.md +6 -5
  232. package/readme.zh-CN.md +6 -5
  233. package/tokens/alpha.css +3 -1
  234. package/tokens/colors.css +1 -1
  235. package/tokens/dark.css +1 -1
  236. package/tokens/layout.css +7 -5
  237. package/tokens/tokens.d.ts +22 -0
  238. package/tokens/tokens.js +39 -6
  239. package/tokens/tokens.json +90 -11
  240. package/tokens/typography.css +26 -9
@@ -1 +1 @@
1
- window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"tabs/SegmentedControl.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/**\n * Segmented control — inset track, raised active segment. Switches views of the same data, applies instantly.\n * options: string[] | { label, icon?, disabled? }[] value/onChange by label\n * size sm 24 | md 32 | lg 36 — omit it to inherit the region's tier · iconOnly hides labels (label becomes aria-label / title)\n * disabled greys the whole control · fullWidth stretches segments evenly\n * Keyboard: ← → move between enabled segments.\n */\nconst tierIcon = { width: \"var(--control-icon)\", height: \"var(--control-icon)\" };\nexport function SegmentedControl({ options = [], value, onChange, size, iconOnly, disabled, fullWidth, style }) {\n const opts = options.map((o) => (typeof o === \"string\" ? { label: o } : o));\n const enabled = opts.filter((o) => !o.disabled);\n const onKey = (e) => {\n if (e.key !== \"ArrowLeft\" && e.key !== \"ArrowRight\" || disabled || !enabled.length) return;\n e.preventDefault();\n const i = enabled.findIndex((o) => o.label === value);\n const n = e.key === \"ArrowRight\" ? enabled[(i + 1) % enabled.length] : enabled[(i - 1 + enabled.length) % enabled.length];\n onChange && onChange(n.label);\n };\n return (\n <div role=\"tablist\" data-size={size} aria-disabled={disabled || undefined} onKeyDown={onKey} style={{ display: fullWidth ? \"flex\" : \"inline-flex\", height: \"var(--control-h)\", padding: 2, gap: 2, background: \"var(--bg-inset)\", border: \"1px solid var(--border-control)\", borderRadius: \"var(--radius-md)\", boxSizing: \"border-box\", opacity: disabled ? .5 : 1, ...style }}>\n {opts.map((o) => {\n const is = value === o.label, off = disabled || o.disabled;\n return (\n <button key={o.label} type=\"button\" role=\"tab\" aria-selected={is} aria-label={iconOnly ? o.label : undefined} title={iconOnly ? o.label : undefined} disabled={off} tabIndex={is ? 0 : -1} data-ink={is || off ? undefined : \"\"}\n onClick={() => !off && onChange && onChange(o.label)}\n style={{ height: \"calc(var(--control-h) - 6px)\", padding: iconOnly ? \"0 calc(var(--control-px) - 4px)\" : \"0 calc(var(--control-px) - 2px)\", flex: fullWidth ? 1 : \"none\", justifyContent: \"center\", display: \"inline-flex\", alignItems: \"center\", gap: 6, borderRadius: \"var(--radius-sm)\", border: \"none\", cursor: off ? \"not-allowed\" : \"pointer\", background: is ? \"var(--bg-surface)\" : \"transparent\", boxShadow: is ? \"0 1px 2px rgba(0,0,0,.08)\" : \"none\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--text-heading)\" : \"var(--ink, var(--text-secondary))\", opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: \"nowrap\", transition: \"var(--t-hover)\" }}>\n {o.icon ? <Icon name={o.icon} size={16} style={tierIcon} /> : null}{iconOnly ? null : o.label}\n </button>\n );\n })}\n </div>\n );\n}\n","tabs/SegmentedControl.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Segmented control — inset track, raised active segment. Switches views of the same data, applies instantly.\n// options: string[] | { label, icon?, disabled? }[] · v-model by label · size sm 24 | md 32 | lg 36 — omit it to inherit the region's tier\n// iconOnly hides labels (label → aria-label/title) · disabled greys the control · fullWidth stretches segments · ← → keyboard.\nconst tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }\nconst props = defineProps({ options: { type: Array, default: () => [] }, modelValue: String, size: String, iconOnly: Boolean, disabled: Boolean, fullWidth: Boolean, style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst opts = computed(() => props.options.map(o => typeof o === 'string' ? { label: o } : o))\nconst enabled = computed(() => opts.value.filter(o => !o.disabled))\nconst pick = (o) => { if (!(props.disabled || o.disabled)) emit('update:modelValue', o.label) }\nfunction onKey(e) {\n if ((e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') || props.disabled || !enabled.value.length) return\n e.preventDefault()\n const i = enabled.value.findIndex(o => o.label === props.modelValue), n = enabled.value.length\n emit('update:modelValue', (e.key === 'ArrowRight' ? enabled.value[(i + 1) % n] : enabled.value[(i - 1 + n) % n]).label)\n}\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :aria-disabled=\"disabled || undefined\" @keydown=\"onKey\" :style=\"{ display: fullWidth ? 'flex' : 'inline-flex', height: 'var(--control-h)', padding: '2px', gap: '2px', background: 'var(--bg-inset)', border: '1px solid var(--border-control)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box', opacity: disabled ? .5 : 1, ...style }\">\n <button v-for=\"o in opts\" :key=\"o.label\" type=\"button\" role=\"tab\" :aria-selected=\"modelValue === o.label\" :aria-label=\"iconOnly ? o.label : undefined\" :title=\"iconOnly ? o.label : undefined\" :disabled=\"disabled || o.disabled\" :tabindex=\"modelValue === o.label ? 0 : -1\" :data-ink=\"modelValue === o.label || disabled || o.disabled ? undefined : ''\" @click=\"pick(o)\"\n :style=\"{ height: 'calc(var(--control-h) - 6px)', padding: iconOnly ? '0 calc(var(--control-px) - 4px)' : '0 calc(var(--control-px) - 2px)', flex: fullWidth ? 1 : 'none', justifyContent: 'center', display: 'inline-flex', alignItems: 'center', gap: '6px', borderRadius: 'var(--radius-sm)', border: 'none', cursor: (disabled || o.disabled) ? 'not-allowed' : 'pointer', background: modelValue === o.label ? 'var(--bg-surface)' : 'transparent', boxShadow: modelValue === o.label ? '0 1px 2px rgba(0,0,0,.08)' : 'none', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: modelValue === o.label ? 'var(--text-heading)' : 'var(--ink, var(--text-secondary))', opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: 'nowrap', transition: 'var(--t-hover)' }\">\n <Icon v-if=\"o.icon\" :name=\"o.icon\" :size=\"16\" :style=\"tierIcon\" /><template v-if=\"!iconOnly\">{{ o.label }}</template>\n </button>\n </div>\n</template>\n","tabs/Tabs.jsx":"import React from \"react\";\n\n/** Underline tabs on a hairline baseline (Import/Export). Active = blue text + 2px blue underline. The bar is the region's bar height (sm 32 · md 40 · lg 44) with the tier's control text; omit `size` to inherit. */\nexport function Tabs({ tabs = [], active, onChange, size, style }) {\n return (\n <div role=\"tablist\" data-size={size} style={{ display: \"flex\", gap: 4, borderBottom: \"1px solid var(--border-control)\", ...style }}>\n {tabs.map((t) => {\n const is = t === active;\n return (\n <button key={t} type=\"button\" role=\"tab\" aria-selected={is} data-ink={is ? undefined : \"\"} onClick={() => onChange && onChange(t)}\n style={{ background: \"transparent\", border: \"none\", cursor: \"pointer\", height: \"var(--bar-h)\", boxSizing: \"border-box\", padding: \"0 var(--control-px)\", marginBottom: -1, borderBottom: is ? \"2px solid var(--action-primary)\" : \"2px solid transparent\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--action-primary)\" : \"var(--ink, var(--text-body))\", whiteSpace: \"nowrap\" }}>\n {t}\n </button>\n );\n })}\n </div>\n );\n}\n\n/** Panel tabs (Node Info · Calc Rules · Output Fields · SQL Preview): a lighter underline tab bar at the region's bar height with regular-weight control text. Put the panel in an sm region for the compact 32px/12px look. */\nexport function PanelTabs({ tabs = [], active, onChange, size, style }) {\n return (\n <div role=\"tablist\" data-size={size} style={{ display: \"flex\", borderBottom: \"1px solid var(--border-subtle)\", ...style }}>\n {tabs.map((t) => {\n const is = t === active;\n return (\n <button key={t} type=\"button\" role=\"tab\" aria-selected={is} data-ink={is ? undefined : \"\"} onClick={() => onChange && onChange(t)}\n style={{ background: \"transparent\", border: \"none\", cursor: \"pointer\", height: \"var(--bar-h)\", boxSizing: \"border-box\", padding: \"0 var(--control-px)\", marginBottom: -1, borderBottom: is ? \"2px solid var(--action-primary)\" : \"2px solid transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--action-primary)\" : \"var(--ink, var(--text-secondary))\", whiteSpace: \"nowrap\" }}>\n {t}\n </button>\n );\n })}\n </div>\n );\n}\n\n/** Text pill filter tabs (For Review · To be Submitted · For Approval). Active = tinted pill. Pills are control-height (sm 24 · md 32 · lg 36); omit `size` to inherit. */\nexport function PillTabs({ tabs = [], active, onChange, size, style }) {\n return (\n <div role=\"tablist\" data-size={size} style={{ display: \"flex\", gap: \"var(--control-gap)\", ...style }}>\n {tabs.map((t) => {\n const is = t === active;\n return (\n <button key={t} type=\"button\" role=\"tab\" aria-selected={is} data-ink={is ? undefined : \"\"} data-wash={is ? undefined : \"\"} onClick={() => onChange && onChange(t)}\n style={{ background: is ? \"var(--bg-active)\" : \"var(--wash, transparent)\", border: \"none\", cursor: \"pointer\", height: \"var(--control-h)\", padding: \"0 var(--control-px)\", borderRadius: \"var(--radius-md)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--text-heading)\" : \"var(--ink, var(--text-secondary))\", whiteSpace: \"nowrap\" }}>\n {t}\n </button>\n );\n })}\n </div>\n );\n}\n","tabs/Tabs.vue":"<script setup>\n// Underline tabs on a hairline baseline (Import/Export). Active = blue text + 2px blue underline. The bar is the region's bar height (sm 32 · md 40 · lg 44) with the tier's control text; omit `size` to inherit.\ndefineProps({ tabs: { type: Array, default: () => [] }, active: String, size: String, style: Object })\nconst emit = defineEmits(['change'])\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :style=\"{ display: 'flex', gap: '4px', borderBottom: '1px solid var(--border-control)', ...style }\">\n <button v-for=\"t in tabs\" :key=\"t\" type=\"button\" role=\"tab\" :aria-selected=\"t === active\" :data-ink=\"t === active ? undefined : ''\" @click=\"emit('change', t)\"\n :style=\"{ background: 'transparent', border: 'none', cursor: 'pointer', height: 'var(--bar-h)', boxSizing: 'border-box', padding: '0 var(--control-px)', marginBottom: '-1px', borderBottom: t === active ? '2px solid var(--action-primary)' : '2px solid transparent', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: t === active ? 'var(--action-primary)' : 'var(--ink, var(--text-body))', whiteSpace: 'nowrap' }\">{{ t }}</button>\n </div>\n</template>\n","tabs/ViewToggle.jsx":"import React from \"react\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Two-state list/grid icon toggle (palette header). Two IconButtons at the region's tier; omit `size` to inherit. */\nexport function ViewToggle({ value = \"grid\", onChange, size, style }) {\n return (\n <div data-size={size} style={{ display: \"inline-flex\", gap: 2, ...style }}>\n <IconButton icon=\"list\" active={value === \"list\"} onClick={() => onChange && onChange(\"list\")} label=\"List view\" />\n <IconButton icon=\"layout-grid\" active={value === \"grid\"} onClick={() => onChange && onChange(\"grid\")} label=\"Grid view\" />\n </div>\n );\n}\n","tabs/ViewToggle.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\n// Two-state list/grid icon toggle (palette header). Two IconButtons at the region's tier; omit `size` to inherit.\ndefineProps({ modelValue: { type: String, default: 'grid' }, size: String, style: Object })\nconst emit = defineEmits(['update:modelValue'])\n</script>\n<template>\n <div :data-size=\"size\" :style=\"{ display: 'inline-flex', gap: '2px', ...style }\">\n <IconButton icon=\"list\" :active=\"modelValue === 'list'\" label=\"List view\" @click=\"emit('update:modelValue', 'list')\" />\n <IconButton icon=\"layout-grid\" :active=\"modelValue === 'grid'\" label=\"Grid view\" @click=\"emit('update:modelValue', 'grid')\" />\n </div>\n</template>\n","tabs/PanelTabs.vue":"<script setup>\n// Panel tabs (Node Info · Calc Rules · …): a lighter underline tab bar at the region's bar height with regular-weight control text. Put the panel in an sm region for the compact 32px/12px look.\ndefineProps({ tabs: { type: Array, default: () => [] }, active: String, size: String, style: Object })\nconst emit = defineEmits(['change'])\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :style=\"{ display: 'flex', borderBottom: '1px solid var(--border-subtle)', ...style }\">\n <button v-for=\"t in tabs\" :key=\"t\" type=\"button\" role=\"tab\" :aria-selected=\"t === active\" :data-ink=\"t === active ? undefined : ''\" @click=\"emit('change', t)\"\n :style=\"{ background: 'transparent', border: 'none', cursor: 'pointer', height: 'var(--bar-h)', boxSizing: 'border-box', padding: '0 var(--control-px)', marginBottom: '-1px', borderBottom: t === active ? '2px solid var(--action-primary)' : '2px solid transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: t === active ? 'var(--action-primary)' : 'var(--ink, var(--text-secondary))', whiteSpace: 'nowrap' }\">{{ t }}</button>\n </div>\n</template>\n","tabs/PillTabs.vue":"<script setup>\n// Text pill filter tabs. Active = tinted pill. Pills are control-height (sm 24 · md 32 · lg 36); omit `size` to inherit.\ndefineProps({ tabs: { type: Array, default: () => [] }, active: String, size: String, style: Object })\nconst emit = defineEmits(['change'])\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :style=\"{ display: 'flex', gap: 'var(--control-gap)', ...style }\">\n <button v-for=\"t in tabs\" :key=\"t\" type=\"button\" role=\"tab\" :aria-selected=\"t === active\" :data-ink=\"t === active ? undefined : ''\" :data-wash=\"t === active ? undefined : ''\" @click=\"emit('change', t)\"\n :style=\"{ background: t === active ? 'var(--bg-active)' : 'var(--wash, transparent)', border: 'none', cursor: 'pointer', height: 'var(--control-h)', padding: '0 var(--control-px)', borderRadius: 'var(--radius-md)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: t === active ? 'var(--text-heading)' : 'var(--ink, var(--text-secondary))', whiteSpace: 'nowrap' }\">{{ t }}</button>\n </div>\n</template>\n"});
1
+ window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"tabs/SegmentedControl.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/**\n * Segmented control — inset track, raised active segment. Switches views of the same data, applies instantly.\n * options: string[] | { label, icon?, disabled? }[] value/onChange by label\n * size sm 28 | md 32 | lg 40 — omit it to inherit the region's tier · iconOnly hides labels (label becomes aria-label / title)\n * disabled greys the whole control · fullWidth stretches segments evenly\n * Keyboard: ← → move between enabled segments.\n */\nconst tierIcon = { width: \"var(--control-icon)\", height: \"var(--control-icon)\" };\nexport function SegmentedControl({ options = [], value, onChange, size, iconOnly, disabled, fullWidth, style }) {\n const opts = options.map((o) => (typeof o === \"string\" ? { label: o } : o));\n const enabled = opts.filter((o) => !o.disabled);\n const onKey = (e) => {\n if (e.key !== \"ArrowLeft\" && e.key !== \"ArrowRight\" || disabled || !enabled.length) return;\n e.preventDefault();\n const i = enabled.findIndex((o) => o.label === value);\n const n = e.key === \"ArrowRight\" ? enabled[(i + 1) % enabled.length] : enabled[(i - 1 + enabled.length) % enabled.length];\n onChange && onChange(n.label);\n };\n return (\n <div role=\"tablist\" data-size={size} aria-disabled={disabled || undefined} onKeyDown={onKey} style={{ display: fullWidth ? \"flex\" : \"inline-flex\", height: \"var(--control-h)\", padding: 2, gap: 2, background: \"var(--bg-inset)\", border: \"1px solid var(--border-control)\", borderRadius: \"var(--radius-md)\", boxSizing: \"border-box\", opacity: disabled ? .5 : 1, ...style }}>\n {opts.map((o) => {\n const is = value === o.label, off = disabled || o.disabled;\n return (\n <button key={o.label} type=\"button\" role=\"tab\" aria-selected={is} aria-label={iconOnly ? o.label : undefined} title={iconOnly ? o.label : undefined} disabled={off} tabIndex={is ? 0 : -1} data-ink={is || off ? undefined : \"\"}\n onClick={() => !off && onChange && onChange(o.label)}\n style={{ height: \"calc(var(--control-h) - 6px)\", padding: iconOnly ? \"0 calc(var(--control-px) - 4px)\" : \"0 calc(var(--control-px) - 2px)\", flex: fullWidth ? 1 : \"none\", justifyContent: \"center\", display: \"inline-flex\", alignItems: \"center\", gap: 6, borderRadius: \"var(--radius-sm)\", border: \"none\", cursor: off ? \"not-allowed\" : \"pointer\", background: is ? \"var(--bg-surface)\" : \"transparent\", boxShadow: is ? \"0 1px 2px rgba(0,0,0,.08)\" : \"none\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--text-heading)\" : \"var(--ink, var(--text-secondary))\", opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: \"nowrap\", transition: \"var(--t-hover)\" }}>\n {o.icon ? <Icon name={o.icon} size={16} style={tierIcon} /> : null}{iconOnly ? null : o.label}\n </button>\n );\n })}\n </div>\n );\n}\n","tabs/SegmentedControl.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Segmented control — inset track, raised active segment. Switches views of the same data, applies instantly.\n// options: string[] | { label, icon?, disabled? }[] · v-model by label · size sm 28 | md 32 | lg 40 — omit it to inherit the region's tier\n// iconOnly hides labels (label → aria-label/title) · disabled greys the control · fullWidth stretches segments · ← → keyboard.\nconst tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }\nconst props = defineProps({ options: { type: Array, default: () => [] }, modelValue: String, size: String, iconOnly: Boolean, disabled: Boolean, fullWidth: Boolean, style: Object })\nconst emit = defineEmits(['update:modelValue'])\nconst opts = computed(() => props.options.map(o => typeof o === 'string' ? { label: o } : o))\nconst enabled = computed(() => opts.value.filter(o => !o.disabled))\nconst pick = (o) => { if (!(props.disabled || o.disabled)) emit('update:modelValue', o.label) }\nfunction onKey(e) {\n if ((e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') || props.disabled || !enabled.value.length) return\n e.preventDefault()\n const i = enabled.value.findIndex(o => o.label === props.modelValue), n = enabled.value.length\n emit('update:modelValue', (e.key === 'ArrowRight' ? enabled.value[(i + 1) % n] : enabled.value[(i - 1 + n) % n]).label)\n}\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :aria-disabled=\"disabled || undefined\" @keydown=\"onKey\" :style=\"{ display: fullWidth ? 'flex' : 'inline-flex', height: 'var(--control-h)', padding: '2px', gap: '2px', background: 'var(--bg-inset)', border: '1px solid var(--border-control)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box', opacity: disabled ? .5 : 1, ...style }\">\n <button v-for=\"o in opts\" :key=\"o.label\" type=\"button\" role=\"tab\" :aria-selected=\"modelValue === o.label\" :aria-label=\"iconOnly ? o.label : undefined\" :title=\"iconOnly ? o.label : undefined\" :disabled=\"disabled || o.disabled\" :tabindex=\"modelValue === o.label ? 0 : -1\" :data-ink=\"modelValue === o.label || disabled || o.disabled ? undefined : ''\" @click=\"pick(o)\"\n :style=\"{ height: 'calc(var(--control-h) - 6px)', padding: iconOnly ? '0 calc(var(--control-px) - 4px)' : '0 calc(var(--control-px) - 2px)', flex: fullWidth ? 1 : 'none', justifyContent: 'center', display: 'inline-flex', alignItems: 'center', gap: '6px', borderRadius: 'var(--radius-sm)', border: 'none', cursor: (disabled || o.disabled) ? 'not-allowed' : 'pointer', background: modelValue === o.label ? 'var(--bg-surface)' : 'transparent', boxShadow: modelValue === o.label ? '0 1px 2px rgba(0,0,0,.08)' : 'none', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: modelValue === o.label ? 'var(--text-heading)' : 'var(--ink, var(--text-secondary))', opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: 'nowrap', transition: 'var(--t-hover)' }\">\n <Icon v-if=\"o.icon\" :name=\"o.icon\" :size=\"16\" :style=\"tierIcon\" /><template v-if=\"!iconOnly\">{{ o.label }}</template>\n </button>\n </div>\n</template>\n","tabs/Tabs.jsx":"import React from \"react\";\n\n/** Underline tabs on a hairline baseline (Import/Export). Active = blue text + 2px blue underline. The bar is the region's bar height (sm 32 · md 40 · lg 44) with the tier's control text; omit `size` to inherit. */\nexport function Tabs({ tabs = [], active, onChange, size, style }) {\n return (\n <div role=\"tablist\" data-size={size} style={{ display: \"flex\", gap: 4, borderBottom: \"1px solid var(--border-control)\", ...style }}>\n {tabs.map((t) => {\n const is = t === active;\n return (\n <button key={t} type=\"button\" role=\"tab\" aria-selected={is} data-ink={is ? undefined : \"\"} onClick={() => onChange && onChange(t)}\n style={{ background: \"transparent\", border: \"none\", cursor: \"pointer\", height: \"var(--bar-h)\", boxSizing: \"border-box\", padding: \"0 var(--control-px)\", marginBottom: -1, borderBottom: is ? \"2px solid var(--action-primary)\" : \"2px solid transparent\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--action-primary)\" : \"var(--ink, var(--text-body))\", whiteSpace: \"nowrap\" }}>\n {t}\n </button>\n );\n })}\n </div>\n );\n}\n\n/** Panel tabs (Node Info · Calc Rules · Output Fields · SQL Preview): a lighter underline tab bar at the region's bar height with regular-weight control text. Put the panel in an sm region for the compact 32px/12px look. */\nexport function PanelTabs({ tabs = [], active, onChange, size, style }) {\n return (\n <div role=\"tablist\" data-size={size} style={{ display: \"flex\", borderBottom: \"1px solid var(--border-subtle)\", ...style }}>\n {tabs.map((t) => {\n const is = t === active;\n return (\n <button key={t} type=\"button\" role=\"tab\" aria-selected={is} data-ink={is ? undefined : \"\"} onClick={() => onChange && onChange(t)}\n style={{ background: \"transparent\", border: \"none\", cursor: \"pointer\", height: \"var(--bar-h)\", boxSizing: \"border-box\", padding: \"0 var(--control-px)\", marginBottom: -1, borderBottom: is ? \"2px solid var(--action-primary)\" : \"2px solid transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--action-primary)\" : \"var(--ink, var(--text-secondary))\", whiteSpace: \"nowrap\" }}>\n {t}\n </button>\n );\n })}\n </div>\n );\n}\n\n/** Text pill filter tabs (For Review · To be Submitted · For Approval). Active = tinted pill. Pills are control-height (sm 28 · md 32 · lg 40); omit `size` to inherit. */\nexport function PillTabs({ tabs = [], active, onChange, size, style }) {\n return (\n <div role=\"tablist\" data-size={size} style={{ display: \"flex\", gap: \"var(--control-gap)\", ...style }}>\n {tabs.map((t) => {\n const is = t === active;\n return (\n <button key={t} type=\"button\" role=\"tab\" aria-selected={is} data-ink={is ? undefined : \"\"} data-wash={is ? undefined : \"\"} onClick={() => onChange && onChange(t)}\n style={{ background: is ? \"var(--bg-active)\" : \"var(--wash, transparent)\", border: \"none\", cursor: \"pointer\", height: \"var(--control-h)\", padding: \"0 var(--control-px)\", borderRadius: \"var(--radius-md)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: is ? \"var(--text-heading)\" : \"var(--ink, var(--text-secondary))\", whiteSpace: \"nowrap\" }}>\n {t}\n </button>\n );\n })}\n </div>\n );\n}\n","tabs/Tabs.vue":"<script setup>\n// Underline tabs on a hairline baseline (Import/Export). Active = blue text + 2px blue underline. The bar is the region's bar height (sm 32 · md 40 · lg 44) with the tier's control text; omit `size` to inherit.\ndefineProps({ tabs: { type: Array, default: () => [] }, active: String, size: String, style: Object })\nconst emit = defineEmits(['change'])\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :style=\"{ display: 'flex', gap: '4px', borderBottom: '1px solid var(--border-control)', ...style }\">\n <button v-for=\"t in tabs\" :key=\"t\" type=\"button\" role=\"tab\" :aria-selected=\"t === active\" :data-ink=\"t === active ? undefined : ''\" @click=\"emit('change', t)\"\n :style=\"{ background: 'transparent', border: 'none', cursor: 'pointer', height: 'var(--bar-h)', boxSizing: 'border-box', padding: '0 var(--control-px)', marginBottom: '-1px', borderBottom: t === active ? '2px solid var(--action-primary)' : '2px solid transparent', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: t === active ? 'var(--action-primary)' : 'var(--ink, var(--text-body))', whiteSpace: 'nowrap' }\">{{ t }}</button>\n </div>\n</template>\n","tabs/ViewToggle.jsx":"import React from \"react\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Two-state list/grid icon toggle (palette header). Two IconButtons at the region's tier; omit `size` to inherit. */\nexport function ViewToggle({ value = \"grid\", onChange, size, style }) {\n return (\n <div data-size={size} style={{ display: \"inline-flex\", gap: 2, ...style }}>\n <IconButton icon=\"list\" active={value === \"list\"} onClick={() => onChange && onChange(\"list\")} label=\"List view\" />\n <IconButton icon=\"layout-grid\" active={value === \"grid\"} onClick={() => onChange && onChange(\"grid\")} label=\"Grid view\" />\n </div>\n );\n}\n","tabs/ViewToggle.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\n// Two-state list/grid icon toggle (palette header). Two IconButtons at the region's tier; omit `size` to inherit.\ndefineProps({ modelValue: { type: String, default: 'grid' }, size: String, style: Object })\nconst emit = defineEmits(['update:modelValue'])\n</script>\n<template>\n <div :data-size=\"size\" :style=\"{ display: 'inline-flex', gap: '2px', ...style }\">\n <IconButton icon=\"list\" :active=\"modelValue === 'list'\" label=\"List view\" @click=\"emit('update:modelValue', 'list')\" />\n <IconButton icon=\"layout-grid\" :active=\"modelValue === 'grid'\" label=\"Grid view\" @click=\"emit('update:modelValue', 'grid')\" />\n </div>\n</template>\n","tabs/PanelTabs.vue":"<script setup>\n// Panel tabs (Node Info · Calc Rules · …): a lighter underline tab bar at the region's bar height with regular-weight control text. Put the panel in an sm region for the compact 32px/12px look.\ndefineProps({ tabs: { type: Array, default: () => [] }, active: String, size: String, style: Object })\nconst emit = defineEmits(['change'])\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :style=\"{ display: 'flex', borderBottom: '1px solid var(--border-subtle)', ...style }\">\n <button v-for=\"t in tabs\" :key=\"t\" type=\"button\" role=\"tab\" :aria-selected=\"t === active\" :data-ink=\"t === active ? undefined : ''\" @click=\"emit('change', t)\"\n :style=\"{ background: 'transparent', border: 'none', cursor: 'pointer', height: 'var(--bar-h)', boxSizing: 'border-box', padding: '0 var(--control-px)', marginBottom: '-1px', borderBottom: t === active ? '2px solid var(--action-primary)' : '2px solid transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: t === active ? 'var(--action-primary)' : 'var(--ink, var(--text-secondary))', whiteSpace: 'nowrap' }\">{{ t }}</button>\n </div>\n</template>\n","tabs/PillTabs.vue":"<script setup>\n// Text pill filter tabs. Active = tinted pill. Pills are control-height (sm 28 · md 32 · lg 40); omit `size` to inherit.\ndefineProps({ tabs: { type: Array, default: () => [] }, active: String, size: String, style: Object })\nconst emit = defineEmits(['change'])\n</script>\n<template>\n <div role=\"tablist\" :data-size=\"size\" :style=\"{ display: 'flex', gap: 'var(--control-gap)', ...style }\">\n <button v-for=\"t in tabs\" :key=\"t\" type=\"button\" role=\"tab\" :aria-selected=\"t === active\" :data-ink=\"t === active ? undefined : ''\" :data-wash=\"t === active ? undefined : ''\" @click=\"emit('change', t)\"\n :style=\"{ background: t === active ? 'var(--bg-active)' : 'var(--wash, transparent)', border: 'none', cursor: 'pointer', height: 'var(--control-h)', padding: '0 var(--control-px)', borderRadius: 'var(--radius-md)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: t === active ? 'var(--text-heading)' : 'var(--ink, var(--text-secondary))', whiteSpace: 'nowrap' }\">{{ t }}</button>\n </div>\n</template>\n"});
@@ -89,7 +89,7 @@ function mount(rootSelector, files){
89
89
  ['React','Vue 3'].forEach(function(fw){
90
90
  var fb = document.createElement('button');
91
91
  fb.type = 'button'; fb.textContent = fw;
92
- fb.style.cssText = 'font-size:11px;font-family:var(--font-sans,sans-serif);padding:4px 9px;border-radius:6px;border:1px solid var(--border-control,#e5e5e5);background:var(--bg-surface,#fff);color:var(--text-secondary,#737373);cursor:pointer;';
92
+ fb.style.cssText = 'font-size:12px;font-family:var(--font-sans,sans-serif);padding:4px 9px;border-radius:6px;border:1px solid var(--border-control,#e5e5e5);background:var(--bg-surface,#fff);color:var(--text-secondary,#737373);cursor:pointer;';
93
93
  fb.onclick = function(){
94
94
  framework = fw === 'Vue 3' ? 'vue' : 'react';
95
95
  Array.prototype.forEach.call(fwBar.children, function(el){
@@ -119,11 +119,11 @@ function mount(rootSelector, files){
119
119
  var box = document.createElement('div');
120
120
  box.style.position = 'relative';
121
121
  var pre = document.createElement('pre');
122
- pre.style.cssText = 'margin:0;padding:14px 44px 14px 16px;overflow:auto;max-height:380px;font-family:var(--font-mono,monospace);font-size:11px;line-height:16.5px;color:var(--text-body,#525252);white-space:pre;';
122
+ pre.style.cssText = 'margin:0;padding:14px 44px 14px 16px;overflow:auto;max-height:380px;font-family:var(--font-mono,monospace);font-size:12px;line-height:16.5px;color:var(--text-body,#525252);white-space:pre;';
123
123
  var copyBtn = document.createElement('button');
124
124
  copyBtn.type = 'button';
125
125
  copyBtn.innerHTML = copySvg() + '<span style="margin-left:4px">Copy</span>';
126
- copyBtn.style.cssText = 'position:absolute;top:8px;right:10px;display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:6px;background:var(--bg-surface,#fff);color:var(--text-secondary,#737373);font-size:11px;font-family:var(--font-sans,sans-serif);cursor:pointer;border:1px solid var(--border-control,#e5e5e5);';
126
+ copyBtn.style.cssText = 'position:absolute;top:8px;right:10px;display:inline-flex;align-items:center;height:24px;padding:0 8px;border-radius:6px;background:var(--bg-surface,#fff);color:var(--text-secondary,#737373);font-size:12px;font-family:var(--font-sans,sans-serif);cursor:pointer;border:1px solid var(--border-control,#e5e5e5);';
127
127
  function setText(text){
128
128
  pre.textContent = text;
129
129
  copyBtn.onclick = function(){
@@ -137,3 +137,10 @@ export { default as PillTabs } from "../tabs/PillTabs.vue";
137
137
  export { default as SegmentedControl } from "../tabs/SegmentedControl.vue";
138
138
  export { default as Tabs } from "../tabs/Tabs.vue";
139
139
  export { default as ViewToggle } from "../tabs/ViewToggle.vue";
140
+
141
+ /* shared values */
142
+ export { KEYBRICKS_NAV } from "../navigation/nav-model.js";
143
+ export { applyStoredTheme } from "../navigation/theme-store.js";
144
+ export { PIPELINE_CATALOG } from "../pipeline/pipeline-catalog.js";
145
+ export { VUE_ICON_PATHS } from "../icons/icon-paths.js";
146
+ export { FEATHER_PATHS } from "../icons/feather-paths.js";
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  /** Tinted bulk-action chip (comparison-table actions). Tier height and text (32px/14px at md), r8, weight 500. */
3
3
  export interface ActionChipProps {
4
- /** Size tier: sm 24 · md 32 · lg 36. Omit to inherit the enclosing region's tier (the default). */
4
+ /** Size tier: sm 28 · md 32 · lg 40. Omit to inherit the enclosing region's tier (the default). */
5
5
  size?: "sm" | "md" | "lg";
6
6
  /** success #ecfdf5/#007a55 · warning #fff7ed/#ca3500 · neutral #f5f5f5/#525252 · info · danger */
7
7
  tone?: "success" | "warning" | "neutral" | "info" | "danger";
@@ -5,7 +5,7 @@ import * as React from "react";
5
5
  export interface ButtonProps {
6
6
  /** primary (blue #155dfc) | dark (#171717, e.g. "AI Assistant") | outline | ghost | primary-soft (blue tint toggle, e.g. "Data Preview") | danger (solid red, destructive) | danger-soft (red tint, e.g. "Remove") | text (no background/border ever — underlines on hover, e.g. "View all", "Cancel") */
7
7
  variant?: "primary" | "dark" | "outline" | "ghost" | "primary-soft" | "danger" | "danger-soft" | "text";
8
- /** Size tier: sm 24px/12px text · md 32px/14px · lg 36px/14px. Omit to inherit the tier of the enclosing region
8
+ /** Size tier: sm 28px/12px text (13px in CJK) · md 32px/14px · lg 40px/14px. Omit to inherit the tier of the enclosing region
9
9
  * (toolbar, form, table, dialog — or the app root), which is the default and the normal case: controls that share
10
10
  * a row must share a tier. Set it only on a control that stands alone. */
11
11
  size?: "sm" | "md" | "lg";
@@ -35,7 +35,7 @@ function Spinner({ size, color }) {
35
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 }} />;
36
36
  }
37
37
 
38
- /** Keybricks button. variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text; size: sm 24 | md 32 | lg 36 — 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. */
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
39
  export function Button({ variant = "primary", size, icon, iconSize, iconPosition = "left", medium, disabled, loading, fullWidth, iconOnly, label, pressed, focused, children, style, onClick, ...rest }) {
40
40
  const [hover, setHover] = React.useState(false);
41
41
  const [press, setPress] = React.useState(false);
@@ -22,7 +22,7 @@ const hovers = { primary: 'var(--action-primary-hover)', dark: 'var(--action-dar
22
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)' }
23
23
  const colorHoverVariants = { text: 'var(--text-heading)' }
24
24
 
25
- // Button — variant: primary | dark | outline | ghost | primary-soft | danger | danger-soft | text; size: sm 24 | md 32 | lg 36 — omit it to inherit the region's tier (the default).
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).
26
26
  const props = defineProps({
27
27
  variant: { type: String, default: 'primary' }, size: String,
28
28
  icon: String, iconSize: Number, iconPosition: { type: String, default: 'left' },
@@ -3,7 +3,7 @@ import * as React from "react";
3
3
  export interface IconButtonProps {
4
4
  /** Icon glyph name */
5
5
  icon: string;
6
- /** Size tier: a square of sm 24 · md 32 · lg 36 with a 14/16/16 icon. Omit to inherit the enclosing region's tier
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
7
  * (the default). "xs" is a deprecated alias of "sm". */
8
8
  size?: "xs" | "sm" | "md" | "lg";
9
9
  /** Pressed/selected state (#f5f5f5 wash) */
@@ -5,7 +5,7 @@ import { Icon } from "../icons/Icon.jsx";
5
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
6
  const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
7
7
 
8
- /** Square ghost icon button (toolbar/back/bell/view toggles). size: sm 24 | md 32 | lg 36 — 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. */
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
9
  export function IconButton({ icon, size, active, muted, danger, disabled, loading, label, pressed, focused, style, onClick, ...rest }) {
10
10
  const [hover, setHover] = React.useState(false);
11
11
  const [press, setPress] = React.useState(false);
@@ -14,9 +14,11 @@ export function IconButton({ icon, size, active, muted, danger, disabled, loadin
14
14
  const busy = disabled || loading;
15
15
  const isPressed = (press || pressed) && !busy;
16
16
  const color = danger ? "var(--danger)" : muted ? "var(--text-muted)" : "var(--text-body)";
17
- 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)")
18
20
  : active ? "var(--bg-active)"
19
- : hover && !busy ? (danger ? "var(--danger-bg)" : "var(--bg-hover)") : "transparent";
21
+ : hover && !busy ? (danger ? "var(--danger-bg)" : "var(--overlay-hover)") : "transparent";
20
22
  return (
21
23
  <button type="button" data-press="own" data-size={tier} aria-label={label} disabled={busy} onClick={onClick}
22
24
  onMouseEnter={() => setHover(true)} onMouseLeave={() => { setHover(false); setPress(false); }}
@@ -4,7 +4,7 @@ import Icon from '../icons/Icon.vue'
4
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
5
  // the button; omit it to inherit — the default. "xs" is kept as an alias of "sm" for 1.x callers.
6
6
  const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
7
- // Square ghost icon button (toolbar/back/bell/view toggles). size: sm 24 | md 32 | lg 36 — omit to inherit the region's tier.
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
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 })
9
9
  defineEmits(['click'])
10
10
  const hover = ref(false), press = ref(false), focus = ref(false)
@@ -12,7 +12,8 @@ const tier = computed(() => props.size === 'xs' ? 'sm' : props.size)
12
12
  const busy = computed(() => props.disabled || props.loading)
13
13
  const isPressed = computed(() => (press.value || props.pressed) && !busy.value)
14
14
  const color = computed(() => props.danger ? 'var(--danger)' : props.muted ? 'var(--text-muted)' : 'var(--text-body)')
15
- 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'))
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'))
16
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 }))
17
18
  </script>
18
19
  <template>
@@ -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: "var(--text-2xs)", 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) => {
@@ -56,7 +56,7 @@ 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:var(--text-2xs);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
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"
@@ -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,7 +92,7 @@ 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: "var(--text-2xs)", 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) => (
@@ -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: "var(--text-2xs)", 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: "var(--text-2xs)", 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:var(--text-2xs);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:var(--text-2xs);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:var(--text-2xs);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:var(--text-2xs);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">
@@ -17,9 +17,9 @@ export function BarList({ rows = [], max, width = 360, valueFormat = fmtNum, sho
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
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-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}
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)" }} />
@@ -13,9 +13,9 @@ const hover = ref(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
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-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>
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)'}"/>
@@ -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:var(--text-2xs);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)">
@@ -50,7 +50,7 @@ function move(e){
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>
@@ -21,14 +21,14 @@ export function BulletChart({ rows = [], width = 360, valueFormat = fmtNum, tone
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
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-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}
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)" }} />
@@ -16,14 +16,14 @@ function geom(r){
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
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-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>
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)'}"/>
@@ -38,7 +38,7 @@ function move(e){
38
38
  <template v-if="axes">
39
39
  <g v-for="t in g.ticks" :key="t">
40
40
  <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"/>
41
- <text :x="g.pad.l-8" :y="g.sy(t)+3" text-anchor="end" style="font-family:var(--font-mono);font-size:var(--text-2xs);fill:var(--chart-label)">{{ valueFormat(t) }}</text>
41
+ <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>
42
42
  </g>
43
43
  </template>
44
44
  <rect v-if="hover!=null" :x="g.pad.l+g.slot*hover" :y="g.pad.t" :width="g.slot" :height="height-g.pad.t-g.axisB" fill="var(--bg-hover)"/>
@@ -53,8 +53,8 @@ function move(e){
53
53
  </svg>
54
54
  <div v-if="hover!=null && interactive" :style="{position:'absolute',left:g.cx(hover)+'px',top:(height/2-20)+'px',transform:g.cx(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'}">
55
55
  <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px">{{ candles[hover].t }}</div>
56
- <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">O {{ valueFormat(candles[hover].o) }} · H {{ valueFormat(candles[hover].h) }} · L {{ valueFormat(candles[hover].l) }} · C {{ valueFormat(candles[hover].c) }}</div>
57
- <div :style="{fontFamily:'var(--font-mono)',fontSize:'var(--text-2xs)',color:candles[hover].c>=candles[hover].o?'var(--chart-pos)':'var(--chart-neg)'}">
56
+ <div style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading)">O {{ valueFormat(candles[hover].o) }} · H {{ valueFormat(candles[hover].h) }} · L {{ valueFormat(candles[hover].l) }} · C {{ valueFormat(candles[hover].c) }}</div>
57
+ <div :style="{fontFamily: 'var(--font-sans)', fontVariantNumeric: 'tabular-nums',fontSize:'var(--text-2xs-mono)',color:candles[hover].c>=candles[hover].o?'var(--chart-pos)':'var(--chart-neg)'}">
58
58
  {{ (candles[hover].c-candles[hover].o>=0?'+':'')+(((candles[hover].c-candles[hover].o)/(candles[hover].o||1))*100).toFixed(2) }}%<span v-if="candles[hover].v!=null" style="color:var(--text-muted)"> · vol {{ fmtNum(candles[hover].v) }}</span>
59
59
  </div>
60
60
  </div>
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
2
  import { fmtNum, seriesColor } from "./chart-geometry.js";
3
3
 
4
- const sans = "var(--font-sans)", mono = "var(--font-mono)";
4
+ /* Chart numbers use the UI font with tabular figures; the monospace font is reserved for code. */
5
+ const sans = "var(--font-sans)", num = { fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums" };
5
6
 
6
7
  /** Legend row. items: [{label,color}] — with onToggle, clicking mutes a series. */
7
8
  export function ChartLegend({ items = [], hidden = [], onToggle, align = "left", style }) {
@@ -31,7 +32,7 @@ export function ChartTooltip({ x, y, title, rows = [], flip }) {
31
32
  <div key={i} style={{ display: "flex", alignItems: "center", gap: 6, padding: "1px 0" }}>
32
33
  {r.color ? <span style={{ width: 7, height: 7, borderRadius: 2, background: r.color, flexShrink: 0 }} /> : null}
33
34
  <span style={{ fontFamily: sans, fontSize: "var(--text-xs)", color: "var(--text-secondary)", whiteSpace: "nowrap" }}>{r.label}</span>
34
- <span style={{ fontFamily: mono, fontSize: "var(--text-xs)", color: "var(--text-heading)", marginLeft: "auto", paddingLeft: 10 }}>{r.value}</span>
35
+ <span style={{ ...num, fontSize: "var(--text-xs)", color: "var(--text-heading)", marginLeft: "auto", paddingLeft: 10 }}>{r.value}</span>
35
36
  </div>
36
37
  ))}
37
38
  </div>
@@ -45,7 +46,7 @@ export function PlotGrid({ w, h, pad, ticks, sy, xLabels = [], sxLabel, showAxis
45
46
  {ticks.map((t) => (
46
47
  <g key={t}>
47
48
  <line x1={pad.l} x2={w - pad.r} y1={sy(t)} y2={sy(t)} stroke={t === 0 ? "var(--chart-axis)" : "var(--chart-grid)"} strokeWidth="1" />
48
- <text x={pad.l - 8} y={sy(t) + 3} textAnchor="end" style={{ fontFamily: mono, fontSize: "var(--text-2xs)", fill: "var(--chart-label)" }}>{yFormat(t)}</text>
49
+ <text x={pad.l - 8} y={sy(t) + 3} textAnchor="end" style={{ ...num, fontSize: "var(--text-2xs-mono)", fill: "var(--chart-label)" }}>{yFormat(t)}</text>
49
50
  </g>
50
51
  ))}
51
52
  {showAxisLine ? <line x1={pad.l} x2={pad.l} y1={pad.t} y2={h - pad.b} stroke="var(--chart-axis)" strokeWidth="1" /> : null}
@@ -10,7 +10,7 @@ defineProps({ x: [Number, String], y: [Number, String], title: [String, Number],
10
10
  <div v-for="(r, i) in rows" :key="i" style="display:flex;align-items:center;gap:6px;padding:1px 0">
11
11
  <span v-if="r.color" :style="{ width: '7px', height: '7px', borderRadius: '2px', background: r.color, flexShrink: 0 }"></span>
12
12
  <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary);white-space:nowrap">{{ r.label }}</span>
13
- <span style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading);margin-left:auto;padding-left:10px">{{ r.value }}</span>
13
+ <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">{{ r.value }}</span>
14
14
  </div>
15
15
  </div>
16
16
  </template>
@@ -44,7 +44,7 @@ export function ChordChart({ labels = [], matrix = [], size = 380, thickness = 1
44
44
  style={{ cursor: interactive ? "default" : undefined }}>
45
45
  <path d={arcPath(cx, cy, rO, rI, a.a0, a.a1)} fill={colorOf(a.i)} opacity={hover == null || on ? 1 : 0.4} style={{ transition: "opacity var(--dur-fast) var(--ease-out)" }} />
46
46
  <text x={lx} y={ly + 3} textAnchor={anchor} style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-2xs)", fill: on ? "var(--text-heading)" : "var(--text-secondary)" }}>{labels[a.i]}</text>
47
- <text x={lx} y={ly + 14} textAnchor={anchor} style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", fill: "var(--chart-label)" }}>{valueFormat(a.value)}</text>
47
+ <text x={lx} y={ly + 14} textAnchor={anchor} style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-2xs-mono)", fill: "var(--chart-label)" }}>{valueFormat(a.value)}</text>
48
48
  </g>
49
49
  );
50
50
  })}
@@ -43,12 +43,12 @@ const tip = computed(()=>{
43
43
  <g v-for="a in g.arcs" :key="a.i" @mouseenter="interactive && (hover={kind:'node',i:a.i})" @mouseleave="interactive && (hover=null)">
44
44
  <path :d="a.d" :fill="a.color" :opacity="hover==null||(hover.kind==='node'&&hover.i===a.i)?1:0.4" style="transition:opacity var(--dur-fast) var(--ease-out)"/>
45
45
  <text :x="a.lx" :y="a.ly+3" :text-anchor="a.anchor" style="font-family:var(--font-sans);font-size:var(--text-2xs);fill:var(--text-secondary)">{{ labels[a.i] }}</text>
46
- <text :x="a.lx" :y="a.ly+14" :text-anchor="a.anchor" style="font-family:var(--font-mono);font-size:var(--text-2xs);fill:var(--chart-label)">{{ valueFormat(a.value) }}</text>
46
+ <text :x="a.lx" :y="a.ly+14" :text-anchor="a.anchor" style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-2xs-mono);fill:var(--chart-label)">{{ valueFormat(a.value) }}</text>
47
47
  </g>
48
48
  </svg>
49
49
  <div v-if="tip && interactive" :style="{position:'absolute',left:tip.x+'px',top:tip.y+'px',transform:tip.x>size*0.55?'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'}">
50
50
  <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px">{{ tip.title }}</div>
51
- <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">{{ tip.value }}</div>
51
+ <div style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading)">{{ tip.value }}</div>
52
52
  </div>
53
53
  </div>
54
54
  </template>
@@ -62,7 +62,7 @@ export function DonutChart({ segments = [], size = 180, thickness = 22, pie = fa
62
62
  style={{ display: "inline-flex", alignItems: "center", gap: 7, cursor: interactive ? "default" : undefined, opacity: hover == null || hover === i ? 1 : 0.45, transition: "var(--t-hover)" }}>
63
63
  <span style={{ width: 8, height: 8, borderRadius: 2, background: colorOf(s, i), flexShrink: 0 }} />
64
64
  <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", color: "var(--text-secondary)" }}>{s.label}</span>
65
- <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-heading)", marginLeft: 6 }}>{((s.value / total) * 100).toFixed(0)}%</span>
65
+ <span style={{ fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-xs)", color: "var(--text-heading)", marginLeft: 6 }}>{((s.value / total) * 100).toFixed(0)}%</span>
66
66
  </span>
67
67
  ))}
68
68
  </div>
@@ -45,7 +45,7 @@ const tipStyle = computed(() => {
45
45
  </div>
46
46
  <div v-if="hover!=null && interactive" :style="tipStyle" >
47
47
  <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px;white-space:nowrap">{{ segments[hover].label }}</div>
48
- <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading);white-space:nowrap">{{ valueFormat(segments[hover].value) }} · {{ ((segments[hover].value/total)*100).toFixed(1) }}%</div>
48
+ <div style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading);white-space:nowrap">{{ valueFormat(segments[hover].value) }} · {{ ((segments[hover].value/total)*100).toFixed(1) }}%</div>
49
49
  </div>
50
50
  </div>
51
51
  <div v-if="legend" style="display:flex;flex-direction:column;gap:6px">
@@ -53,7 +53,7 @@ const tipStyle = computed(() => {
53
53
  :style="{display:'inline-flex',alignItems:'center',gap:'7px',opacity:hover==null||hover===a.i?1:.45,transition:'var(--t-hover)'}">
54
54
  <span :style="{width:'8px',height:'8px',borderRadius:'2px',background:a.color,flexShrink:0}"/>
55
55
  <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)">{{ a.s.label }}</span>
56
- <span style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading);margin-left:6px">{{ ((a.s.value/total)*100).toFixed(0) }}%</span>
56
+ <span style="font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-heading);margin-left:6px">{{ ((a.s.value/total)*100).toFixed(0) }}%</span>
57
57
  </span>
58
58
  </div>
59
59
  </div>