@keyfunc/keybricks-ds 1.0.3 → 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 (358) hide show
  1. package/DEPLOY.md +4 -4
  2. package/DESIGN.md +154 -53
  3. package/INTEGRATION.md +176 -36
  4. package/SKILL.md +6 -4
  5. package/_adherence.oxlintrc.json +183 -72
  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 +46 -0
  80. package/components/_cards/search.card.html +5 -2
  81. package/components/_cards/segmented-control.card.html +39 -0
  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 +45 -56
  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/_shared/view-source.js +9 -4
  106. package/components/_vue/index.mjs +9 -5
  107. package/components/buttons/ActionChip.d.ts +3 -1
  108. package/components/buttons/ActionChip.jsx +4 -4
  109. package/components/buttons/ActionChip.vue +4 -4
  110. package/components/buttons/Button.d.ts +5 -2
  111. package/components/buttons/Button.jsx +15 -18
  112. package/components/buttons/Button.vue +17 -18
  113. package/components/buttons/ButtonGroup.vue +1 -1
  114. package/components/buttons/IconButton.d.ts +2 -1
  115. package/components/buttons/IconButton.jsx +9 -7
  116. package/components/buttons/IconButton.vue +10 -8
  117. package/components/buttons/buttons.prompt.md +6 -5
  118. package/components/charts/Bar3D.jsx +2 -2
  119. package/components/charts/Bar3D.vue +2 -2
  120. package/components/charts/BarChart.jsx +4 -4
  121. package/components/charts/BarChart.vue +4 -4
  122. package/components/charts/BarList.jsx +1 -1
  123. package/components/charts/BarList.vue +1 -1
  124. package/components/charts/BoxPlot.jsx +1 -1
  125. package/components/charts/BoxPlot.vue +2 -2
  126. package/components/charts/BulletChart.jsx +1 -1
  127. package/components/charts/BulletChart.vue +1 -1
  128. package/components/charts/CandlestickChart.jsx +2 -2
  129. package/components/charts/CandlestickChart.vue +3 -3
  130. package/components/charts/ChartKit.jsx +5 -5
  131. package/components/charts/ChartLegend.vue +16 -0
  132. package/components/charts/ChartTooltip.vue +16 -0
  133. package/components/charts/ChordChart.jsx +2 -2
  134. package/components/charts/ChordChart.vue +2 -2
  135. package/components/charts/GanttChart.jsx +2 -2
  136. package/components/charts/GanttChart.vue +2 -2
  137. package/components/charts/GaugeChart.jsx +2 -2
  138. package/components/charts/GaugeChart.vue +2 -2
  139. package/components/charts/HeatmapChart.jsx +2 -2
  140. package/components/charts/HeatmapChart.vue +2 -2
  141. package/components/charts/Histogram.jsx +2 -2
  142. package/components/charts/Histogram.vue +3 -3
  143. package/components/charts/LineChart.jsx +1 -1
  144. package/components/charts/LineChart.vue +3 -3
  145. package/components/charts/ParetoChart.jsx +3 -3
  146. package/components/charts/ParetoChart.vue +4 -4
  147. package/components/charts/PlotGrid.vue +18 -0
  148. package/components/charts/RadarChart.jsx +1 -1
  149. package/components/charts/RadarChart.vue +1 -1
  150. package/components/charts/Ribbon3D.jsx +2 -2
  151. package/components/charts/Ribbon3D.vue +2 -2
  152. package/components/charts/SankeyChart.jsx +2 -2
  153. package/components/charts/SankeyChart.vue +2 -2
  154. package/components/charts/Scatter3D.jsx +1 -1
  155. package/components/charts/Scatter3D.vue +1 -1
  156. package/components/charts/ScatterChart.jsx +3 -3
  157. package/components/charts/ScatterChart.vue +4 -4
  158. package/components/charts/Sparkline.jsx +1 -1
  159. package/components/charts/Sparkline.vue +1 -1
  160. package/components/charts/SunburstChart.jsx +1 -1
  161. package/components/charts/SunburstChart.vue +1 -1
  162. package/components/charts/Surface3D.jsx +2 -2
  163. package/components/charts/Surface3D.vue +2 -2
  164. package/components/charts/TreemapChart.jsx +2 -2
  165. package/components/charts/TreemapChart.vue +2 -2
  166. package/components/charts/WaterfallChart.jsx +2 -2
  167. package/components/charts/WaterfallChart.vue +3 -3
  168. package/components/charts/WorldMap.jsx +2 -2
  169. package/components/charts/WorldMap.vue +2 -2
  170. package/components/charts/XLabels.vue +11 -0
  171. package/components/data/CodeBlock.jsx +1 -1
  172. package/components/data/CodeBlock.vue +1 -1
  173. package/components/data/DiffValue.jsx +1 -1
  174. package/components/data/DiffValue.vue +1 -1
  175. package/components/data/FilterBar.d.ts +2 -0
  176. package/components/data/FilterBar.jsx +10 -9
  177. package/components/data/FilterBar.vue +9 -8
  178. package/components/data/KeyValue.jsx +3 -3
  179. package/components/data/KeyValue.vue +3 -3
  180. package/components/data/ListRow.jsx +2 -2
  181. package/components/data/ListRow.vue +2 -2
  182. package/components/data/Pagination.d.ts +5 -1
  183. package/components/data/Pagination.jsx +16 -12
  184. package/components/data/Pagination.vue +14 -9
  185. package/components/data/SmartTable.d.ts +4 -1
  186. package/components/data/SmartTable.jsx +16 -14
  187. package/components/data/SmartTable.vue +25 -22
  188. package/components/data/Spreadsheet.d.ts +2 -0
  189. package/components/data/Spreadsheet.jsx +12 -11
  190. package/components/data/Spreadsheet.vue +14 -13
  191. package/components/data/Table.d.ts +47 -0
  192. package/components/data/Table.jsx +85 -0
  193. package/components/data/Table.vue +63 -0
  194. package/components/data/data.prompt.md +3 -3
  195. package/components/display/Avatar.vue +3 -2
  196. package/components/display/AvatarGroup.vue +3 -2
  197. package/components/display/Badge.vue +1 -1
  198. package/components/display/Divider.vue +3 -3
  199. package/components/display/Donut.vue +3 -2
  200. package/components/display/IconTile.vue +2 -1
  201. package/components/display/Kbd.vue +1 -1
  202. package/components/display/ProgressBar.jsx +1 -1
  203. package/components/display/ProgressBar.vue +1 -1
  204. package/components/display/StatCard.vue +1 -1
  205. package/components/display/Tag.d.ts +3 -1
  206. package/components/display/Tag.jsx +4 -4
  207. package/components/display/Tag.vue +4 -4
  208. package/components/feedback/Accordion.jsx +2 -2
  209. package/components/feedback/Accordion.vue +2 -2
  210. package/components/feedback/ActivityItem.d.ts +3 -1
  211. package/components/feedback/ActivityItem.jsx +5 -5
  212. package/components/feedback/ActivityItem.vue +5 -4
  213. package/components/feedback/Alert.jsx +4 -4
  214. package/components/feedback/Alert.vue +3 -3
  215. package/components/feedback/Banner.jsx +5 -5
  216. package/components/feedback/Banner.vue +5 -5
  217. package/components/feedback/ContextMenu.d.ts +3 -1
  218. package/components/feedback/ContextMenu.jsx +5 -5
  219. package/components/feedback/ContextMenu.vue +5 -5
  220. package/components/feedback/DotActivity.vue +3 -3
  221. package/components/feedback/Drawer.d.ts +2 -0
  222. package/components/feedback/Drawer.jsx +6 -6
  223. package/components/feedback/Drawer.vue +22 -15
  224. package/components/feedback/EmptyState.vue +2 -2
  225. package/components/feedback/LogViewer.vue +1 -1
  226. package/components/feedback/Modal.d.ts +2 -0
  227. package/components/feedback/Modal.jsx +12 -11
  228. package/components/feedback/Modal.vue +10 -8
  229. package/components/feedback/Skeleton.vue +2 -1
  230. package/components/feedback/Spinner.vue +2 -1
  231. package/components/feedback/Stepper.vue +1 -1
  232. package/components/feedback/Timeline.jsx +1 -1
  233. package/components/feedback/Timeline.vue +1 -1
  234. package/components/feedback/Toast.vue +4 -4
  235. package/components/feedback/ToastStack.vue +11 -0
  236. package/components/feedback/Tooltip.vue +19 -0
  237. package/components/feedback/feedback.prompt.md +4 -4
  238. package/components/forms/Checkbox.d.ts +3 -2
  239. package/components/forms/Checkbox.jsx +7 -7
  240. package/components/forms/Checkbox.vue +8 -8
  241. package/components/forms/Combobox.d.ts +2 -0
  242. package/components/forms/Combobox.jsx +12 -10
  243. package/components/forms/Combobox.vue +11 -10
  244. package/components/forms/DatePicker.d.ts +4 -0
  245. package/components/forms/DatePicker.jsx +16 -12
  246. package/components/forms/DatePicker.vue +15 -11
  247. package/components/forms/Dropzone.jsx +2 -2
  248. package/components/forms/Dropzone.vue +5 -5
  249. package/components/forms/Field.d.ts +5 -1
  250. package/components/forms/Field.jsx +4 -4
  251. package/components/forms/Field.vue +4 -4
  252. package/components/forms/Input.d.ts +5 -1
  253. package/components/forms/Input.jsx +7 -7
  254. package/components/forms/Input.vue +3 -3
  255. package/components/forms/MultiSelect.d.ts +2 -0
  256. package/components/forms/MultiSelect.jsx +13 -11
  257. package/components/forms/MultiSelect.vue +12 -11
  258. package/components/forms/NumberStepper.d.ts +3 -1
  259. package/components/forms/NumberStepper.jsx +7 -7
  260. package/components/forms/NumberStepper.vue +8 -7
  261. package/components/forms/Radio.d.ts +30 -0
  262. package/components/forms/Radio.jsx +65 -0
  263. package/components/forms/Radio.vue +17 -9
  264. package/components/forms/RadioGroup.vue +22 -4
  265. package/components/forms/SearchInput.d.ts +3 -1
  266. package/components/forms/SearchInput.jsx +6 -6
  267. package/components/forms/SearchInput.vue +7 -6
  268. package/components/forms/Select.d.ts +3 -2
  269. package/components/forms/Select.jsx +6 -7
  270. package/components/forms/Select.vue +6 -6
  271. package/components/forms/Slider.jsx +3 -3
  272. package/components/forms/Slider.vue +3 -3
  273. package/components/forms/Switch.d.ts +32 -24
  274. package/components/forms/Switch.jsx +85 -35
  275. package/components/forms/Switch.vue +57 -11
  276. package/components/forms/Textarea.vue +4 -4
  277. package/components/forms/forms.prompt.md +4 -1
  278. package/components/forms/input-base.js +2 -1
  279. package/components/index.d.ts +2 -2
  280. package/components/index.js +5 -5
  281. package/components/navigation/Breadcrumb.jsx +3 -3
  282. package/components/navigation/Breadcrumb.vue +4 -4
  283. package/components/navigation/DocTree.jsx +3 -3
  284. package/components/navigation/DocTree.vue +3 -3
  285. package/components/navigation/PageHeading.vue +1 -1
  286. package/components/navigation/PageToolbar.vue +7 -6
  287. package/components/navigation/Sidebar.jsx +1 -1
  288. package/components/navigation/Sidebar.vue +3 -3
  289. package/components/navigation/ThemeToggle.vue +2 -1
  290. package/components/navigation/TopBar.d.ts +3 -1
  291. package/components/navigation/TopBar.jsx +6 -6
  292. package/components/navigation/Tree.jsx +5 -5
  293. package/components/navigation/Tree.vue +5 -5
  294. package/components/pipeline/ConfigPanel.d.ts +2 -0
  295. package/components/pipeline/ConfigPanel.jsx +9 -9
  296. package/components/pipeline/ConfigPanel.vue +9 -9
  297. package/components/pipeline/DataPreview.d.ts +3 -1
  298. package/components/pipeline/DataPreview.jsx +7 -7
  299. package/components/pipeline/DataPreview.vue +7 -7
  300. package/components/pipeline/NodeCard.jsx +3 -3
  301. package/components/pipeline/NodeCard.vue +3 -3
  302. package/components/pipeline/PaletteItem.vue +2 -2
  303. package/components/pipeline/PaletteListItem.vue +1 -1
  304. package/components/pipeline/PalettePanel.jsx +2 -2
  305. package/components/pipeline/PalettePanel.vue +1 -1
  306. package/components/pipeline/Port.vue +15 -0
  307. package/components/pipeline/PortLabel.vue +15 -0
  308. package/components/pipeline/ZoomControls.vue +1 -1
  309. package/components/pipeline/port-skin.js +13 -0
  310. package/components/px.js +4 -0
  311. package/components/tabs/PanelTabs.vue +5 -4
  312. package/components/tabs/PillTabs.vue +5 -4
  313. package/components/tabs/SegmentedControl.d.ts +11 -4
  314. package/components/tabs/SegmentedControl.jsx +25 -10
  315. package/components/tabs/SegmentedControl.vue +19 -7
  316. package/components/tabs/Tabs.d.ts +5 -3
  317. package/components/tabs/Tabs.jsx +15 -15
  318. package/components/tabs/Tabs.vue +5 -5
  319. package/components/tabs/ViewToggle.d.ts +3 -1
  320. package/components/tabs/ViewToggle.jsx +5 -5
  321. package/components/tabs/ViewToggle.vue +5 -5
  322. package/components/tabs/tabs.prompt.md +4 -2
  323. package/keybricks.css +4 -0
  324. package/package.json +6 -3
  325. package/readme.md +90 -123
  326. package/readme.zh-CN.md +120 -0
  327. package/tokens/charts.css +20 -17
  328. package/tokens/colors.css +2 -2
  329. package/tokens/dark.css +2 -2
  330. package/tokens/hover.css +32 -0
  331. package/tokens/interaction.css +3 -1
  332. package/tokens/layout.css +29 -7
  333. package/tokens/tokens.d.ts +36 -0
  334. package/tokens/tokens.js +143 -89
  335. package/tokens/tokens.json +240 -106
  336. package/tokens/typography.css +27 -14
  337. package/components/_cards/brand-visual.card.html +0 -43
  338. package/components/_cards/datatable.card.html +0 -48
  339. package/components/_shared/src-brand.js +0 -1
  340. package/components/brand/BrandVisual.d.ts +0 -17
  341. package/components/brand/BrandVisual.jsx +0 -29
  342. package/components/brand/BrandVisual.vue +0 -34
  343. package/components/brand/_dir.card.html +0 -13
  344. package/components/brand/brandMotifs.js +0 -210
  345. package/components/buttons/_dir.card.html +0 -13
  346. package/components/charts/ChartKit.vue +0 -47
  347. package/components/charts/_dir.card.html +0 -7
  348. package/components/data/DataTable.d.ts +0 -28
  349. package/components/data/DataTable.jsx +0 -32
  350. package/components/data/DataTable.vue +0 -25
  351. package/components/data/_dir.card.html +0 -13
  352. package/components/display/_dir.card.html +0 -13
  353. package/components/feedback/_dir.card.html +0 -13
  354. package/components/forms/_dir.card.html +0 -13
  355. package/components/icons/_dir.card.html +0 -13
  356. package/components/navigation/_dir.card.html +0 -13
  357. package/components/pipeline/_dir.card.html +0 -13
  358. package/components/tabs/_dir.card.html +0 -13
@@ -26,14 +26,14 @@ const g = computed(() => {
26
26
  <template v-if="axes">
27
27
  <g v-for="t in g.ticks" :key="t">
28
28
  <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"/>
29
- <text :x="g.pad.l-8" :y="g.sy(t)+3" text-anchor="end" style="font-family:var(--font-mono);font-size:10px;fill:var(--chart-label)">{{ valueFormat(t) }}</text>
29
+ <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>
30
30
  </g>
31
31
  </template>
32
32
  <g v-for="(b,i) in g.out" :key="i" :opacity="hover==null||hover===i?1:0.45" style="transition:opacity var(--dur-fast) var(--ease-out)">
33
33
  <rect :x="b.x-b.bw/2" :y="b.y" :width="b.bw" :height="b.h" rx="2" :fill="b.fill"/>
34
34
  <line v-if="!b.last" :x1="b.x+b.bw/2" :x2="b.nextX" :y1="b.run" :y2="b.run" stroke="var(--chart-guide)" stroke-width="1" stroke-dasharray="2 3"/>
35
- <text v-if="showValues" :x="b.x" :y="b.y-5" text-anchor="middle" :style="{fontFamily:'var(--font-mono)',fontSize:'10px',fill:b.kind==='neg'?'var(--chart-neg)':'var(--text-heading)'}">{{ (b.kind==='pos'?'+':'')+valueFormat(b.value) }}</text>
36
- <text :x="b.x" :y="height-g.pad.b+15" text-anchor="middle" style="font-family:var(--font-sans);font-size:10px;fill:var(--chart-label)">
35
+ <text v-if="showValues" :x="b.x" :y="b.y-5" text-anchor="middle" :style="{fontFamily:'var(--font-mono)',fontSize:'var(--text-2xs)',fill:b.kind==='neg'?'var(--chart-neg)':'var(--text-heading)'}">{{ (b.kind==='pos'?'+':'')+valueFormat(b.value) }}</text>
36
+ <text :x="b.x" :y="height-g.pad.b+15" text-anchor="middle" style="font-family:var(--font-sans);font-size:var(--text-2xs);fill:var(--chart-label)">
37
37
  <tspan v-for="(ln,li) in b.lines" :key="li" :x="b.x" :dy="li?11:0">{{ ln }}</tspan>
38
38
  </text>
39
39
  </g>
@@ -91,11 +91,11 @@ export function WorldMap({ values = {}, width = 660, height = 340, projection =
91
91
  </svg>
92
92
  {legend ? (
93
93
  <div style={{ display: "flex", alignItems: "center", gap: 6, paddingTop: 6, lineHeight: "normal" }}>
94
- <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--chart-label)" }}>{valueFormat(lo)}</span>
94
+ <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--chart-label)" }}>{valueFormat(lo)}</span>
95
95
  {(scale === "diverging" ? ["var(--chart-neg)", "color-mix(in srgb, var(--chart-neg) 40%, var(--bg-surface))", "var(--chart-track)", "color-mix(in srgb, var(--chart-pos) 40%, var(--bg-surface))", "var(--chart-pos)"] : HEAT).map((c, i) => (
96
96
  <span key={i} style={{ width: 22, height: 8, borderRadius: 2, background: c }} />
97
97
  ))}
98
- <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--chart-label)" }}>{valueFormat(hi)}{unit ? " " + unit : ""}</span>
98
+ <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--chart-label)" }}>{valueFormat(hi)}{unit ? " " + unit : ""}</span>
99
99
  </div>
100
100
  ) : null}
101
101
  {h && interactive ? (
@@ -61,9 +61,9 @@ const swatches = computed(() => props.scale==='diverging'
61
61
  <path v-if="hover!=null" :d="paths[hover].d" fill="none" stroke="var(--action-primary)" stroke-width="1.4" pointer-events="none"/>
62
62
  </svg>
63
63
  <div v-if="legend" style="display:flex;align-items:center;gap:6px;padding-top:6px;line-height:normal">
64
- <span style="font-family:var(--font-mono);font-size:10px;color:var(--chart-label)">{{ valueFormat(lo) }}</span>
64
+ <span style="font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--chart-label)">{{ valueFormat(lo) }}</span>
65
65
  <span v-for="(c,i) in swatches" :key="i" :style="{width:'22px',height:'8px',borderRadius:'2px',background:c}"/>
66
- <span style="font-family:var(--font-mono);font-size:10px;color:var(--chart-label)">{{ valueFormat(hi) }}{{ unit ? ' '+unit : '' }}</span>
66
+ <span style="font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--chart-label)">{{ valueFormat(hi) }}{{ unit ? ' '+unit : '' }}</span>
67
67
  </div>
68
68
  <div v-if="hover!=null && interactive" :style="{position:'absolute',left:paths[hover].c[0]+'px',top:paths[hover].c[1]+'px',transform:paths[hover].c[0]>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'}">
69
69
  <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px">{{ paths[hover].name }}</div>
@@ -0,0 +1,11 @@
1
+ <script setup>
2
+ // Thin x-axis category labels only (small charts with no y axis). Render inside an <svg>.
3
+ defineProps({ h: Number, pad: Object, xLabels: { type: Array, default: () => [] }, sxLabel: Function })
4
+ </script>
5
+ <template>
6
+ <g>
7
+ <template v-for="(lb, i) in xLabels" :key="i">
8
+ <text v-if="lb != null" :x="sxLabel(i)" :y="h - pad.b + 16" text-anchor="middle" style="font-family:var(--font-sans);font-size:var(--text-2xs);fill:var(--chart-label)">{{ lb }}</text>
9
+ </template>
10
+ </g>
11
+ </template>
@@ -9,7 +9,7 @@ export function CodeBlock({ code = "", language, height, style }) {
9
9
  <div style={{ background: "var(--bg-inset)", border: "1px solid var(--border-subtle)", borderRadius: "var(--radius-md)", ...style }}>
10
10
  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "7px 10px", borderBottom: "1px solid var(--border-subtle)" }}>
11
11
  <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-muted)" }}>{language || "code"}</span>
12
- <button type="button" onClick={copy} style={{ display: "flex", alignItems: "center", gap: 4, border: "none", background: "transparent", cursor: "pointer", color: copied ? "var(--success-strong)" : "var(--text-secondary)", fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", padding: 0 }}>
12
+ <button type="button" onClick={copy} data-ink="" style={{ display: "flex", alignItems: "center", gap: 4, border: "none", background: "transparent", cursor: "pointer", color: copied ? "var(--success-strong)" : "var(--ink, var(--text-secondary))", fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", padding: 0 }}>
13
13
  <Icon name={copied ? "check" : "copy"} size={12} />{copied ? "Copied" : "Copy"}
14
14
  </button>
15
15
  </div>
@@ -13,7 +13,7 @@ function copy() {
13
13
  <div :style="{ background: 'var(--bg-inset)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', ...style }">
14
14
  <div style="display:flex;justify-content:space-between;align-items:center;padding:7px 10px;border-bottom:1px solid var(--border-subtle)">
15
15
  <span style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-muted)">{{ language || 'code' }}</span>
16
- <button type="button" @click="copy" :style="{ display: 'flex', alignItems: 'center', gap: 4, border: 'none', background: 'transparent', cursor: 'pointer', color: copied ? 'var(--success-strong)' : 'var(--text-secondary)', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', padding: 0 }">
16
+ <button type="button" @click="copy" data-ink="" :style="{ display: 'flex', alignItems: 'center', gap: '4px', border: 'none', background: 'transparent', cursor: 'pointer', color: copied ? 'var(--success-strong)' : 'var(--ink, var(--text-secondary))', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', padding: 0 }">
17
17
  <Icon :name="copied ? 'check' : 'copy'" :size="12" />{{ copied ? 'Copied' : 'Copy' }}
18
18
  </button>
19
19
  </div>
@@ -6,7 +6,7 @@ const toneColor = { neutral: "var(--text-heading)", warning: "var(--warning-text
6
6
  /** Before → after value change ("USD → EUR"). tone colors the new value; from renders muted + struck through when both sides are given. */
7
7
  export function DiffValue({ from, to, tone = "warning", mono = true, style }) {
8
8
  return (
9
- <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: "var(--text-sm)", ...style }}>
9
+ <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: "var(--control-text)", ...style }}>
10
10
  {from != null ? <span style={{ color: "var(--text-muted)", textDecoration: "line-through" }}>{from}</span> : null}
11
11
  <Icon name="arrow-right" size={12} style={{ color: "var(--text-muted)" }} />
12
12
  <span style={{ color: toneColor[tone], fontWeight: "var(--weight-medium)" }}>{to}</span>
@@ -5,7 +5,7 @@ const toneColor = { neutral: 'var(--text-heading)', warning: 'var(--warning-text
5
5
  defineProps({ from: [String, Number], to: [String, Number], tone: { type: String, default: 'warning' }, mono: { type: Boolean, default: true }, style: Object })
6
6
  </script>
7
7
  <template>
8
- <span :style="{ display: 'inline-flex', alignItems: 'center', gap: 6, fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: 'var(--text-sm)', ...style }">
8
+ <span :style="{ display: 'inline-flex', alignItems: 'center', gap: '6px', fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: 'var(--control-text)', ...style }">
9
9
  <span v-if="from != null" style="color:var(--text-muted);text-decoration:line-through">{{ from }}</span>
10
10
  <Icon name="arrow-right" :size="12" style="color:var(--text-muted)" />
11
11
  <span :style="{ color: toneColor[tone], fontWeight: 'var(--weight-medium)' }">{{ to }}</span>
@@ -9,6 +9,8 @@ export interface FilterBarProps {
9
9
  /** Count shown in "Adapt Filters (n)" */
10
10
  adaptCount?: number;
11
11
  defaultExpanded?: boolean;
12
+ /** Size tier for the whole bar and every field in it. Omit to inherit the enclosing region's tier (the default). */
13
+ size?: "sm" | "md" | "lg";
12
14
  onGo?: () => void;
13
15
  onAdapt?: () => void;
14
16
  children?: React.ReactNode;
@@ -2,21 +2,22 @@ import React from "react";
2
2
  import { Icon } from "../icons/Icon.jsx";
3
3
  import { Button } from "../buttons/Button.jsx";
4
4
 
5
- /** SAP Fiori-style list-report filter bar: variant title row with expand/collapse, a wrapping row of labeled filter fields (pass Field-wrapped controls as children), and Adapt Filters (n) + Go actions. */
6
- export function FilterBar({ variant = "Standard", modified, adaptCount, defaultExpanded = true, onGo, onAdapt, children, style }) {
5
+ /** SAP Fiori-style list-report filter bar: variant title row with expand/collapse, a wrapping row of labeled filter fields (pass Field-wrapped controls as children), and Adapt Filters (n) + Go actions.
6
+ * A size region: `size` (default inherit → md) sets the title bar height, the fields' tier and the gap between fields (--group-gap) so every control in it matches. */
7
+ export function FilterBar({ variant = "Standard", modified, adaptCount, defaultExpanded = true, size, onGo, onAdapt, children, style }) {
7
8
  const [open, setOpen] = React.useState(defaultExpanded);
8
9
  return (
9
- <div style={{ background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-lg)", padding: "10px 16px 0", boxSizing: "border-box", ...style }}>
10
- <div style={{ display: "flex", alignItems: "center", gap: 6, paddingBottom: 10 }}>
11
- <button type="button" onClick={() => setOpen(!open)} style={{ display: "flex", alignItems: "center", gap: 6, border: "none", background: "transparent", cursor: "pointer", padding: 0, color: "var(--text-heading)", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", letterSpacing: "var(--tracking-base)" }}>
10
+ <div data-size={size} style={{ background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-lg)", boxSizing: "border-box", ...style }}>
11
+ <div style={{ display: "flex", alignItems: "center", gap: "var(--control-gap)", minHeight: "var(--bar-card-h)", padding: "0 var(--edge-pad)", boxSizing: "border-box" }}>
12
+ <button type="button" aria-expanded={open} onClick={() => setOpen(!open)} style={{ display: "flex", alignItems: "center", gap: 6, border: "none", background: "transparent", cursor: "pointer", padding: 0, color: "var(--text-heading)", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)" }}>
12
13
  {variant}{modified && <span style={{ color: "var(--text-muted)", fontWeight: "var(--weight-regular)" }}>*</span>}
13
- <span style={{ color: "var(--text-muted)", display: "inline-flex", transform: open ? "rotate(180deg)" : "none", transition: "transform var(--dur-fast) var(--ease-out)" }}><Icon name="chevron-down" size={14} /></span>
14
+ <span style={{ color: "var(--text-muted)", display: "inline-flex", transform: open ? "rotate(180deg)" : "none", transition: "transform var(--dur-fast) var(--ease-out)" }}><Icon name="chevron-down" size={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /></span>
14
15
  </button>
15
16
  <span style={{ flex: 1 }}></span>
16
- <Button variant="text" size="sm" onClick={onAdapt}>Adapt Filters{adaptCount ? ` (${adaptCount})` : ""}</Button>
17
- <Button size="sm" onClick={onGo}>Go</Button>
17
+ <Button variant="text" onClick={onAdapt}>Adapt Filters{adaptCount ? ` (${adaptCount})` : ""}</Button>
18
+ <Button onClick={onGo}>Go</Button>
18
19
  </div>
19
- {open && <div style={{ display: "flex", flexWrap: "wrap", gap: "12px 16px", paddingBottom: 14, borderTop: "1px solid var(--border-subtle)", paddingTop: 12 }}>{children}</div>}
20
+ {open && <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end", gap: "var(--control-gap) var(--group-gap)", padding: "12px var(--edge-pad) var(--edge-pad)", borderTop: "1px solid var(--border-subtle)" }}>{children}</div>}
20
21
  </div>
21
22
  );
22
23
  }
@@ -3,21 +3,22 @@ import { ref } from 'vue'
3
3
  import Icon from '../icons/Icon.vue'
4
4
  import Button from '../buttons/Button.vue'
5
5
  // SAP Fiori-style list-report filter bar: variant title + expand/collapse, wrapping labeled filter fields (slot), Adapt Filters (n) + Go.
6
- const props = defineProps({ variant: { type: String, default: 'Standard' }, modified: Boolean, adaptCount: Number, defaultExpanded: { type: Boolean, default: true }, style: Object })
6
+ // A size region: `size` (default inherit → md) sets the title bar height, the fields' tier and the gap between fields (--group-gap) so every control in it matches.
7
+ const props = defineProps({ variant: { type: String, default: 'Standard' }, modified: Boolean, adaptCount: Number, defaultExpanded: { type: Boolean, default: true }, size: String, style: Object })
7
8
  const emit = defineEmits(['go', 'adapt'])
8
9
  const open = ref(props.defaultExpanded)
9
10
  </script>
10
11
  <template>
11
- <div :style="{ background: 'var(--bg-surface)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', padding: '10px 16px 0', boxSizing: 'border-box', ...style }">
12
- <div style="display:flex;align-items:center;gap:6px;padding-bottom:10px">
13
- <button type="button" @click="open = !open" style="display:flex;align-items:center;gap:6px;border:none;background:transparent;cursor:pointer;padding:0;color:var(--text-heading);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base)">
12
+ <div :data-size="size" :style="{ background: 'var(--bg-surface)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', boxSizing: 'border-box', ...style }">
13
+ <div style="display:flex;align-items:center;gap:var(--control-gap);min-height:var(--bar-card-h);padding:0 var(--edge-pad);box-sizing:border-box">
14
+ <button type="button" :aria-expanded="open" @click="open = !open" style="display:flex;align-items:center;gap:6px;border:none;background:transparent;cursor:pointer;padding:0;color:var(--text-heading);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking)">
14
15
  {{ variant }}<span v-if="modified" style="color:var(--text-muted);font-weight:var(--weight-regular)">*</span>
15
- <span :style="{ color: 'var(--text-muted)', display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }"><Icon name="chevron-down" :size="14" /></span>
16
+ <span :style="{ color: 'var(--text-muted)', display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }"><Icon name="chevron-down" :size="16" :style="{ width: 'var(--control-icon)', height: 'var(--control-icon)' }" /></span>
16
17
  </button>
17
18
  <span style="flex:1" />
18
- <Button variant="text" size="sm" @click="emit('adapt')">Adapt Filters{{ adaptCount ? ` (${adaptCount})` : '' }}</Button>
19
- <Button size="sm" @click="emit('go')">Go</Button>
19
+ <Button variant="text" @click="emit('adapt')">Adapt Filters{{ adaptCount ? ` (${adaptCount})` : '' }}</Button>
20
+ <Button @click="emit('go')">Go</Button>
20
21
  </div>
21
- <div v-if="open" style="display:flex;flex-wrap:wrap;gap:12px 16px;padding-bottom:14px;border-top:1px solid var(--border-subtle);padding-top:12px"><slot /></div>
22
+ <div v-if="open" style="display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--control-gap) var(--group-gap);padding:12px var(--edge-pad) var(--edge-pad);border-top:1px solid var(--border-subtle)"><slot /></div>
22
23
  </div>
23
24
  </template>
@@ -3,9 +3,9 @@ import React from "react";
3
3
  /** Label/value row (config summaries, confirmation steps). mono renders the value in Menlo. */
4
4
  export function KeyValue({ label, value, mono, style }) {
5
5
  return (
6
- <div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "7px 0", ...style }}>
7
- <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-secondary)" }}>{label}</span>
8
- <span style={{ fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: mono ? "var(--text-xs)" : "var(--text-sm)", letterSpacing: mono ? "var(--tracking-xs)" : 0, color: "var(--text-heading)", textAlign: "right" }}>{value}</span>
6
+ <div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "6px 0", ...style }}>
7
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", color: "var(--text-secondary)" }}>{label}</span>
8
+ <span style={{ fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: mono ? 0 : "var(--tracking-base)", fontVariantNumeric: "tabular-nums", color: "var(--text-heading)", textAlign: "right" }}>{value}</span>
9
9
  </div>
10
10
  );
11
11
  }
@@ -3,8 +3,8 @@
3
3
  defineProps({ label: String, value: [String, Number], mono: Boolean, style: Object })
4
4
  </script>
5
5
  <template>
6
- <div :style="{ display: 'flex', justifyContent: 'space-between', gap: 12, padding: '7px 0', ...style }">
7
- <span style="font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)">{{ label }}</span>
8
- <span :style="{ fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: mono ? 'var(--text-xs)' : 'var(--text-sm)', letterSpacing: mono ? 'var(--tracking-xs)' : 0, color: 'var(--text-heading)', textAlign: 'right' }">{{ value }}</span>
6
+ <div :style="{ display: 'flex', justifyContent: 'space-between', gap: '12px', padding: '6px 0', ...style }">
7
+ <span 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)">{{ label }}</span>
8
+ <span :style="{ fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: mono ? 0 : 'var(--tracking-base)', fontVariantNumeric: 'tabular-nums', color: 'var(--text-heading)', textAlign: 'right' }">{{ value }}</span>
9
9
  </div>
10
10
  </template>
@@ -14,10 +14,10 @@ export function ListRow({ icon = "file-text", title, meta, trailing, expanded, o
14
14
  <IconTile icon={icon} kind="row" />
15
15
  <div style={{ display: "flex", flexDirection: "column" }}>
16
16
  <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", color: "var(--text-heading)" }}>{title}</span>
17
- {meta ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-secondary)" }}>{meta}</span> : null}
17
+ {meta ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-regular)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", color: "var(--text-secondary)" }}>{meta}</span> : null}
18
18
  </div>
19
19
  </div>
20
- {trailing ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-secondary)" }}>{trailing}</span> : null}
20
+ {trailing ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", fontVariantNumeric: "tabular-nums", color: "var(--text-secondary)" }}>{trailing}</span> : null}
21
21
  </div>
22
22
  {expanded && children ? <div style={{ padding: "0 16px 12px 72px" }}>{children}</div> : null}
23
23
  </div>
@@ -15,10 +15,10 @@ const hover = ref(false)
15
15
  <IconTile :icon="icon" kind="row" />
16
16
  <div style="display:flex;flex-direction:column">
17
17
  <span 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);color:var(--text-heading)">{{ title }}</span>
18
- <span v-if="meta" style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)">{{ meta }}</span>
18
+ <span v-if="meta" style="font-family:var(--font-sans);font-weight:var(--weight-regular);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary)">{{ meta }}</span>
19
19
  </div>
20
20
  </div>
21
- <span v-if="trailing" style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)">{{ trailing }}</span>
21
+ <span v-if="trailing" 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);font-variant-numeric:tabular-nums;color:var(--text-secondary)">{{ trailing }}</span>
22
22
  </div>
23
23
  <div v-if="expanded" style="padding:0 16px 12px 72px"><slot /></div>
24
24
  </div>
@@ -1,11 +1,15 @@
1
1
  import * as React from "react";
2
- /** Table pagination (26px controls, r4). Addition — not in the source file. */
2
+ /** Table pagination — page items are control-height squares at the region's tier, r8. Inside a Table card it follows the table's tier. */
3
3
  export interface PaginationProps {
4
4
  page?: number;
5
5
  pageCount?: number;
6
6
  /** Total rows; shows an "1–20 of 128" range label when given */
7
7
  total?: number;
8
8
  pageSize?: number;
9
+ /** Size tier. Omit to inherit the enclosing region's tier (the default). */
10
+ size?: "sm" | "md" | "lg";
11
+ /** Builds the range label, for other languages: (from, to, total, page, pageCount) => string. from/to/total are null when `total` is not given. */
12
+ format?: (from: number | null, to: number | null, total: number | null, page: number, pageCount: number) => string;
9
13
  onChange?: (page: number) => void;
10
14
  style?: React.CSSProperties;
11
15
  }
@@ -1,26 +1,30 @@
1
1
  import React from "react";
2
2
  import { Icon } from "../icons/Icon.jsx";
3
3
 
4
- /** Table pagination — 12px range label + prev/next + numbered pages. */
5
- export function Pagination({ page = 1, pageCount = 1, total, pageSize = 20, onChange, style }) {
6
- const btn = (content, disabled, onClick, active) => (
7
- <button type="button" disabled={disabled} onClick={onClick}
8
- style={{ minWidth: 26, height: 26, padding: "0 6px", borderRadius: "var(--radius-sm)", border: `1px solid ${active ? "var(--action-primary)" : "var(--border-control)"}`, background: active ? "var(--action-primary)" : "var(--bg-surface)", color: active ? "var(--on-accent)" : "var(--text-body)", fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", cursor: disabled ? "default" : "pointer", opacity: disabled ? .45 : 1, display: "inline-flex", alignItems: "center", justifyContent: "center", transition: "var(--t-hover)" }}>
4
+ const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
5
+
6
+ /** Table pagination — range label + prev/next + numbered pages, all at the region's tier (page items are control-height squares). `format(from, to, total, page, pageCount)` localises the label. */
7
+ export function Pagination({ page = 1, pageCount = 1, total, pageSize = 20, size, format, onChange, style }) {
8
+ const btn = (content, disabled, onClick, active, label) => (
9
+ <button type="button" disabled={disabled} onClick={onClick} aria-label={label} aria-current={active ? "page" : undefined} data-wash={active || disabled ? undefined : ""}
10
+ style={{ minWidth: "var(--control-h)", height: "var(--control-h)", padding: "0 6px", boxSizing: "border-box", borderRadius: "var(--radius-md)", border: `1px solid ${active ? "var(--action-primary)" : "var(--border-control)"}`, background: active ? "var(--action-primary)" : "var(--wash, var(--bg-surface))", color: active ? "var(--on-accent)" : "var(--text-body)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", fontVariantNumeric: "tabular-nums", cursor: disabled ? "default" : "pointer", opacity: disabled ? .45 : 1, display: "inline-flex", alignItems: "center", justifyContent: "center", transition: "var(--t-hover)" }}>
9
11
  {content}
10
12
  </button>
11
13
  );
12
14
  const pages = [];
13
- const from = Math.max(1, Math.min(page - 1, pageCount - 2));
14
- for (let i = from; i < from + 3 && i <= pageCount; i++) pages.push(i);
15
+ const first = Math.max(1, Math.min(page - 1, pageCount - 2));
16
+ for (let i = first; i < first + 3 && i <= pageCount; i++) pages.push(i);
17
+ const from = total != null ? (page - 1) * pageSize + 1 : null, to = total != null ? Math.min(page * pageSize, total) : null;
18
+ const text = format ? format(from, to, total, page, pageCount) : (total != null ? `${from}–${to} of ${total}` : `Page ${page} of ${pageCount}`);
15
19
  return (
16
- <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, ...style }}>
17
- <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-secondary)" }}>
18
- {total != null ? `${(page - 1) * pageSize + 1}–${Math.min(page * pageSize, total)} of ${total}` : `Page ${page} of ${pageCount}`}
20
+ <div data-size={size} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "var(--group-gap)", ...style }}>
21
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", fontVariantNumeric: "tabular-nums", color: "var(--text-secondary)" }}>
22
+ {text}
19
23
  </span>
20
24
  <div style={{ display: "flex", gap: 4 }}>
21
- {btn(<Icon name="chevron-left" size={13} />, page <= 1, () => onChange && onChange(page - 1))}
25
+ {btn(<Icon name="chevron-left" size={16} style={tierIcon} />, page <= 1, () => onChange && onChange(page - 1), false, "Previous page")}
22
26
  {pages.map((p) => <React.Fragment key={p}>{btn(p, false, () => onChange && onChange(p), p === page)}</React.Fragment>)}
23
- {btn(<Icon name="chevron-right" size={13} />, page >= pageCount, () => onChange && onChange(page + 1))}
27
+ {btn(<Icon name="chevron-right" size={16} style={tierIcon} />, page >= pageCount, () => onChange && onChange(page + 1), false, "Next page")}
24
28
  </div>
25
29
  </div>
26
30
  );
@@ -1,24 +1,29 @@
1
1
  <script setup>
2
2
  import { computed } from 'vue'
3
3
  import Icon from '../icons/Icon.vue'
4
- // Table pagination — 12px range label + prev/next + numbered pages.
5
- const props = defineProps({ page: { type: Number, default: 1 }, pageCount: { type: Number, default: 1 }, total: Number, pageSize: { type: Number, default: 20 }, style: Object })
4
+ // Table pagination — range label + prev/next + numbered pages, all at the region's tier (page items are control-height squares). `format(from, to, total, page, pageCount)` localises the label.
5
+ const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
6
+ const props = defineProps({ page: { type: Number, default: 1 }, pageCount: { type: Number, default: 1 }, total: Number, pageSize: { type: Number, default: 20 }, size: String, format: Function, style: Object })
6
7
  const emit = defineEmits(['change'])
7
8
  const pages = computed(() => {
8
9
  const from = Math.max(1, Math.min(props.page - 1, props.pageCount - 2)), out = []
9
10
  for (let i = from; i < from + 3 && i <= props.pageCount; i++) out.push(i)
10
11
  return out
11
12
  })
12
- const label = computed(() => props.total != null ? `${(props.page - 1) * props.pageSize + 1}–${Math.min(props.page * props.pageSize, props.total)} of ${props.total}` : `Page ${props.page} of ${props.pageCount}`)
13
- function btnStyle(active, disabled) { return { minWidth: 26, height: 26, padding: '0 6px', borderRadius: 'var(--radius-sm)', border: `1px solid ${active ? 'var(--action-primary)' : 'var(--border-control)'}`, background: active ? 'var(--action-primary)' : 'var(--bg-surface)', color: active ? 'var(--on-accent)' : 'var(--text-body)', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? .45 : 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', transition: 'var(--t-hover)' } }
13
+ const label = computed(() => {
14
+ const from = props.total != null ? (props.page - 1) * props.pageSize + 1 : null, to = props.total != null ? Math.min(props.page * props.pageSize, props.total) : null
15
+ if (props.format) return props.format(from, to, props.total, props.page, props.pageCount)
16
+ return props.total != null ? `${from}–${to} of ${props.total}` : `Page ${props.page} of ${props.pageCount}`
17
+ })
18
+ function btnStyle(active, disabled) { return { minWidth: 'var(--control-h)', height: 'var(--control-h)', padding: '0 6px', boxSizing: 'border-box', borderRadius: 'var(--radius-md)', border: `1px solid ${active ? 'var(--action-primary)' : 'var(--border-control)'}`, background: active ? 'var(--action-primary)' : 'var(--wash, var(--bg-surface))', color: active ? 'var(--on-accent)' : 'var(--text-body)', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', letterSpacing: 'var(--control-tracking)', fontVariantNumeric: 'tabular-nums', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? .45 : 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', transition: 'var(--t-hover)' } }
14
19
  </script>
15
20
  <template>
16
- <div :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, ...style }">
17
- <span style="font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)">{{ label }}</span>
21
+ <div :data-size="size" :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--group-gap)', ...style }">
22
+ <span style="font-family:var(--font-sans);font-size:var(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking);font-variant-numeric:tabular-nums;color:var(--text-secondary)">{{ label }}</span>
18
23
  <div style="display:flex;gap:4px">
19
- <button type="button" :disabled="page <= 1" @click="emit('change', page - 1)" :style="btnStyle(false, page <= 1)"><Icon name="chevron-left" :size="13" /></button>
20
- <button v-for="p in pages" :key="p" type="button" @click="emit('change', p)" :style="btnStyle(p === page, false)">{{ p }}</button>
21
- <button type="button" :disabled="page >= pageCount" @click="emit('change', page + 1)" :style="btnStyle(false, page >= pageCount)"><Icon name="chevron-right" :size="13" /></button>
24
+ <button type="button" aria-label="Previous page" :disabled="page <= 1" :data-wash="page <= 1 ? undefined : ''" @click="emit('change', page - 1)" :style="btnStyle(false, page <= 1)"><Icon name="chevron-left" :size="16" :style="tierIcon" /></button>
25
+ <button v-for="p in pages" :key="p" type="button" :aria-current="p === page ? 'page' : undefined" :data-wash="p === page ? undefined : ''" @click="emit('change', p)" :style="btnStyle(p === page, false)">{{ p }}</button>
26
+ <button type="button" aria-label="Next page" :disabled="page >= pageCount" :data-wash="page >= pageCount ? undefined : ''" @click="emit('change', page + 1)" :style="btnStyle(false, page >= pageCount)"><Icon name="chevron-right" :size="16" :style="tierIcon" /></button>
22
27
  </div>
23
28
  </div>
24
29
  </template>
@@ -24,7 +24,10 @@ export interface SmartTableProps {
24
24
  toolbar?: boolean;
25
25
  /** Scroll-area max height */
26
26
  height?: number | string;
27
- rowHeight?: number;
27
+ /** Row height; defaults to the tier's bar height (32 at sm) */
28
+ rowHeight?: number | string;
29
+ /** Size tier of the grid region. Defaults to "sm" — a database grid is a dense surface (32px rows, 12px text, 24px toolbar buttons). */
30
+ size?: "sm" | "md" | "lg";
28
31
  onChange?: (rows: object[]) => void;
29
32
  style?: React.CSSProperties;
30
33
  }
@@ -9,7 +9,8 @@ const typeIcon = { text: "type", longText: "align-left", number: "hash", select:
9
9
  const Pill = ({ hue = "gray", children }) => <span style={{ display: "inline-flex", alignItems: "center", height: 20, padding: "0 8px", borderRadius: "var(--radius-full)", background: `var(--hue-${hue}-bg)`, color: `var(--hue-${hue}-fg)`, fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-xs)", lineHeight: 1, whiteSpace: "nowrap" }}>{children}</span>;
10
10
 
11
11
  /** Notion/Airtable-style database grid ("smart table"): field-type icons in headers, colored select pills, person/date/checkbox/url/progress cells, row hover + expand, inline text/number editing, + New row, add-field column, per-column footer aggregates, and a view toolbar (Hide fields · Filter · Group · Sort · search). */
12
- export function SmartTable({ fields = [], rows = [], title = "Grid view", toolbar = true, height, rowHeight = 32, onChange, style }) {
12
+ /* A size region that defaults to sm: a database grid is a dense surface, so rows are the sm bar height (32), cells and toolbar read the sm control text (12px) and the toolbar's buttons are 24px. Pass size="md" for a roomier grid. */
13
+ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolbar = true, height, rowHeight = "var(--bar-h)", size = "sm", onChange, style }) {
13
14
  const [data, setData] = React.useState(rows);
14
15
  const [hover, setHover] = React.useState(-1);
15
16
  const [editing, setEditing] = React.useState(null);
@@ -17,7 +18,8 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
17
18
  const setCell = (r, key, v) => { const next = data.map((row, i) => i === r ? { ...row, [key]: v } : row); setData(next); onChange && onChange(next); };
18
19
  const widths = ["40px", ...fields.map(f => (f.width || 140) + "px"), "40px"];
19
20
  const grid = { display: "grid", gridTemplateColumns: widths.join(" "), width: "fit-content", minWidth: "100%" };
20
- const cellBase = { display: "flex", alignItems: "center", gap: 5, padding: "0 8px", borderRight: "1px solid var(--border-subtle)", borderBottom: "1px solid var(--border-subtle)", height: rowHeight, boxSizing: "border-box", overflow: "hidden", whiteSpace: "nowrap", fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-primary)" };
21
+ const cellBase = { display: "flex", alignItems: "center", gap: 5, padding: "0 var(--cell-px)", borderRight: "1px solid var(--border-subtle)", borderBottom: "1px solid var(--border-subtle)", height: rowHeight, boxSizing: "border-box", overflow: "hidden", whiteSpace: "nowrap", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-primary)" };
22
+ const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
21
23
  const agg = (f) => {
22
24
  if (!f.aggregate) return "";
23
25
  const vals = data.map(r => r[f.key]);
@@ -35,18 +37,18 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
35
37
  case "select": { const hue = (f.options || {})[v] || "gray"; return v ? <Pill hue={hue}>{v}</Pill> : null; }
36
38
  case "multiSelect": return <span style={{ display: "flex", gap: 4, overflow: "hidden" }}>{(v || []).map(t => <Pill key={t} hue={(f.options || {})[t] || "gray"}>{t}</Pill>)}</span>;
37
39
  case "person": return v ? <span style={{ display: "inline-flex", alignItems: "center", gap: 6, minWidth: 0 }}><Avatar name={v} size={20} /><span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{v}</span></span> : null;
38
- case "checkbox": return <Checkbox size="sm" checked={!!v} onChange={(nv) => setCell(r, f.key, nv)} />;
40
+ case "checkbox": return <Checkbox checked={!!v} onChange={(nv) => setCell(r, f.key, nv)} />;
39
41
  case "url": return v ? <a href={"https://" + v} onClick={(e) => e.preventDefault()} style={{ color: "var(--link)", overflow: "hidden", textOverflow: "ellipsis" }}>{v}</a> : null;
40
- case "progress": return <span style={{ display: "flex", alignItems: "center", gap: 6, width: "100%" }}><span style={{ flex: 1, height: 4, borderRadius: "var(--radius-full)", background: "var(--track)", overflow: "hidden" }}><span style={{ display: "block", width: (v || 0) + "%", height: "100%", background: "var(--chart-blue)" }}></span></span><span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--text-secondary)" }}>{v || 0}%</span></span>;
41
- case "number": return <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: 11.5 }}>{v}</span>;
42
+ case "progress": return <span style={{ display: "flex", alignItems: "center", gap: 6, width: "100%" }}><span style={{ flex: 1, height: 4, borderRadius: "var(--radius-full)", background: "var(--track)", overflow: "hidden" }}><span style={{ display: "block", width: (v || 0) + "%", height: "100%", background: "var(--chart-blue)" }}></span></span><span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-secondary)" }}>{v || 0}%</span></span>;
43
+ case "number": return <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--control-text)", letterSpacing: 0, fontVariantNumeric: "tabular-nums" }}>{v}</span>;
42
44
  default: return <span style={{ overflow: "hidden", textOverflow: "ellipsis", fontWeight: f.primary ? "var(--weight-medium)" : "var(--weight-regular)", color: f.primary ? "var(--text-heading)" : "var(--text-primary)" }}>{v}</span>;
43
45
  }
44
46
  };
45
- const tbBtn = (icon, label) => <button key={label} type="button" style={{ display: "inline-flex", alignItems: "center", gap: 5, height: 26, padding: "0 8px", border: "none", borderRadius: "var(--radius-sm)", background: "transparent", cursor: "pointer", color: "var(--text-body)", fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)" }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-hover)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}><Icon name={icon} size={13} />{label}</button>;
47
+ const tbBtn = (icon, label) => <button key={label} type="button" style={{ display: "inline-flex", alignItems: "center", gap: 5, height: "var(--control-h)", padding: "0 var(--control-px)", border: "none", borderRadius: "var(--radius-md)", background: "transparent", cursor: "pointer", color: "var(--text-body)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)" }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-hover)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}><Icon name={icon} size={14} style={tierIcon} />{label}</button>;
46
48
  return (
47
- <div style={{ border: "1px solid var(--border-control)", borderRadius: "var(--radius-lg)", background: "var(--bg-surface)", overflow: "hidden", boxSizing: "border-box", ...style }}>
48
- {toolbar && <div style={{ display: "flex", alignItems: "center", gap: 4, padding: "6px 10px", borderBottom: "1px solid var(--border-subtle)" }}>
49
- <button type="button" style={{ display: "inline-flex", alignItems: "center", gap: 6, height: 26, padding: "0 8px", border: "none", borderRadius: "var(--radius-sm)", background: "var(--bg-active)", cursor: "pointer", color: "var(--text-heading)", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-sm)" }}><Icon name="layout-grid" size={13} />{title}<Icon name="chevron-down" size={12} /></button>
49
+ <div data-size={size} style={{ border: "1px solid var(--border-control)", borderRadius: "var(--radius-lg)", background: "var(--bg-surface)", overflow: "hidden", boxSizing: "border-box", ...style }}>
50
+ {toolbar && <div style={{ display: "flex", alignItems: "center", gap: 4, minHeight: "var(--bar-card-h)", padding: "0 var(--edge-pad)", boxSizing: "border-box", borderBottom: "1px solid var(--border-subtle)" }}>
51
+ <button type="button" data-wash="strong" style={{ display: "inline-flex", alignItems: "center", gap: 6, height: "var(--control-h)", padding: "0 var(--control-px)", border: "none", borderRadius: "var(--radius-md)", background: "var(--wash, var(--bg-active))", transition: "var(--t-hover)", cursor: "pointer", color: "var(--text-heading)", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)" }}><Icon name="layout-grid" size={14} style={tierIcon} />{title}<Icon name="chevron-down" size={14} style={tierIcon} /></button>
50
52
  <span style={{ width: 1, height: 16, background: "var(--border-control)", margin: "0 4px" }}></span>
51
53
  {tbBtn("eye-off", "Hide fields")}{tbBtn("funnel", "Filter")}{tbBtn("layers", "Group")}{tbBtn("arrow-down", "Sort")}
52
54
  <span style={{ flex: 1 }}></span>
@@ -55,16 +57,16 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
55
57
  <div style={{ overflow: "auto", maxHeight: height }}>
56
58
  <div style={grid}>
57
59
  <span style={{ ...cellBase, background: "var(--bg-inset)", justifyContent: "center" }}></span>
58
- {fields.map(f => <span key={f.key} style={{ ...cellBase, background: "var(--bg-inset)", color: "var(--text-secondary)", fontSize: "var(--text-xs)", fontWeight: "var(--weight-medium)", letterSpacing: "var(--tracking-xs)" }}><Icon name={typeIcon[f.type] || "type"} size={12} style={{ color: "var(--text-muted)" }} />{f.label}</span>)}
59
- <span style={{ ...cellBase, background: "var(--bg-inset)", justifyContent: "center" }}><IconButton icon="plus" size="xs" muted label="Add field" /></span>
60
+ {fields.map(f => <span key={f.key} style={{ ...cellBase, background: "var(--bg-inset)", color: "var(--text-secondary)", fontWeight: "var(--weight-semibold)" }}><Icon name={typeIcon[f.type] || "type"} size={14} style={{ ...tierIcon, color: "var(--text-muted)" }} />{f.label}</span>)}
61
+ <span style={{ ...cellBase, background: "var(--bg-inset)", justifyContent: "center", padding: 0 }}><IconButton icon="plus" muted label="Add field" /></span>
60
62
  {data.map((row, r) => [
61
- <span key={"n" + r} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} style={{ ...cellBase, justifyContent: "center", color: "var(--text-muted)", fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", background: hover === r ? "var(--bg-inset)" : "transparent", cursor: "pointer" }}>{hover === r ? <Icon name="maximize-2" size={12} style={{ color: "var(--action-primary)" }} /> : r + 1}</span>,
63
+ <span key={"n" + r} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} style={{ ...cellBase, justifyContent: "center", color: "var(--text-muted)", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", letterSpacing: 0, background: hover === r ? "var(--bg-inset)" : "transparent", cursor: "pointer" }}>{hover === r ? <Icon name="maximize-2" size={14} style={{ ...tierIcon, color: "var(--action-primary)" }} /> : r + 1}</span>,
62
64
  ...fields.map(f => <span key={r + f.key} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} onDoubleClick={() => { if (f.type === "text" || f.type === "number" || f.type === "date" || f.type === "url") { setEditing({ r, key: f.key }); setDraft(String(row[f.key] == null ? "" : row[f.key])); } }} style={{ ...cellBase, position: "relative", background: hover === r ? "var(--bg-inset)" : "transparent" }}>{renderCell(f, row, r)}</span>),
63
65
  <span key={"a" + r} style={{ ...cellBase, background: hover === r ? "var(--bg-inset)" : "transparent" }}></span>,
64
66
  ])}
65
- <button type="button" onClick={() => { const empty = {}; fields.forEach(f => empty[f.key] = f.type === "multiSelect" ? [] : f.type === "checkbox" ? false : ""); const next = [...data, empty]; setData(next); onChange && onChange(next); }} style={{ gridColumn: "1 / -1", display: "flex", alignItems: "center", gap: 6, height: rowHeight, padding: "0 10px", border: "none", borderBottom: "1px solid var(--border-subtle)", background: "transparent", cursor: "pointer", color: "var(--text-secondary)", fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", textAlign: "left" }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-inset)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}><Icon name="plus" size={13} />New</button>
67
+ <button type="button" onClick={() => { const empty = {}; fields.forEach(f => empty[f.key] = f.type === "multiSelect" ? [] : f.type === "checkbox" ? false : ""); const next = [...data, empty]; setData(next); onChange && onChange(next); }} style={{ gridColumn: "1 / -1", display: "flex", alignItems: "center", gap: 6, height: rowHeight, padding: "0 var(--edge-pad)", border: "none", borderBottom: "1px solid var(--border-subtle)", background: "transparent", cursor: "pointer", color: "var(--text-secondary)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", textAlign: "left" }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-inset)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}><Icon name="plus" size={13} />New</button>
66
68
  <span style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)" }}></span>
67
- {fields.map(f => <span key={"g" + f.key} style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)", justifyContent: "flex-end", fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--text-secondary)" }}>{agg(f)}</span>)}
69
+ {fields.map(f => <span key={"g" + f.key} style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)", justifyContent: "flex-end", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", letterSpacing: 0, color: "var(--text-secondary)" }}>{agg(f)}</span>)}
68
70
  <span style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)" }}></span>
69
71
  </div>
70
72
  </div>