@keyfunc/keybricks-ds 1.0.4 → 2.1.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 (357) hide show
  1. package/DEPLOY.md +4 -4
  2. package/DESIGN.md +154 -53
  3. package/INTEGRATION.md +174 -35
  4. package/SKILL.md +6 -4
  5. package/_adherence.oxlintrc.json +164 -68
  6. package/_scripts/build-index.mjs +9 -0
  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 +8 -3
  12. package/components/_cards/activity.card.html +2 -2
  13. package/components/_cards/alert.card.html +2 -2
  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 +2 -2
  17. package/components/_cards/button-1-base.card.html +2 -2
  18. package/components/_cards/button-2-states.card.html +14 -14
  19. package/components/_cards/button-3-sizes-icons.card.html +1 -1
  20. package/components/_cards/button-4-group.card.html +5 -5
  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 +2 -2
  24. package/components/_cards/charts-10-gauge.card.html +4 -4
  25. package/components/_cards/charts-11-funnel.card.html +2 -2
  26. package/components/_cards/charts-12-treemap.card.html +2 -2
  27. package/components/_cards/charts-14-radar.card.html +1 -1
  28. package/components/_cards/charts-15-distribution.card.html +1 -1
  29. package/components/_cards/charts-16-pareto.card.html +2 -2
  30. package/components/_cards/charts-17-candlestick.card.html +2 -2
  31. package/components/_cards/charts-18-sankey.card.html +2 -2
  32. package/components/_cards/charts-19-chord.card.html +2 -2
  33. package/components/_cards/charts-2-line.card.html +2 -2
  34. package/components/_cards/charts-20-sunburst.card.html +2 -2
  35. package/components/_cards/charts-21-worldmap.card.html +2 -2
  36. package/components/_cards/charts-22-gantt.card.html +2 -2
  37. package/components/_cards/charts-23-bar3d.card.html +2 -2
  38. package/components/_cards/charts-24-pie3d.card.html +2 -2
  39. package/components/_cards/charts-25-surface3d.card.html +2 -2
  40. package/components/_cards/charts-26-scatter3d.card.html +2 -2
  41. package/components/_cards/charts-27-ribbon3d.card.html +2 -2
  42. package/components/_cards/charts-3-bar.card.html +2 -2
  43. package/components/_cards/charts-4-barlist.card.html +2 -2
  44. package/components/_cards/charts-5-donut.card.html +3 -3
  45. package/components/_cards/charts-6-scatter.card.html +2 -2
  46. package/components/_cards/charts-7-heatmap.card.html +2 -2
  47. package/components/_cards/charts-8-waterfall.card.html +2 -2
  48. package/components/_cards/charts-9-bullet.card.html +2 -2
  49. package/components/_cards/charts-99-fullpage.card.html +5 -5
  50. package/components/_cards/checkbox.card.html +4 -4
  51. package/components/_cards/codeblock.card.html +1 -1
  52. package/components/_cards/combobox.card.html +3 -2
  53. package/components/_cards/context-menu.card.html +3 -2
  54. package/components/_cards/date-picker.card.html +5 -2
  55. package/components/_cards/diffvalue.card.html +2 -2
  56. package/components/_cards/divider.card.html +3 -3
  57. package/components/_cards/drawer.card.html +2 -2
  58. package/components/_cards/dropdown-menu.card.html +6 -6
  59. package/components/_cards/dropzone.card.html +1 -1
  60. package/components/_cards/empty-state.card.html +5 -5
  61. package/components/_cards/filter-bar.card.html +5 -5
  62. package/components/_cards/icon-button.card.html +2 -2
  63. package/components/_cards/icons-1-core.card.html +2 -2
  64. package/components/_cards/icons-2-feather.card.html +3 -3
  65. package/components/_cards/icons-3-unicons.card.html +3 -3
  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 +2 -2
  71. package/components/_cards/log-viewer.card.html +2 -2
  72. package/components/_cards/modal.card.html +9 -9
  73. package/components/_cards/multi-select.card.html +3 -2
  74. package/components/_cards/number-stepper.card.html +3 -2
  75. package/components/_cards/pagination.card.html +3 -2
  76. package/components/_cards/people.card.html +3 -3
  77. package/components/_cards/pipeline-node.card.html +2 -2
  78. package/components/_cards/pipeline-palette.card.html +4 -4
  79. package/components/_cards/radio.card.html +2 -2
  80. package/components/_cards/search.card.html +5 -2
  81. package/components/_cards/segmented-control.card.html +2 -2
  82. package/components/_cards/select.card.html +4 -4
  83. package/components/_cards/slider.card.html +1 -1
  84. package/components/_cards/smart-table.card.html +1 -1
  85. package/components/_cards/spreadsheet.card.html +2 -2
  86. package/components/_cards/stepper.card.html +2 -2
  87. package/components/_cards/table.card.html +103 -0
  88. package/components/_cards/tabs.card.html +3 -2
  89. package/components/_cards/tag.card.html +5 -2
  90. package/components/_cards/textarea.card.html +2 -2
  91. package/components/_cards/timeline.card.html +1 -1
  92. package/components/_cards/toast.card.html +4 -4
  93. package/components/_cards/toggles.card.html +3 -3
  94. package/components/_cards/tooltip.card.html +5 -5
  95. package/components/_cards/tree.card.html +2 -2
  96. package/components/_shared/src-buttons.js +1 -1
  97. package/components/_shared/src-charts.js +1 -1
  98. package/components/_shared/src-data.js +1 -1
  99. package/components/_shared/src-display.js +1 -1
  100. package/components/_shared/src-feedback.js +1 -1
  101. package/components/_shared/src-forms.js +1 -1
  102. package/components/_shared/src-navigation.js +1 -1
  103. package/components/_shared/src-pipeline.js +1 -1
  104. package/components/_shared/src-tabs.js +1 -1
  105. package/components/_vue/index.mjs +9 -5
  106. package/components/buttons/ActionChip.d.ts +3 -1
  107. package/components/buttons/ActionChip.jsx +4 -4
  108. package/components/buttons/ActionChip.vue +4 -4
  109. package/components/buttons/Button.d.ts +5 -2
  110. package/components/buttons/Button.jsx +15 -18
  111. package/components/buttons/Button.vue +17 -18
  112. package/components/buttons/ButtonGroup.vue +1 -1
  113. package/components/buttons/IconButton.d.ts +2 -1
  114. package/components/buttons/IconButton.jsx +9 -7
  115. package/components/buttons/IconButton.vue +10 -8
  116. package/components/buttons/buttons.prompt.md +6 -5
  117. package/components/charts/Bar3D.jsx +2 -2
  118. package/components/charts/Bar3D.vue +2 -2
  119. package/components/charts/BarChart.jsx +4 -4
  120. package/components/charts/BarChart.vue +4 -4
  121. package/components/charts/BarList.jsx +1 -1
  122. package/components/charts/BarList.vue +1 -1
  123. package/components/charts/BoxPlot.jsx +1 -1
  124. package/components/charts/BoxPlot.vue +2 -2
  125. package/components/charts/BulletChart.jsx +1 -1
  126. package/components/charts/BulletChart.vue +1 -1
  127. package/components/charts/CandlestickChart.jsx +2 -2
  128. package/components/charts/CandlestickChart.vue +3 -3
  129. package/components/charts/ChartKit.jsx +5 -5
  130. package/components/charts/ChartLegend.vue +16 -0
  131. package/components/charts/ChartTooltip.vue +16 -0
  132. package/components/charts/ChordChart.jsx +2 -2
  133. package/components/charts/ChordChart.vue +2 -2
  134. package/components/charts/GanttChart.jsx +2 -2
  135. package/components/charts/GanttChart.vue +2 -2
  136. package/components/charts/GaugeChart.jsx +2 -2
  137. package/components/charts/GaugeChart.vue +2 -2
  138. package/components/charts/HeatmapChart.jsx +2 -2
  139. package/components/charts/HeatmapChart.vue +2 -2
  140. package/components/charts/Histogram.jsx +2 -2
  141. package/components/charts/Histogram.vue +3 -3
  142. package/components/charts/LineChart.jsx +1 -1
  143. package/components/charts/LineChart.vue +3 -3
  144. package/components/charts/ParetoChart.jsx +3 -3
  145. package/components/charts/ParetoChart.vue +4 -4
  146. package/components/charts/PlotGrid.vue +18 -0
  147. package/components/charts/RadarChart.jsx +1 -1
  148. package/components/charts/RadarChart.vue +1 -1
  149. package/components/charts/Ribbon3D.jsx +2 -2
  150. package/components/charts/Ribbon3D.vue +2 -2
  151. package/components/charts/SankeyChart.jsx +2 -2
  152. package/components/charts/SankeyChart.vue +2 -2
  153. package/components/charts/Scatter3D.jsx +1 -1
  154. package/components/charts/Scatter3D.vue +1 -1
  155. package/components/charts/ScatterChart.jsx +3 -3
  156. package/components/charts/ScatterChart.vue +4 -4
  157. package/components/charts/Sparkline.jsx +1 -1
  158. package/components/charts/Sparkline.vue +1 -1
  159. package/components/charts/SunburstChart.jsx +1 -1
  160. package/components/charts/SunburstChart.vue +1 -1
  161. package/components/charts/Surface3D.jsx +2 -2
  162. package/components/charts/Surface3D.vue +2 -2
  163. package/components/charts/TreemapChart.jsx +2 -2
  164. package/components/charts/TreemapChart.vue +2 -2
  165. package/components/charts/WaterfallChart.jsx +2 -2
  166. package/components/charts/WaterfallChart.vue +3 -3
  167. package/components/charts/WorldMap.jsx +2 -2
  168. package/components/charts/WorldMap.vue +2 -2
  169. package/components/charts/XLabels.vue +11 -0
  170. package/components/data/CodeBlock.jsx +1 -1
  171. package/components/data/CodeBlock.vue +1 -1
  172. package/components/data/DiffValue.jsx +1 -1
  173. package/components/data/DiffValue.vue +1 -1
  174. package/components/data/FilterBar.d.ts +2 -0
  175. package/components/data/FilterBar.jsx +10 -9
  176. package/components/data/FilterBar.vue +9 -8
  177. package/components/data/KeyValue.jsx +3 -3
  178. package/components/data/KeyValue.vue +3 -3
  179. package/components/data/ListRow.jsx +2 -2
  180. package/components/data/ListRow.vue +2 -2
  181. package/components/data/Pagination.d.ts +5 -1
  182. package/components/data/Pagination.jsx +16 -12
  183. package/components/data/Pagination.vue +14 -9
  184. package/components/data/SmartTable.d.ts +4 -1
  185. package/components/data/SmartTable.jsx +16 -14
  186. package/components/data/SmartTable.vue +25 -22
  187. package/components/data/Spreadsheet.d.ts +2 -0
  188. package/components/data/Spreadsheet.jsx +12 -11
  189. package/components/data/Spreadsheet.vue +14 -13
  190. package/components/data/Table.d.ts +47 -0
  191. package/components/data/Table.jsx +85 -0
  192. package/components/data/Table.vue +63 -0
  193. package/components/data/data.prompt.md +3 -3
  194. package/components/display/Avatar.vue +3 -2
  195. package/components/display/AvatarGroup.vue +3 -2
  196. package/components/display/Badge.vue +1 -1
  197. package/components/display/Divider.vue +3 -3
  198. package/components/display/Donut.vue +3 -2
  199. package/components/display/IconTile.vue +2 -1
  200. package/components/display/Kbd.vue +1 -1
  201. package/components/display/ProgressBar.jsx +1 -1
  202. package/components/display/ProgressBar.vue +1 -1
  203. package/components/display/StatCard.vue +1 -1
  204. package/components/display/Tag.d.ts +3 -1
  205. package/components/display/Tag.jsx +4 -4
  206. package/components/display/Tag.vue +4 -4
  207. package/components/feedback/Accordion.jsx +2 -2
  208. package/components/feedback/Accordion.vue +2 -2
  209. package/components/feedback/ActivityItem.d.ts +3 -1
  210. package/components/feedback/ActivityItem.jsx +5 -5
  211. package/components/feedback/ActivityItem.vue +5 -4
  212. package/components/feedback/Alert.jsx +4 -4
  213. package/components/feedback/Alert.vue +3 -3
  214. package/components/feedback/Banner.jsx +5 -5
  215. package/components/feedback/Banner.vue +5 -5
  216. package/components/feedback/ContextMenu.d.ts +3 -1
  217. package/components/feedback/ContextMenu.jsx +5 -5
  218. package/components/feedback/ContextMenu.vue +5 -5
  219. package/components/feedback/DotActivity.vue +3 -3
  220. package/components/feedback/Drawer.d.ts +2 -0
  221. package/components/feedback/Drawer.jsx +6 -6
  222. package/components/feedback/Drawer.vue +22 -15
  223. package/components/feedback/EmptyState.vue +2 -2
  224. package/components/feedback/LogViewer.vue +1 -1
  225. package/components/feedback/Modal.d.ts +2 -0
  226. package/components/feedback/Modal.jsx +12 -11
  227. package/components/feedback/Modal.vue +10 -8
  228. package/components/feedback/Skeleton.vue +2 -1
  229. package/components/feedback/Spinner.vue +2 -1
  230. package/components/feedback/Stepper.vue +1 -1
  231. package/components/feedback/Timeline.jsx +1 -1
  232. package/components/feedback/Timeline.vue +1 -1
  233. package/components/feedback/Toast.vue +4 -4
  234. package/components/feedback/ToastStack.vue +11 -0
  235. package/components/feedback/Tooltip.vue +19 -0
  236. package/components/feedback/feedback.prompt.md +4 -4
  237. package/components/forms/Checkbox.d.ts +3 -2
  238. package/components/forms/Checkbox.jsx +7 -7
  239. package/components/forms/Checkbox.vue +8 -8
  240. package/components/forms/Combobox.d.ts +2 -0
  241. package/components/forms/Combobox.jsx +12 -10
  242. package/components/forms/Combobox.vue +11 -10
  243. package/components/forms/DatePicker.d.ts +4 -0
  244. package/components/forms/DatePicker.jsx +16 -12
  245. package/components/forms/DatePicker.vue +15 -11
  246. package/components/forms/Dropzone.jsx +2 -2
  247. package/components/forms/Dropzone.vue +5 -5
  248. package/components/forms/Field.d.ts +5 -1
  249. package/components/forms/Field.jsx +4 -4
  250. package/components/forms/Field.vue +4 -4
  251. package/components/forms/Input.d.ts +5 -1
  252. package/components/forms/Input.jsx +7 -7
  253. package/components/forms/Input.vue +3 -3
  254. package/components/forms/MultiSelect.d.ts +2 -0
  255. package/components/forms/MultiSelect.jsx +13 -11
  256. package/components/forms/MultiSelect.vue +12 -11
  257. package/components/forms/NumberStepper.d.ts +3 -1
  258. package/components/forms/NumberStepper.jsx +7 -7
  259. package/components/forms/NumberStepper.vue +8 -7
  260. package/components/forms/Radio.d.ts +5 -3
  261. package/components/forms/Radio.jsx +13 -15
  262. package/components/forms/Radio.vue +9 -10
  263. package/components/forms/RadioGroup.vue +5 -5
  264. package/components/forms/SearchInput.d.ts +3 -1
  265. package/components/forms/SearchInput.jsx +6 -6
  266. package/components/forms/SearchInput.vue +7 -6
  267. package/components/forms/Select.d.ts +3 -2
  268. package/components/forms/Select.jsx +6 -7
  269. package/components/forms/Select.vue +6 -6
  270. package/components/forms/Slider.jsx +3 -3
  271. package/components/forms/Slider.vue +3 -3
  272. package/components/forms/Switch.d.ts +2 -2
  273. package/components/forms/Switch.jsx +20 -22
  274. package/components/forms/Switch.vue +15 -14
  275. package/components/forms/Textarea.vue +4 -4
  276. package/components/forms/forms.prompt.md +1 -1
  277. package/components/forms/input-base.js +2 -1
  278. package/components/index.d.ts +1 -2
  279. package/components/index.js +3 -4
  280. package/components/navigation/Breadcrumb.jsx +3 -3
  281. package/components/navigation/Breadcrumb.vue +4 -4
  282. package/components/navigation/DocTree.jsx +3 -3
  283. package/components/navigation/DocTree.vue +3 -3
  284. package/components/navigation/PageHeading.vue +1 -1
  285. package/components/navigation/PageToolbar.vue +7 -6
  286. package/components/navigation/Sidebar.jsx +1 -1
  287. package/components/navigation/Sidebar.vue +3 -3
  288. package/components/navigation/ThemeToggle.vue +2 -1
  289. package/components/navigation/TopBar.d.ts +3 -1
  290. package/components/navigation/TopBar.jsx +6 -6
  291. package/components/navigation/Tree.jsx +5 -5
  292. package/components/navigation/Tree.vue +5 -5
  293. package/components/pipeline/ConfigPanel.d.ts +2 -0
  294. package/components/pipeline/ConfigPanel.jsx +9 -9
  295. package/components/pipeline/ConfigPanel.vue +9 -9
  296. package/components/pipeline/DataPreview.d.ts +3 -1
  297. package/components/pipeline/DataPreview.jsx +7 -7
  298. package/components/pipeline/DataPreview.vue +7 -7
  299. package/components/pipeline/NodeCard.jsx +3 -3
  300. package/components/pipeline/NodeCard.vue +3 -3
  301. package/components/pipeline/PaletteItem.vue +2 -2
  302. package/components/pipeline/PaletteListItem.vue +1 -1
  303. package/components/pipeline/PalettePanel.jsx +2 -2
  304. package/components/pipeline/PalettePanel.vue +1 -1
  305. package/components/pipeline/Port.vue +15 -0
  306. package/components/pipeline/PortLabel.vue +15 -0
  307. package/components/pipeline/ZoomControls.vue +1 -1
  308. package/components/pipeline/port-skin.js +13 -0
  309. package/components/px.js +4 -0
  310. package/components/tabs/PanelTabs.vue +5 -4
  311. package/components/tabs/PillTabs.vue +5 -4
  312. package/components/tabs/SegmentedControl.d.ts +2 -2
  313. package/components/tabs/SegmentedControl.jsx +7 -7
  314. package/components/tabs/SegmentedControl.vue +7 -7
  315. package/components/tabs/Tabs.d.ts +5 -3
  316. package/components/tabs/Tabs.jsx +15 -15
  317. package/components/tabs/Tabs.vue +5 -5
  318. package/components/tabs/ViewToggle.d.ts +3 -1
  319. package/components/tabs/ViewToggle.jsx +5 -5
  320. package/components/tabs/ViewToggle.vue +5 -5
  321. package/components/tabs/tabs.prompt.md +1 -1
  322. package/keybricks.css +4 -0
  323. package/package.json +6 -3
  324. package/readme.md +90 -122
  325. package/readme.zh-CN.md +120 -0
  326. package/tokens/charts.css +20 -17
  327. package/tokens/colors.css +2 -2
  328. package/tokens/dark.css +2 -2
  329. package/tokens/hover.css +32 -0
  330. package/tokens/interaction.css +3 -1
  331. package/tokens/layout.css +29 -7
  332. package/tokens/tokens.d.ts +36 -0
  333. package/tokens/tokens.js +143 -89
  334. package/tokens/tokens.json +240 -106
  335. package/tokens/typography.css +27 -14
  336. package/components/_cards/brand-visual.card.html +0 -43
  337. package/components/_cards/datatable.card.html +0 -48
  338. package/components/_shared/src-brand.js +0 -1
  339. package/components/brand/BrandVisual.d.ts +0 -17
  340. package/components/brand/BrandVisual.jsx +0 -29
  341. package/components/brand/BrandVisual.vue +0 -34
  342. package/components/brand/_dir.card.html +0 -13
  343. package/components/brand/brandMotifs.js +0 -210
  344. package/components/buttons/_dir.card.html +0 -13
  345. package/components/charts/ChartKit.vue +0 -47
  346. package/components/charts/_dir.card.html +0 -7
  347. package/components/data/DataTable.d.ts +0 -28
  348. package/components/data/DataTable.jsx +0 -32
  349. package/components/data/DataTable.vue +0 -25
  350. package/components/data/_dir.card.html +0 -13
  351. package/components/display/_dir.card.html +0 -13
  352. package/components/feedback/_dir.card.html +0 -13
  353. package/components/forms/_dir.card.html +0 -13
  354. package/components/icons/_dir.card.html +0 -13
  355. package/components/navigation/_dir.card.html +0 -13
  356. package/components/pipeline/_dir.card.html +0 -13
  357. package/components/tabs/_dir.card.html +0 -13
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  /**
3
- * Switch — 36×20 pill (lg 44×24), 16/20px knob, 160ms ease-out slide. Controlled.
3
+ * Switch — pill track at the region's tier (sm 28×16 · md 36×20 · lg 44×24), knob 4px smaller, 160ms ease-out slide. Controlled.
4
4
  * Variants: knob glyph (icon or one character), text inside the pill, captions beside it,
5
5
  * a semantic red→green tone, and an async gate that shows a spinner in the knob.
6
6
  */
@@ -8,7 +8,7 @@ export interface SwitchProps {
8
8
  checked?: boolean;
9
9
  onChange?(next: boolean): void;
10
10
  disabled?: boolean;
11
- /** sm 28×16 · md 36×20 (default) · lg 44×24 — use lg whenever the knob carries a glyph */
11
+ /** Size tier: sm 28×16 · md 36×20 · lg 44×24. Omit to inherit the enclosing region's tier (the default); lg is roomy enough for a knob glyph */
12
12
  size?: "sm" | "md" | "lg";
13
13
  /** primary = action blue on / border-strong off · semantic = success on / danger off */
14
14
  tone?: "primary" | "semantic";
@@ -2,11 +2,12 @@ import React from "react";
2
2
  import { Icon } from "../icons/Icon.jsx";
3
3
 
4
4
  /**
5
- * Switch — 36×20 pill, 16px knob, 160ms ease-out slide.
5
+ * Switch — pill track sized by the region's tier (sm 28×16 · md 36×20 · lg 44×24), knob 4px smaller, 160ms ease-out slide.
6
+ * Track, knob, glyph and label all read the tier tokens (tokens/layout.css), so a Switch in a form row matches its neighbours.
6
7
  *
7
8
  * checked / onChange(next) controlled boolean
8
9
  * disabled 50% opacity, no pointer
9
- * size "md" (36×20, default) | "lg" (44×24 — needed for knobIcon / knobText)
10
+ * size "sm" | "md" | "lg" — omit to inherit the region's tier; lg is roomy enough for knobIcon / knobText
10
11
  * tone "primary" (default, action blue) | "semantic" (red off → green on)
11
12
  * onColor / offColor explicit track colors; override tone
12
13
  * knobIcon / knobText glyph inside the knob: string | { on, off }. knobText is ONE letter or ONE CJK character.
@@ -18,27 +19,23 @@ import { Icon } from "../icons/Icon.jsx";
18
19
  * inlinePrompt (Vue-friendly alias of insideOn/insideOff pair {on,off})
19
20
  */
20
21
 
21
- const SIZES = {
22
- sm: { w: 28, h: 16, knob: 12, pad: 2, inside: 9, glyph: 8, label: "var(--text-xs)" },
23
- md: { w: 36, h: 20, knob: 16, pad: 2, inside: 10, glyph: 10, label: "var(--text-sm)" },
24
- lg: { w: 44, h: 24, knob: 20, pad: 2, inside: 11, glyph: 12, label: "var(--text-sm)" }
25
- };
22
+ /* Geometry in CSS so it follows the tier without JavaScript: knob = track height − 4, glyph = half the track height. */
23
+ const H = "var(--switch-h)", W = "var(--switch-w)", KNOB = "calc(var(--switch-h) - 4px)", GLYPH = "calc(var(--switch-h) * .5)";
26
24
 
27
25
  const pick = (v, on) => (v && typeof v === "object" ? (on ? v.on : v.off) : v);
28
26
 
29
- export function Switch({ checked = false, onChange, disabled, size = "md", tone = "primary", onColor, offColor, knobIcon, knobText, insideOn, insideOff, inlinePrompt, label, labelOn, labelOff, loading: loadingProp, beforeChange, style }) {
27
+ export function Switch({ checked = false, onChange, disabled, size, tone = "primary", onColor, offColor, knobIcon, knobText, insideOn, insideOff, inlinePrompt, label, labelOn, labelOff, loading: loadingProp, beforeChange, style }) {
30
28
  // `label` = single caption to the right, shown in text-body regardless of state (settings-row usage)
31
29
  const single = label != null && labelOn == null && labelOff == null;
32
30
  const [busy, setBusy] = React.useState(false);
33
31
  const alive = React.useRef(true);
34
32
  React.useEffect(() => () => { alive.current = false; }, []);
35
33
  const loading = loadingProp || busy;
36
- const s = SIZES[size] || SIZES.md;
37
34
  if (inlinePrompt) { insideOn = insideOn ?? inlinePrompt.on; insideOff = insideOff ?? inlinePrompt.off; }
38
35
  const hasInside = insideOn != null || insideOff != null;
39
- // inside text needs a wider track so the knob never covers the word
40
- const insideExtra = hasInside ? Math.max(0, Math.max(String(insideOn ?? "").length, String(insideOff ?? "").length) * (s.inside * .68) + 6 - (s.w - s.knob - s.pad * 2)) : 0;
41
- const w = s.w + insideExtra;
36
+ // inside text needs a wider track so the knob never covers the word: text runs at half the track height, ~.68em per character
37
+ const insideChars = hasInside ? Math.max(String(insideOn ?? "").length, String(insideOff ?? "").length) : 0;
38
+ const w = hasInside ? `max(${W}, calc(${H} * ${(1 + insideChars * .34).toFixed(2)} + 6px))` : W;
42
39
 
43
40
  const onTrack = onColor || (tone === "semantic" ? "var(--success)" : "var(--action-primary)");
44
41
  const offTrack = offColor || (tone === "semantic" ? "var(--danger)" : "var(--border-strong)");
@@ -61,30 +58,31 @@ export function Switch({ checked = false, onChange, disabled, size = "md", tone
61
58
 
62
59
  const glyphIcon = pick(knobIcon, checked);
63
60
  const glyphText = pick(knobText, checked);
64
- const knobX = checked ? w - s.knob - s.pad : s.pad;
61
+ const knobX = checked ? `calc(100% - ${H} + 2px)` : 2;
65
62
 
66
- const knob = <span aria-hidden="true" style={{ position: "absolute", top: s.pad, left: knobX, width: s.knob, height: s.knob, borderRadius: "var(--radius-full)", background: "var(--bg-surface)", boxShadow: "0 1px 2px rgba(0,0,0,.18)", transition: "left 160ms ease-out", display: "flex", alignItems: "center", justifyContent: "center", color: track, fontSize: s.glyph, lineHeight: 1, fontWeight: 600, fontFamily: "var(--font-sans)", overflow: "hidden" }}>
63
+ const knob = <span aria-hidden="true" style={{ position: "absolute", top: 2, left: knobX, width: KNOB, height: KNOB, borderRadius: "var(--radius-full)", background: "var(--bg-surface)", boxShadow: "0 1px 2px rgba(0,0,0,.18)", transition: "left 160ms ease-out", display: "flex", alignItems: "center", justifyContent: "center", color: track, fontSize: GLYPH, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", overflow: "hidden" }}>
67
64
  {loading
68
- ? <span style={{ width: s.glyph, height: s.glyph, borderRadius: "50%", border: `1.5px solid ${track}`, borderRightColor: "transparent", animation: "kb-spin 720ms linear infinite" }}></span>
69
- : glyphIcon ? <Icon name={glyphIcon} size={s.glyph} strokeWidth={2.5} />
65
+ ? <span style={{ width: GLYPH, height: GLYPH, borderRadius: "50%", border: `1.5px solid ${track}`, borderRightColor: "transparent", animation: "kb-spin 720ms linear infinite" }}></span>
66
+ : glyphIcon ? <Icon name={glyphIcon} size={10} strokeWidth={2.5} style={{ width: GLYPH, height: GLYPH }} />
70
67
  : glyphText ? <span>{String(glyphText).slice(0, 1)}</span> : null}
71
68
  </span>;
72
69
 
73
- const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]: s.pad + 4, display: "flex", alignItems: "center", fontSize: s.inside, lineHeight: 1, fontWeight: 600, fontFamily: "var(--font-sans)", letterSpacing: ".2px", color: "var(--on-accent)", transition: "opacity 160ms ease-out", pointerEvents: "none", whiteSpace: "nowrap" });
70
+ const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]: 6, display: "flex", alignItems: "center", fontSize: GLYPH, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", letterSpacing: ".2px", color: "var(--on-accent)", transition: "opacity 160ms ease-out", pointerEvents: "none", whiteSpace: "nowrap" });
74
71
 
75
72
  const pill = <span role="switch" aria-checked={checked} aria-busy={loading || undefined} aria-disabled={disabled || undefined} tabIndex={inert ? -1 : 0} onClick={flip}
76
73
  onKeyDown={e => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); flip(); } }}
77
- style={{ position: "relative", display: "inline-block", width: w, height: s.h, borderRadius: "var(--radius-full)", background: track, transition: "background 160ms ease-out", cursor: inert ? (loading ? "progress" : "not-allowed") : "pointer", flexShrink: 0, verticalAlign: "middle", outline: "none" }}>
74
+ style={{ position: "relative", display: "inline-block", width: w, height: H, borderRadius: "var(--radius-full)", "--sw-track": track, background: "var(--sw-bg, var(--sw-track))", transition: "background 160ms ease-out", cursor: inert ? (loading ? "progress" : "not-allowed") : "pointer", flexShrink: 0, verticalAlign: "middle", outline: "none" }}>
78
75
  {hasInside && <span style={{ ...insideStyle("left"), opacity: checked ? 1 : 0 }}>{insideOn}</span>}
79
76
  {hasInside && <span style={{ ...insideStyle("right"), opacity: checked ? 0 : 1 }}>{insideOff}</span>}
80
77
  {knob}
81
78
  </span>;
82
79
 
83
- if (single) return <span style={{ display: "inline-flex", alignItems: "center", gap: 8, opacity: disabled ? .5 : 1, ...style }}>{pill}<span onClick={flip} style={{ fontSize: s.label, lineHeight: 1, fontFamily: "var(--font-sans)", fontWeight: 500, color: "var(--text-body)", cursor: inert ? "default" : "pointer", userSelect: "none" }}>{label}</span></span>;
84
- if (labelOn == null && labelOff == null) return <span style={{ display: "inline-flex", alignItems: "center", opacity: disabled ? .5 : 1, ...style }}>{pill}</span>;
80
+ const capBase = { fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", fontFamily: "var(--font-sans)", cursor: inert ? "default" : "pointer", userSelect: "none" };
81
+ if (single) return <span data-size={size} data-sw="" style={{ display: "inline-flex", alignItems: "center", gap: "var(--control-gap)", opacity: disabled ? .5 : 1, ...style }}>{pill}<span onClick={flip} style={{ ...capBase, fontWeight: "var(--weight-regular)", color: "var(--text-body)" }}>{label}</span></span>;
82
+ if (labelOn == null && labelOff == null) return <span data-size={size} style={{ display: "inline-flex", alignItems: "center", opacity: disabled ? .5 : 1, ...style }}>{pill}</span>;
85
83
 
86
- const cap = (text, active) => text == null ? null : <span style={{ fontSize: s.label, lineHeight: 1, fontFamily: "var(--font-sans)", fontWeight: active ? 500 : 400, color: active ? "var(--text-heading)" : "var(--text-muted)", transition: "color 160ms ease-out", cursor: inert ? "default" : "pointer", userSelect: "none" }} onClick={flip}>{text}</span>;
87
- return <span style={{ display: "inline-flex", alignItems: "center", gap: 8, opacity: disabled ? .5 : 1, ...style }}>
84
+ const cap = (text, active) => text == null ? null : <span style={{ ...capBase, fontWeight: active ? "var(--weight-medium)" : "var(--weight-regular)", color: active ? "var(--text-heading)" : "var(--text-muted)", transition: "color 160ms ease-out" }} onClick={flip}>{text}</span>;
85
+ return <span data-size={size} data-sw="" style={{ display: "inline-flex", alignItems: "center", gap: "var(--control-gap)", opacity: disabled ? .5 : 1, ...style }}>
88
86
  {cap(labelOff, !checked)}{pill}{cap(labelOn, checked)}
89
87
  </span>;
90
88
  }
@@ -1,32 +1,33 @@
1
1
  <script setup>
2
2
  import { ref, computed, onBeforeUnmount } from 'vue'
3
3
  import Icon from '../icons/Icon.vue'
4
- // Switch — 36×20 pill (lg 44×24), 16/20px knob, 160ms slide. v-model:checked.
4
+ // Switch — pill track sized by the region's tier (sm 28×16 · md 36×20 · lg 44×24), knob 4px smaller, 160ms slide. v-model:checked.
5
+ // Geometry is CSS (tokens/layout.css --switch-w/-h), so it follows the tier without JavaScript; `size` stamps data-size to override.
5
6
  // tone "primary" | "semantic" (red off → green on); onColor/offColor override.
6
7
  // knobIcon / knobText: string | { on, off } — knobText is ONE letter or ONE CJK char.
7
8
  // insideOn / insideOff: text inside the pill opposite the knob. labelOn / labelOff: captions beside the pill.
8
9
  // loading: controlled spinner. beforeChange(next) → Promise|boolean gates the flip and shows loading meanwhile.
9
10
  const props = defineProps({
10
11
  checked: Boolean, disabled: Boolean, loading: Boolean,
11
- size: { type: String, default: 'md' }, tone: { type: String, default: 'primary' },
12
+ size: String, tone: { type: String, default: 'primary' },
12
13
  onColor: String, offColor: String,
13
14
  knobIcon: [String, Object], knobText: [String, Object],
14
15
  insideOn: String, insideOff: String, label: String, labelOn: String, labelOff: String,
15
16
  beforeChange: Function, style: Object
16
17
  })
17
18
  const emit = defineEmits(['update:checked', 'change'])
18
- const SIZES = { sm: { w: 28, h: 16, knob: 12, pad: 2, inside: 9, glyph: 8 }, md: { w: 36, h: 20, knob: 16, pad: 2, inside: 10, glyph: 10 }, lg: { w: 44, h: 24, knob: 20, pad: 2, inside: 11, glyph: 12 } }
19
- const s = computed(() => SIZES[props.size] || SIZES.md)
19
+ const H = 'var(--switch-h)', W = 'var(--switch-w)', KNOB = 'calc(var(--switch-h) - 4px)', GLYPH = 'calc(var(--switch-h) * .5)'
20
20
  const busy = ref(false); let alive = true; onBeforeUnmount(() => { alive = false })
21
21
  const isLoading = computed(() => props.loading || busy.value)
22
22
  const inert = computed(() => props.disabled || isLoading.value)
23
23
  const hasInside = computed(() => props.insideOn != null || props.insideOff != null)
24
- const width = computed(() => { if (!hasInside.value) return s.value.w; const n = Math.max((props.insideOn || '').length, (props.insideOff || '').length); return s.value.w + Math.max(0, n * (s.value.inside * .68) + 6 - (s.value.w - s.value.knob - s.value.pad * 2)) })
24
+ // inside text needs a wider track so the knob never covers the word: text runs at half the track height, ~.68em per character
25
+ const width = computed(() => { if (!hasInside.value) return W; const n = Math.max((props.insideOn || '').length, (props.insideOff || '').length); return `max(${W}, calc(${H} * ${(1 + n * .34).toFixed(2)} + 6px))` })
25
26
  const track = computed(() => props.checked ? (props.onColor || (props.tone === 'semantic' ? 'var(--success)' : 'var(--action-primary)')) : (props.offColor || (props.tone === 'semantic' ? 'var(--danger)' : 'var(--border-strong)')))
26
27
  const pick = (v) => v && typeof v === 'object' ? (props.checked ? v.on : v.off) : v
27
28
  const glyphIcon = computed(() => pick(props.knobIcon))
28
29
  const glyphText = computed(() => { const t = pick(props.knobText); return t ? String(t).slice(0, 1) : '' })
29
- const knobX = computed(() => props.checked ? width.value - s.value.knob - s.value.pad : s.value.pad)
30
+ const knobX = computed(() => props.checked ? `calc(100% - ${H} + 2px)` : '2px')
30
31
  async function flip() {
31
32
  if (inert.value) return
32
33
  const next = !props.checked
@@ -39,23 +40,23 @@ async function flip() {
39
40
  }
40
41
  emit('update:checked', next); emit('change', next)
41
42
  }
42
- const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: (s.value.pad + 4) + 'px', display: 'flex', alignItems: 'center', fontSize: s.value.inside + 'px', lineHeight: 1, fontWeight: 600, fontFamily: 'var(--font-sans)', letterSpacing: '.2px', color: 'var(--on-accent)', transition: 'opacity 160ms ease-out', pointerEvents: 'none', whiteSpace: 'nowrap', opacity: on === props.checked ? 1 : 0 })
43
- const cap = (active) => ({ fontSize: props.size === 'sm' ? 'var(--text-xs)' : 'var(--text-sm)', lineHeight: 1, fontFamily: 'var(--font-sans)', fontWeight: active ? 500 : 400, color: active ? 'var(--text-heading)' : 'var(--text-muted)', transition: 'color 160ms ease-out', cursor: inert.value ? 'default' : 'pointer', userSelect: 'none' })
43
+ const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: '6px', display: 'flex', alignItems: 'center', fontSize: GLYPH, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', letterSpacing: '.2px', color: 'var(--on-accent)', transition: 'opacity 160ms ease-out', pointerEvents: 'none', whiteSpace: 'nowrap', opacity: on === props.checked ? 1 : 0 })
44
+ const cap = (active) => ({ fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontFamily: 'var(--font-sans)', fontWeight: active ? 'var(--weight-medium)' : 'var(--weight-regular)', color: active ? 'var(--text-heading)' : 'var(--text-muted)', transition: 'color 160ms ease-out', cursor: inert.value ? 'default' : 'pointer', userSelect: 'none' })
44
45
  </script>
45
46
  <template>
46
- <span :style="{ display: 'inline-flex', alignItems: 'center', gap: '8px', opacity: disabled ? .5 : 1, ...style }">
47
+ <span :data-size="size" data-sw="" :style="{ display: 'inline-flex', alignItems: 'center', gap: 'var(--control-gap)', opacity: disabled ? .5 : 1, ...style }">
47
48
  <span v-if="labelOff != null" :style="cap(!checked)" @click="flip">{{ labelOff }}</span>
48
49
  <span role="switch" :aria-checked="checked" :aria-busy="isLoading || undefined" :aria-disabled="disabled || undefined" :tabindex="inert ? -1 : 0" @click="flip" @keydown.space.prevent="flip" @keydown.enter.prevent="flip"
49
- :style="{ position: 'relative', display: 'inline-block', width: width + 'px', height: s.h + 'px', borderRadius: 'var(--radius-full)', background: track, transition: 'background 160ms ease-out', cursor: inert ? (isLoading ? 'progress' : 'not-allowed') : 'pointer', flexShrink: 0, verticalAlign: 'middle', outline: 'none' }">
50
+ :style="{ position: 'relative', display: 'inline-block', width: width, height: H, borderRadius: 'var(--radius-full)', '--sw-track': track, background: 'var(--sw-bg, var(--sw-track))', transition: 'background 160ms ease-out', cursor: inert ? (isLoading ? 'progress' : 'not-allowed') : 'pointer', flexShrink: 0, verticalAlign: 'middle', outline: 'none' }">
50
51
  <span v-if="hasInside" :style="insideStyle('left', true)">{{ insideOn }}</span>
51
52
  <span v-if="hasInside" :style="insideStyle('right', false)">{{ insideOff }}</span>
52
- <span aria-hidden="true" :style="{ position: 'absolute', top: s.pad + 'px', left: knobX + 'px', width: s.knob + 'px', height: s.knob + 'px', borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', boxShadow: '0 1px 2px rgba(0,0,0,.18)', transition: 'left 160ms ease-out', display: 'flex', alignItems: 'center', justifyContent: 'center', color: track, fontSize: s.glyph + 'px', lineHeight: 1, fontWeight: 600, fontFamily: 'var(--font-sans)', overflow: 'hidden' }">
53
- <span v-if="isLoading" :style="{ width: s.glyph + 'px', height: s.glyph + 'px', borderRadius: '50%', border: '1.5px solid ' + track, borderRightColor: 'transparent', animation: 'kb-spin 720ms linear infinite' }"></span>
54
- <Icon v-else-if="glyphIcon" :name="glyphIcon" :size="s.glyph" :stroke-width="2.5" />
53
+ <span aria-hidden="true" :style="{ position: 'absolute', top: '2px', left: knobX, width: KNOB, height: KNOB, borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', boxShadow: '0 1px 2px rgba(0,0,0,.18)', transition: 'left 160ms ease-out', display: 'flex', alignItems: 'center', justifyContent: 'center', color: track, fontSize: GLYPH, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', overflow: 'hidden' }">
54
+ <span v-if="isLoading" :style="{ width: GLYPH, height: GLYPH, borderRadius: '50%', border: '1.5px solid ' + track, borderRightColor: 'transparent', animation: 'kb-spin 720ms linear infinite' }"></span>
55
+ <Icon v-else-if="glyphIcon" :name="glyphIcon" :size="10" :stroke-width="2.5" :style="{ width: GLYPH, height: GLYPH }" />
55
56
  <span v-else-if="glyphText">{{ glyphText }}</span>
56
57
  </span>
57
58
  </span>
58
59
  <span v-if="labelOn != null" :style="cap(checked)" @click="flip">{{ labelOn }}</span>
59
- <span v-if="label != null && labelOn == null && labelOff == null" :style="{ fontSize: size === 'sm' ? 'var(--text-xs)' : 'var(--text-sm)', lineHeight: 1, fontFamily: 'var(--font-sans)', fontWeight: 500, color: 'var(--text-body)', cursor: inert ? 'default' : 'pointer', userSelect: 'none' }" @click="flip">{{ label }}</span>
60
+ <span v-if="label != null && labelOn == null && labelOff == null" :style="{ fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-regular)', color: 'var(--text-body)', cursor: inert ? 'default' : 'pointer', userSelect: 'none' }" @click="flip">{{ label }}</span>
60
61
  </span>
61
62
  </template>
@@ -1,20 +1,20 @@
1
1
  <script setup>
2
2
  import { ref, computed } from 'vue'
3
3
  import { inputBase } from './input-base.js'
4
- // Multiline textarea, r8, default 90px tall. v-model. showCount + maxLength renders a bottom-right "n/max" counter (red past the limit).
5
- const props = defineProps({ modelValue: String, rows: { type: Number, default: 4 }, error: Boolean, disabled: Boolean, showCount: Boolean, maxLength: Number, style: Object })
4
+ // Multiline textarea, r8, default 90px tall; text follows the region's tier. v-model. showCount + maxLength renders a bottom-right "n/max" counter (red past the limit).
5
+ const props = defineProps({ modelValue: String, rows: { type: Number, default: 4 }, error: Boolean, disabled: Boolean, showCount: Boolean, maxLength: Number, size: String, style: Object })
6
6
  defineEmits(['update:modelValue'])
7
7
  const focus = ref(false)
8
8
  const len = computed(() => (props.modelValue || '').length)
9
9
  const over = computed(() => props.maxLength != null && len.value > props.maxLength)
10
- const s = computed(() => ({ ...inputBase, height: '90px', resize: 'vertical', paddingBottom: props.showCount ? '22px' : '8px',
10
+ const s = computed(() => ({ ...inputBase, height: '90px', resize: 'vertical', paddingTop: '8px', paddingBottom: props.showCount ? '22px' : '8px',
11
11
  ...((props.error || over.value) ? { borderColor: 'var(--danger)' } : null),
12
12
  ...(focus.value && !props.disabled ? { borderColor: (props.error || over.value) ? 'var(--danger)' : 'var(--focus-border)', boxShadow: `0 0 0 2px ${(props.error || over.value) ? 'var(--danger-bg)' : 'var(--focus-ring)'}` } : null),
13
13
  ...(props.disabled ? { opacity: .5, cursor: 'not-allowed', background: 'var(--bg-inset)' } : null), ...props.style }))
14
14
  </script>
15
15
  <template>
16
16
  <div style="position:relative">
17
- <textarea :rows="rows" :disabled="disabled" :maxlength="showCount ? undefined : maxLength" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" @focus="focus = true" @blur="focus = false" :style="s"></textarea>
17
+ <textarea :rows="rows" :disabled="disabled" :data-size="size" :data-field="disabled ? undefined : ''" :maxlength="showCount ? undefined : maxLength" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" @focus="focus = true" @blur="focus = false" :style="s"></textarea>
18
18
  <span v-if="showCount" :style="{ position: 'absolute', right: '10px', bottom: '7px', fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', color: over ? 'var(--danger)' : 'var(--text-muted)' }">{{ len }}{{ maxLength != null ? `/${maxLength}` : '' }}</span>
19
19
  </div>
20
20
  </template>
@@ -15,7 +15,7 @@ Form controls, split by component (each with its own specimen card covering defa
15
15
  Everything is 12px text in 36px controls; readOnly inputs go #fafafa; disabled = 50% opacity; error = red border + red helper text via `Field`'s `error` prop.
16
16
 
17
17
 
18
- `Switch` / `Radio` / `RadioGroup` (additions, not in the source file). Switch: default 36×20 blue-on / border-strong-off; `size="lg"` (44×24) whenever the knob carries a glyph — `knobIcon` (string or `{on,off}`) or `knobText` (ONE letter / ONE CJK char); `insideOn`/`insideOff` put 1–3 characters inside the pill; `labelOn`/`labelOff` put captions on both sides with the active one in text-heading; `tone="semantic"` = danger off → success on, only for states with consequence (a live integration, a kill switch); `beforeChange` async-gates the flip with a spinner in the knob, `loading` does the same under your control.
18
+ `Switch` / `Radio` / `RadioGroup` (additions, not in the source file). Switch: the track follows the size tier (sm 28×16 · md 36×20 · lg 44×24), blue-on / border-strong-off; a knob glyph needs the lg tier (`size="lg"` only when the switch stands alone) — `knobIcon` (string or `{on,off}`) or `knobText` (ONE letter / ONE CJK char); `insideOn`/`insideOff` put 1–3 characters inside the pill; `labelOn`/`labelOff` put captions on both sides with the active one in text-heading; `tone="semantic"` = danger off → success on, only for states with consequence (a live integration, a kill switch); `beforeChange` async-gates the flip with a spinner in the knob, `loading` does the same under your control.
19
19
 
20
20
  ```jsx
21
21
  <Switch label="Debug Mode" checked={on} onChange={setOn}/>
@@ -1,3 +1,4 @@
1
1
  // Shared base style for text-like controls (Input, Textarea, Combobox, DatePicker).
2
2
  // Lives in a plain module because <script setup> cannot export values.
3
- export const inputBase = { height: 'var(--h-input)', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-control)', background: 'var(--bg-surface)', padding: '8px 12px', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-primary)', outline: 'none', boxSizing: 'border-box', width: '100%' }
3
+ // Height, text and padding come from the size tier of the enclosing region (tokens/layout.css): sm 24 · md 32 · lg 36.
4
+ export const inputBase = { height: 'var(--control-h)', borderRadius: 'var(--radius-md)', border: '1px solid var(--field-border, var(--border-control))', background: 'var(--bg-surface)', padding: '0 var(--control-px)', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-primary)', outline: 'none', boxSizing: 'border-box', width: '100%' }
@@ -1,5 +1,4 @@
1
1
  /* Generated barrel — do not edit by hand. */
2
- export * from "./brand/BrandVisual";
3
2
  export * from "./buttons/ActionChip";
4
3
  export * from "./buttons/Button";
5
4
  export * from "./buttons/IconButton";
@@ -32,7 +31,6 @@ export * from "./charts/TreemapChart";
32
31
  export * from "./charts/WaterfallChart";
33
32
  export * from "./charts/WorldMap";
34
33
  export * from "./data/CodeBlock";
35
- export * from "./data/DataTable";
36
34
  export * from "./data/DiffValue";
37
35
  export * from "./data/FilterBar";
38
36
  export * from "./data/KeyValue";
@@ -40,6 +38,7 @@ export * from "./data/ListRow";
40
38
  export * from "./data/Pagination";
41
39
  export * from "./data/SmartTable";
42
40
  export * from "./data/Spreadsheet";
41
+ export * from "./data/Table";
43
42
  export * from "./display/Avatar";
44
43
  export * from "./display/Badge";
45
44
  export * from "./display/Card";
@@ -1,9 +1,6 @@
1
1
  /* Generated barrel — do not edit by hand. Run: node scripts/build-index.mjs
2
2
  Source-only package: consumers compile these .jsx files with their own toolchain. */
3
3
 
4
- /* brand/ */
5
- export { BrandVisual } from "./brand/BrandVisual.jsx";
6
-
7
4
  /* buttons/ */
8
5
  export { ActionChip } from "./buttons/ActionChip.jsx";
9
6
  export { Button, ButtonGroup } from "./buttons/Button.jsx";
@@ -41,7 +38,6 @@ export { WorldMap } from "./charts/WorldMap.jsx";
41
38
 
42
39
  /* data/ */
43
40
  export { CodeBlock } from "./data/CodeBlock.jsx";
44
- export { DataTable } from "./data/DataTable.jsx";
45
41
  export { DiffValue } from "./data/DiffValue.jsx";
46
42
  export { FilterBar } from "./data/FilterBar.jsx";
47
43
  export { KeyValue, KeyValueList } from "./data/KeyValue.jsx";
@@ -49,6 +45,9 @@ export { ListRow } from "./data/ListRow.jsx";
49
45
  export { Pagination } from "./data/Pagination.jsx";
50
46
  export { SmartTable } from "./data/SmartTable.jsx";
51
47
  export { Spreadsheet } from "./data/Spreadsheet.jsx";
48
+ export { Table } from "./data/Table.jsx";
49
+ /** @deprecated renamed to Table in 1.0.6 — still exported in 2.x, removed in 3.0 */
50
+ export { Table as DataTable } from "./data/Table.jsx";
52
51
 
53
52
  /* display/ */
54
53
  export { Avatar, AvatarGroup } from "./display/Avatar.jsx";
@@ -9,7 +9,7 @@ function Crumb({ label, last, hovered, onSelect }) {
9
9
  onClick={() => !last && onSelect && onSelect(label)}
10
10
  onKeyDown={(e) => { if (!last && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onSelect && onSelect(label); } }}
11
11
  onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
12
- style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: last || on ? "var(--text-heading)" : "var(--text-secondary)", fontWeight: last ? "var(--weight-medium)" : "var(--weight-regular)", cursor: last ? "default" : "pointer", borderRadius: "var(--radius-sm)", transition: "var(--t-hover)" }}>{label}</span>
12
+ style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", color: last || on ? "var(--text-heading)" : "var(--text-secondary)", fontWeight: last ? "var(--weight-medium)" : "var(--weight-regular)", cursor: last ? "default" : "pointer", borderRadius: "var(--radius-sm)", transition: "var(--t-hover)" }}>{label}</span>
13
13
  );
14
14
  }
15
15
 
@@ -34,7 +34,7 @@ function Overflow({ items, onSelect }) {
34
34
  onClick={() => setOpen(!open)}
35
35
  onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen(!open); } if (e.key === "Escape") setOpen(false); }}
36
36
  onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
37
- style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 20, height: 18, borderRadius: "var(--radius-sm)", cursor: "pointer", color: open || hover ? "var(--text-heading)" : "var(--text-muted)", background: open ? "var(--bg-active)" : hover ? "var(--bg-hover)" : "transparent", transition: "var(--t-hover)" }}>
37
+ style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 24, height: 24, borderRadius: "var(--radius-sm)", cursor: "pointer", color: open || hover ? "var(--text-heading)" : "var(--text-muted)", background: open ? "var(--bg-active)" : hover ? "var(--bg-hover)" : "transparent", transition: "var(--t-hover)" }}>
38
38
  <Icon name="ellipsis" size={14} />
39
39
  </span>
40
40
  {open ? (
@@ -52,7 +52,7 @@ function OverflowItem({ label, depth, onSelect }) {
52
52
  <span role="menuitem" tabIndex={0} onClick={() => onSelect(label)}
53
53
  onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onSelect(label); } }}
54
54
  onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
55
- style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 9px", paddingLeft: 9 + depth * 12, borderRadius: "var(--radius-sm)", cursor: "pointer", background: hover ? "var(--bg-hover)" : "transparent", fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", transition: "var(--t-hover)" }}>
55
+ style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 9px", paddingLeft: 9 + depth * 12, borderRadius: "var(--radius-sm)", cursor: "pointer", background: hover ? "var(--bg-hover)" : "transparent", fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", transition: "var(--t-hover)" }}>
56
56
  {depth > 0 ? <span style={{ color: "var(--text-muted)", display: "inline-flex", flexShrink: 0 }}><Icon name="chevron-right" size={11} /></span> : null}
57
57
  {label}
58
58
  </span>
@@ -24,10 +24,10 @@ function pickHidden(it) { open.value = false; emit('select', it) }
24
24
  function onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }
25
25
  watch(open, (v) => { if (v) document.addEventListener('mousedown', onDoc); else document.removeEventListener('mousedown', onDoc) })
26
26
  onUnmounted(() => document.removeEventListener('mousedown', onDoc))
27
- const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: isLast(it) || on(i, it) ? 'var(--text-heading)' : 'var(--text-secondary)', fontWeight: isLast(it) ? 'var(--weight-medium)' : 'var(--weight-regular)', cursor: isLast(it) ? 'default' : 'pointer', borderRadius: 'var(--radius-sm)', transition: 'var(--t-hover)' })
27
+ const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', color: isLast(it) || on(i, it) ? 'var(--text-heading)' : 'var(--text-secondary)', fontWeight: isLast(it) ? 'var(--weight-medium)' : 'var(--weight-regular)', cursor: isLast(it) ? 'default' : 'pointer', borderRadius: 'var(--radius-sm)', transition: 'var(--t-hover)' })
28
28
  </script>
29
29
  <template>
30
- <nav :style="{ display: 'flex', alignItems: 'center', gap: 4, ...style }">
30
+ <nav :style="{ display: 'flex', alignItems: 'center', gap: '4px', ...style }">
31
31
  <template v-for="(it, i) in head" :key="it">
32
32
  <span v-if="i > 0" style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
33
33
  <span :role="isLast(it) ? undefined : 'button'" :tabindex="isLast(it) ? undefined : 0"
@@ -40,14 +40,14 @@ const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(
40
40
  <span role="button" tabindex="0" :aria-label="`Show ${hidden.length} hidden levels`" :aria-expanded="open"
41
41
  @click="open = !open" @keydown.enter.prevent="open = !open" @keydown.space.prevent="open = !open" @keydown.esc="open = false"
42
42
  @mouseenter="ovHover = true" @mouseleave="ovHover = false"
43
- :style="{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '20px', height: '18px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', color: open || ovHover ? 'var(--text-heading)' : 'var(--text-muted)', background: open ? 'var(--bg-active)' : ovHover ? 'var(--bg-hover)' : 'transparent', transition: 'var(--t-hover)' }">
43
+ :style="{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '24px', height: '24px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', color: open || ovHover ? 'var(--text-heading)' : 'var(--text-muted)', background: open ? 'var(--bg-active)' : ovHover ? 'var(--bg-hover)' : 'transparent', transition: 'var(--t-hover)' }">
44
44
  <Icon name="ellipsis" :size="14" />
45
45
  </span>
46
46
  <span v-if="open" style="position:absolute;top:calc(100% + 5px);left:0;z-index:var(--z-menu);min-width:168px;max-width:260px;background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-md);box-shadow:var(--shadow-menu);padding:4px;display:flex;flex-direction:column">
47
47
  <span v-for="(h, hi) in hidden" :key="h" role="menuitem" tabindex="0"
48
48
  @click="pickHidden(h)" @keydown.enter.prevent="pickHidden(h)" @keydown.space.prevent="pickHidden(h)"
49
49
  @mouseenter="menuHover = hi" @mouseleave="menuHover = -1"
50
- :style="{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 9px', paddingLeft: (9 + hi * 12) + 'px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: menuHover === hi ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', transition: 'var(--t-hover)' }">
50
+ :style="{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 9px', paddingLeft: (9 + hi * 12) + 'px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: menuHover === hi ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', color: 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', transition: 'var(--t-hover)' }">
51
51
  <span v-if="hi > 0" style="color:var(--text-muted);display:inline-flex;flex-shrink:0"><Icon name="chevron-right" :size="11" /></span>{{ h }}
52
52
  </span>
53
53
  </span>
@@ -20,14 +20,14 @@ export function DocTree({ title = "Documents", items = [], active, onSelect, sty
20
20
  {items.map((it) => {
21
21
  const is = active === it.label;
22
22
  return (
23
- <div key={it.label} role="treeitem" tabIndex={0} onClick={() => onSelect && onSelect(it.label)}
24
- style={{ height: "var(--h-tree-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", boxSizing: "border-box", cursor: "pointer", background: is ? "var(--bg-active)" : "transparent" }}
23
+ <div key={it.label} role="treeitem" tabIndex={0} data-wash={is ? undefined : ""} onClick={() => onSelect && onSelect(it.label)}
24
+ style={{ height: "var(--h-tree-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", boxSizing: "border-box", cursor: "pointer", background: is ? "var(--bg-active)" : "var(--wash, transparent)", transition: "var(--t-hover)" }}
25
25
  onMouseEnter={(e) => { if (!is) e.currentTarget.style.background = "var(--bg-hover)"; }}
26
26
  onMouseLeave={(e) => { if (!is) e.currentTarget.style.background = "transparent"; }}>
27
27
  <span style={{ color: it.kind === "folder" ? "var(--text-muted)" : docHues[it.hue || "gray"], display: "inline-flex" }}>
28
28
  <Icon name={it.icon || (it.kind === "folder" ? "folder" : "file-text")} size={16} />
29
29
  </span>
30
- <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{it.label}</span>
30
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{it.label}</span>
31
31
  </div>
32
32
  );
33
33
  })}
@@ -15,10 +15,10 @@ const emit = defineEmits(['select'])
15
15
  </div>
16
16
  <div style="padding:12px 16px 8px;border-bottom:1px solid var(--border-subtle)"><SearchInput bare placeholder="Search..." style="gap:8px" /></div>
17
17
  <div style="padding:8px;display:flex;flex-direction:column;gap:2px;overflow-y:auto">
18
- <div v-for="it in items" :key="it.label" @click="emit('select', it.label)"
19
- :style="{ height: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: 8, padding: '6px 8px', boxSizing: 'border-box', cursor: 'pointer', background: active === it.label ? 'var(--bg-active)' : 'transparent' }">
18
+ <div v-for="it in items" :key="it.label" role="treeitem" tabindex="0" @click="emit('select', it.label)" @keydown.enter="emit('select', it.label)"
19
+ :style="{ height: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', boxSizing: 'border-box', cursor: 'pointer', background: active === it.label ? 'var(--bg-active)' : 'var(--wash, transparent)', transition: 'var(--t-hover)' }" :data-wash="active === it.label ? undefined : ''">
20
20
  <span :style="{ color: it.kind === 'folder' ? 'var(--text-muted)' : docHues[it.hue || 'gray'], display: 'inline-flex' }"><Icon :name="it.icon || (it.kind === 'folder' ? 'folder' : 'file-text')" :size="16" /></span>
21
- <span style="font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-heading);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ it.label }}</span>
21
+ <span style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-heading);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ it.label }}</span>
22
22
  </div>
23
23
  </div>
24
24
  </div>
@@ -2,7 +2,7 @@
2
2
  defineProps({ title: String, subtitle: String, style: Object })
3
3
  </script>
4
4
  <template>
5
- <div :style="{ display: 'flex', flexDirection: 'column', gap: 4, ...style }">
5
+ <div :style="{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }">
6
6
  <span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-lg);line-height:var(--leading-lg);letter-spacing:var(--tracking-lg);color:var(--text-primary)">{{ title }}</span>
7
7
  <span v-if="subtitle" style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-secondary)">{{ subtitle }}</span>
8
8
  </div>
@@ -1,15 +1,16 @@
1
1
  <script setup>
2
2
  import IconButton from '../buttons/IconButton.vue'
3
- defineProps({ title: String, status: { type: String, default: '• Editing' }, showBack: Boolean, style: Object })
3
+ // Page toolbar under the top bar: back · title · status, actions on the right. A size region (default inherit → md).
4
+ defineProps({ title: String, status: { type: String, default: '• Editing' }, showBack: Boolean, size: String, style: Object })
4
5
  const emit = defineEmits(['back'])
5
6
  </script>
6
7
  <template>
7
- <div :style="{ height: 'var(--topbar-h)', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 16px', boxSizing: 'border-box', flexShrink: 0, ...style }">
8
- <div style="display:flex;align-items:center;gap:8px">
9
- <IconButton v-if="showBack" icon="chevron-left" size="md" @click="emit('back')" />
8
+ <div :data-size="size" :style="{ height: 'var(--topbar-h)', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 var(--edge-pad)', boxSizing: 'border-box', flexShrink: 0, ...style }">
9
+ <div style="display:flex;align-items:center;gap:var(--control-gap)">
10
+ <IconButton v-if="showBack" icon="chevron-left" label="Back" @click="emit('back')" />
10
11
  <span style="font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)">{{ title }}</span>
11
- <span v-if="status" style="font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-muted);margin-left:6px">{{ status }}</span>
12
+ <span v-if="status" style="font-family:var(--font-sans);font-size:var(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking);color:var(--text-muted);margin-left:6px">{{ status }}</span>
12
13
  </div>
13
- <div style="display:flex;align-items:center;gap:8px"><slot name="actions" /></div>
14
+ <div style="display:flex;align-items:center;gap:var(--control-gap)"><slot name="actions" /></div>
14
15
  </div>
15
16
  </template>
@@ -30,7 +30,7 @@ export function Sidebar({ items = [], footerItems = [{ icon: "circle-help", labe
30
30
  {onToggleCollapse ? <NavItem icon="panel-left" label="Collapse" collapsed={collapsed} onClick={onToggleCollapse} /> : null}
31
31
  {items.map((it) => it.children ? (
32
32
  <div key={it.label}>
33
- <div onClick={() => toggleSection(it.label)} style={{ height: "var(--h-subnav-item)", display: "flex", alignItems: "center", gap: 10, padding: collapsed ? "6px 10px" : "6px 10px", justifyContent: collapsed ? "center" : "flex-start", cursor: "pointer", color: "var(--text-body)", borderRadius: "var(--radius-md)", boxSizing: "border-box" }}>
33
+ <div role="button" tabIndex={0} data-wash="" onClick={() => toggleSection(it.label)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggleSection(it.label); } }} style={{ background: "var(--wash, transparent)", transition: "var(--t-hover)", height: "var(--h-subnav-item)", display: "flex", alignItems: "center", gap: 10, padding: collapsed ? "6px 10px" : "6px 10px", justifyContent: collapsed ? "center" : "flex-start", cursor: "pointer", color: "var(--text-body)", borderRadius: "var(--radius-md)", boxSizing: "border-box" }}>
34
34
  <Icon name={it.icon} size={16} />
35
35
  {!collapsed ? <><span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", letterSpacing: "var(--tracking-base)", flex: 1 }}>{it.label}</span><Icon name={openSections.includes(it.label) ? "chevron-up" : "chevron-down"} size={14} /></> : null}
36
36
  </div>
@@ -16,11 +16,11 @@ const props = defineProps({
16
16
  const emit = defineEmits(['select', 'toggleCollapse'])
17
17
  const openSections = ref(props.items.filter(i => i.children && (i.open || i.children.some(c => c.label === props.active))).map(i => i.label))
18
18
  function toggleSection(l) { openSections.value = openSections.value.includes(l) ? openSections.value.filter(x => x !== l) : [...openSections.value, l] }
19
- function itemStyle(active, indent) { return { height: indent ? 'var(--h-subnav-item)' : 'var(--h-nav-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: 10, padding: indent ? '6px 10px' : '8px 10px', boxSizing: 'border-box', cursor: 'pointer', background: active ? 'var(--bg-active)' : 'transparent', color: active ? 'var(--text-heading)' : 'var(--text-body)', transition: 'var(--t-hover)' } }
19
+ function itemStyle(active, indent) { return { height: indent ? 'var(--h-subnav-item)' : 'var(--h-nav-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '10px', padding: indent ? '6px 10px' : '8px 10px', boxSizing: 'border-box', cursor: 'pointer', background: active ? 'var(--bg-active)' : 'transparent', color: active ? 'var(--text-heading)' : 'var(--text-body)', transition: 'var(--t-hover)' } }
20
20
  </script>
21
21
  <template>
22
22
  <aside :style="{ width: collapsed ? 'var(--rail-w)' : 'var(--sidebar-w)', height, background: 'var(--bg-surface)', borderRight: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, transition: 'width .15s ease', ...style }">
23
- <div :style="{ height: 'var(--topbar-h)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', gap: 10, padding: '0 16px', boxSizing: 'border-box', justifyContent: collapsed ? 'center' : 'flex-start', flexShrink: 0 }">
23
+ <div :style="{ height: 'var(--topbar-h)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', gap: '10px', padding: '0 16px', boxSizing: 'border-box', justifyContent: collapsed ? 'center' : 'flex-start', flexShrink: 0 }">
24
24
  <img :src="logo" alt="" style="width:24px;height:24px;object-fit:contain" />
25
25
  <span v-if="!collapsed" style="font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--leading-lg);letter-spacing:-.609px;color:var(--text-primary)">{{ brand }}</span>
26
26
  </div>
@@ -28,7 +28,7 @@ function itemStyle(active, indent) { return { height: indent ? 'var(--h-subnav-i
28
28
  <div v-if="canCollapse" role="button" tabindex="0" @click="emit('toggleCollapse')" :style="itemStyle(false, false)"><Icon name="panel-left" :size="16" /><span v-if="!collapsed" style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);white-space:nowrap;flex:1">Collapse</span></div>
29
29
  <template v-for="it in items" :key="it.label">
30
30
  <div v-if="it.children">
31
- <div role="button" tabindex="0" @click="toggleSection(it.label)" :style="{ height: 'var(--h-subnav-item)', display: 'flex', alignItems: 'center', gap: 10, padding: '6px 10px', justifyContent: collapsed ? 'center' : 'flex-start', cursor: 'pointer', color: 'var(--text-body)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box' }">
31
+ <div role="button" tabindex="0" data-wash="" @click="toggleSection(it.label)" @keydown.enter.prevent="toggleSection(it.label)" @keydown.space.prevent="toggleSection(it.label)" :style="{ background: 'var(--wash, transparent)', transition: 'var(--t-hover)', height: 'var(--h-subnav-item)', display: 'flex', alignItems: 'center', gap: '10px', padding: '6px 10px', justifyContent: collapsed ? 'center' : 'flex-start', cursor: 'pointer', color: 'var(--text-body)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box' }">
32
32
  <Icon :name="it.icon" :size="16" />
33
33
  <template v-if="!collapsed"><span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);flex:1">{{ it.label }}</span><Icon :name="openSections.includes(it.label) ? 'chevron-up' : 'chevron-down'" :size="14" /></template>
34
34
  </div>
@@ -1,4 +1,5 @@
1
1
  <script setup>
2
+ import { px } from '../px.js'
2
3
  import { ref, computed, watch, onMounted } from 'vue'
3
4
  import Icon from '../icons/Icon.vue'
4
5
  const props = defineProps({ size: { type: Number, default: 34 }, dark: { type: Boolean, default: null }, style: Object })
@@ -12,7 +13,7 @@ function toggle() { if (props.dark != null) emit('update:dark', !dark.value); el
12
13
  </script>
13
14
  <template>
14
15
  <button type="button" :aria-label="dark ? 'Switch to light mode' : 'Switch to dark mode'" @click="toggle" @mouseenter="hover = true" @mouseleave="hover = false"
15
- :style="{ width: size, height: size, borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: hover ? 'var(--bg-hover)' : 'transparent', color: 'var(--text-body)', padding: 0, transition: 'var(--t-hover)', ...style }">
16
+ :style="{ width: px(size), height: px(size), borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: hover ? 'var(--bg-hover)' : 'transparent', color: 'var(--text-body)', padding: 0, transition: 'var(--t-hover)', ...style }">
16
17
  <Icon :name="dark ? 'sparkles' : 'moon'" :size="18" />
17
18
  </button>
18
19
  </template>
@@ -15,10 +15,12 @@ export interface PageToolbarProps {
15
15
  status?: string;
16
16
  actions?: React.ReactNode;
17
17
  onBack?: () => void;
18
+ /** Size tier for the toolbar's controls. Omit to inherit (the default). */
19
+ size?: "sm" | "md" | "lg";
18
20
  style?: React.CSSProperties;
19
21
  }
20
22
  export declare function PageToolbar(props: PageToolbarProps): JSX.Element;
21
- /** 15px/500 page title + muted subtitle. */
23
+ /** Page title (--text-lg 20px/500) + muted subtitle. */
22
24
  export interface PageHeadingProps {
23
25
  title?: string;
24
26
  subtitle?: string;
@@ -20,15 +20,15 @@ export function TopBar({ placeholder = "Search documents, workflows, data...", a
20
20
  }
21
21
 
22
22
  /** Editor toolbar: back chevron, 14px/600 title, "• Editing" status, right action slot. */
23
- export function PageToolbar({ title, status = "• Editing", actions, onBack, style }) {
23
+ export function PageToolbar({ title, status = "• Editing", actions, onBack, size, style }) {
24
24
  return (
25
- <div style={{ height: "var(--topbar-h)", background: "var(--bg-surface)", borderBottom: "1px solid var(--border-card)", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 16px", boxSizing: "border-box", flexShrink: 0, ...style }}>
26
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
27
- {onBack ? <IconButton icon="chevron-left" size="md" onClick={onBack} /> : null}
25
+ <div data-size={size} style={{ height: "var(--topbar-h)", background: "var(--bg-surface)", borderBottom: "1px solid var(--border-card)", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 var(--edge-pad)", boxSizing: "border-box", flexShrink: 0, ...style }}>
26
+ <div style={{ display: "flex", alignItems: "center", gap: "var(--control-gap)" }}>
27
+ {onBack ? <IconButton icon="chevron-left" onClick={onBack} label="Back" /> : null}
28
28
  <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--text-md)", lineHeight: "var(--leading-md)", letterSpacing: "var(--tracking-md)", color: "var(--text-heading)" }}>{title}</span>
29
- {status ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-muted)", marginLeft: 6 }}>{status}</span> : null}
29
+ {status ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-muted)", marginLeft: 6 }}>{status}</span> : null}
30
30
  </div>
31
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>{actions}</div>
31
+ <div style={{ display: "flex", alignItems: "center", gap: "var(--control-gap)" }}>{actions}</div>
32
32
  </div>
33
33
  );
34
34
  }
@@ -153,7 +153,7 @@ function Row({ item, state, cfg, api }) {
153
153
  {chevron}
154
154
  {cfg.checkable && node.checkable !== false ? (
155
155
  <span onClick={(e) => e.stopPropagation()} style={{ display: "inline-flex", flexShrink: 0 }}>
156
- <Checkbox size="sm" checked={checked} indeterminate={half} disabled={disabled || node.disableCheckbox} onChange={() => api.check(key)} />
156
+ <Checkbox checked={checked} indeterminate={half} disabled={disabled || node.disableCheckbox} onChange={() => api.check(key)} />
157
157
  </span>
158
158
  ) : null}
159
159
  {cfg.showIcon ? (
@@ -161,11 +161,11 @@ function Row({ item, state, cfg, api }) {
161
161
  <Icon name={node.icon || (branch ? (open ? "folder-open" : "folder") : "file-text")} size={15} />
162
162
  </span>
163
163
  ) : null}
164
- <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", fontWeight: selected ? "var(--weight-medium)" : "var(--weight-regular)", color: disabled ? "var(--text-muted)" : "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
164
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", fontWeight: selected ? "var(--weight-medium)" : "var(--weight-regular)", color: disabled ? "var(--text-muted)" : "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
165
165
  <Highlight text={titleOf(node)} query={cfg.searchValue} />
166
166
  </span>
167
- {node.tag ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-2xs)", lineHeight: 1, padding: "2px 5px", borderRadius: "var(--radius-sm)", background: "var(--bg-inset)", color: "var(--text-secondary)", flexShrink: 0 }}>{node.tag}</span> : null}
168
- {node.count != null ? <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--text-muted)", paddingLeft: 8 }}>{node.count}</span> : null}
167
+ {node.tag ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: 1, padding: "2px 5px", borderRadius: "var(--radius-sm)", background: "var(--bg-inset)", color: "var(--text-secondary)", flexShrink: 0 }}>{node.tag}</span> : null}
168
+ {node.count != null ? <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-muted)", paddingLeft: 8 }}>{node.count}</span> : null}
169
169
  {cfg.rowActions && !disabled ? <span style={{ marginLeft: node.count != null ? 6 : "auto", display: "flex", gap: 2, opacity: hover ? 1 : 0, transition: "var(--t-hover)" }} onClick={(e) => e.stopPropagation()}>{cfg.rowActions(node)}</span> : null}
170
170
  </div>
171
171
  {open && loading && !kids.length ? (
@@ -340,7 +340,7 @@ export function Tree({
340
340
  <div role="tree" aria-multiselectable={multiple || undefined} style={{ display: "flex", flexDirection: "column", gap: showLine ? 0 : 1, ...style }}>
341
341
  <style>{"@keyframes kb-tree-spin{to{transform:rotate(360deg)}}"}</style>
342
342
  {rows.length === 0 ? (
343
- <div style={{ padding: "10px 8px", fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-muted)" }}>No matching items</div>
343
+ <div style={{ padding: "10px 8px", fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", color: "var(--text-muted)" }}>No matching items</div>
344
344
  ) : rows.map((r) => <Row key={r.key} item={r} state={state} cfg={cfg} api={api} />)}
345
345
  </div>
346
346
  );