basalt-ui 1.25.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (462) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +18 -0
  3. package/MIGRATING.md +546 -20
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +155 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +113 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1275 -31
  17. package/configs/oxlint-plugin.test.ts +947 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +30 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +91 -0
  75. package/dist/controls/filter-context.d.ts +55 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +19 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +44 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/peer-boundary.test.d.ts +2 -0
  99. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  100. package/dist/controls/props-contract.test.d.ts +2 -0
  101. package/dist/controls/props-contract.test.d.ts.map +1 -0
  102. package/dist/controls/range-filter.d.ts +29 -0
  103. package/dist/controls/range-filter.d.ts.map +1 -0
  104. package/dist/controls/range-filter.js +120 -0
  105. package/dist/controls/search-filter.d.ts +10 -0
  106. package/dist/controls/search-filter.d.ts.map +1 -0
  107. package/dist/controls/search-filter.js +81 -0
  108. package/dist/controls/select-filter.d.ts +74 -0
  109. package/dist/controls/select-filter.d.ts.map +1 -0
  110. package/dist/controls/select-filter.js +10 -0
  111. package/dist/controls/sync-button.d.ts +38 -0
  112. package/dist/controls/sync-button.d.ts.map +1 -0
  113. package/dist/controls/sync-button.js +119 -0
  114. package/dist/controls/sync-button.module.css +63 -0
  115. package/dist/controls/sync-button.module.css.d.ts +11 -0
  116. package/dist/controls/sync-button.test.d.ts +2 -0
  117. package/dist/controls/sync-button.test.d.ts.map +1 -0
  118. package/dist/controls/toggle-filter.d.ts +9 -0
  119. package/dist/controls/toggle-filter.d.ts.map +1 -0
  120. package/dist/controls/toggle-filter.js +42 -0
  121. package/dist/controls/view-tabs.d.ts +23 -0
  122. package/dist/controls/view-tabs.d.ts.map +1 -0
  123. package/dist/controls/view-tabs.js +59 -0
  124. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  125. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  126. package/dist/controls-dates/date-range-picker.js +18 -0
  127. package/dist/controls-dates/index.d.ts +9 -0
  128. package/dist/controls-dates/index.d.ts.map +1 -0
  129. package/dist/controls-dates/index.js +4 -0
  130. package/dist/dashboard/delta-badge.d.ts +6 -12
  131. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  132. package/dist/dashboard/delta-badge.js +1 -50
  133. package/dist/dashboard/settings-section.d.ts +6 -4
  134. package/dist/dashboard/settings-section.d.ts.map +1 -1
  135. package/dist/dashboard/settings-section.js +23 -16
  136. package/dist/dashboard/settings-section.module.css +10 -0
  137. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  138. package/dist/dashboard/settings-section.test.d.ts +2 -0
  139. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  140. package/dist/dashboard/stat-card.d.ts +44 -9
  141. package/dist/dashboard/stat-card.d.ts.map +1 -1
  142. package/dist/dashboard/stat-card.js +80 -48
  143. package/dist/dashboard/stat-card.module.css +82 -0
  144. package/dist/dashboard/stat-card.module.css.d.ts +11 -0
  145. package/dist/data/data-table.d.ts +37 -15
  146. package/dist/data/data-table.d.ts.map +1 -1
  147. package/dist/data/data-table.js +88 -58
  148. package/dist/guard/grace.test.d.ts +20 -0
  149. package/dist/guard/grace.test.d.ts.map +1 -0
  150. package/dist/guard/index.d.ts +66 -3
  151. package/dist/guard/index.d.ts.map +1 -1
  152. package/dist/guard/index.js +82 -7
  153. package/dist/guard/types.d.ts +3 -3
  154. package/dist/guard/types.d.ts.map +1 -1
  155. package/dist/index.d.ts +12 -2
  156. package/dist/index.d.ts.map +1 -1
  157. package/dist/index.js +13 -10
  158. package/dist/nav/types.d.ts +78 -2
  159. package/dist/nav/types.d.ts.map +1 -1
  160. package/dist/notifications/bell.d.ts +4 -1
  161. package/dist/notifications/bell.d.ts.map +1 -1
  162. package/dist/notifications/index.d.ts +1 -1
  163. package/dist/router-tanstack/field.d.ts +14 -0
  164. package/dist/router-tanstack/field.d.ts.map +1 -0
  165. package/dist/router-tanstack/field.js +4 -0
  166. package/dist/router-tanstack/index.d.ts +6 -2
  167. package/dist/router-tanstack/index.d.ts.map +1 -1
  168. package/dist/router-tanstack/index.js +4 -0
  169. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  170. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  171. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  172. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  173. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  174. package/dist/router-tanstack/search-param-store.js +16 -45
  175. package/dist/router-tanstack/search-store.d.ts +81 -0
  176. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  177. package/dist/router-tanstack/search-store.js +187 -0
  178. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  179. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  180. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  181. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  182. package/dist/router-tanstack/use-nav.d.ts +0 -2
  183. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  184. package/dist/router-tanstack/use-nav.js +34 -12
  185. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  186. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  187. package/dist/section/index.d.ts +2 -0
  188. package/dist/section/index.d.ts.map +1 -0
  189. package/dist/section/index.js +4 -0
  190. package/dist/section/section.d.ts +24 -0
  191. package/dist/section/section.d.ts.map +1 -0
  192. package/dist/section/section.js +126 -0
  193. package/dist/section/section.module.css +104 -0
  194. package/dist/section/section.module.css.d.ts +11 -0
  195. package/dist/section/section.test.d.ts +2 -0
  196. package/dist/section/section.test.d.ts.map +1 -0
  197. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  198. package/dist/shell/app-breadcrumbs.js +18 -15
  199. package/dist/shell/app-header.module.css +77 -37
  200. package/dist/shell/app-header.module.css.d.ts +2 -1
  201. package/dist/shell/app-mobile-nav.d.ts +9 -2
  202. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  203. package/dist/shell/app-mobile-nav.js +98 -11
  204. package/dist/shell/app-mobile-nav.module.css +8 -0
  205. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  206. package/dist/shell/app-sidebar.d.ts +55 -28
  207. package/dist/shell/app-sidebar.d.ts.map +1 -1
  208. package/dist/shell/app-sidebar.js +116 -67
  209. package/dist/shell/app-sidebar.module.css +303 -20
  210. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  211. package/dist/shell/index.d.ts +55 -21
  212. package/dist/shell/index.d.ts.map +1 -1
  213. package/dist/shell/index.js +40 -18
  214. package/dist/shell/mobile-nav-model.d.ts +13 -2
  215. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  216. package/dist/shell/mobile-nav-model.js +15 -1
  217. package/dist/shell/nav-count-badge.d.ts +1 -1
  218. package/dist/shell/page-bar.d.ts +51 -0
  219. package/dist/shell/page-bar.d.ts.map +1 -0
  220. package/dist/shell/page-bar.js +154 -0
  221. package/dist/shell/page-bar.module.css +178 -0
  222. package/dist/shell/page-bar.module.css.d.ts +17 -0
  223. package/dist/shell/page-bar.test.d.ts +2 -0
  224. package/dist/shell/page-bar.test.d.ts.map +1 -0
  225. package/dist/shell/sidebar-block-model.d.ts +48 -0
  226. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  227. package/dist/shell/sidebar-block-model.js +39 -0
  228. package/dist/shell/sidebar-blocks.d.ts +32 -0
  229. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  230. package/dist/shell/sidebar-blocks.js +169 -0
  231. package/dist/shell/sidebar-search.d.ts +13 -1
  232. package/dist/shell/sidebar-search.d.ts.map +1 -1
  233. package/dist/shell/sidebar-search.js +34 -1
  234. package/dist/shell/sidebar-search.module.css +26 -0
  235. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  236. package/dist/state/fields.d.ts +392 -0
  237. package/dist/state/fields.d.ts.map +1 -0
  238. package/dist/state/fields.js +369 -0
  239. package/dist/state/fields.test.d.ts +2 -0
  240. package/dist/state/fields.test.d.ts.map +1 -0
  241. package/dist/state/persisted.d.ts +48 -0
  242. package/dist/state/persisted.d.ts.map +1 -0
  243. package/dist/state/persisted.js +105 -0
  244. package/dist/state.d.ts +11 -56
  245. package/dist/state.d.ts.map +1 -1
  246. package/dist/state.js +14 -120
  247. package/dist/styles.css +13 -0
  248. package/dist/surfaces.d.ts +106 -24
  249. package/dist/surfaces.d.ts.map +1 -1
  250. package/dist/surfaces.js +155 -19
  251. package/dist/theme/controls.module.css +16 -1
  252. package/dist/theme/ctl-theme.d.ts +32 -0
  253. package/dist/theme/ctl-theme.d.ts.map +1 -0
  254. package/dist/theme/ctl-theme.js +54 -0
  255. package/dist/theme/ctl-theme.test.d.ts +2 -0
  256. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  257. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  258. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  259. package/dist/theme/divider-law.test.d.ts +2 -0
  260. package/dist/theme/divider-law.test.d.ts.map +1 -0
  261. package/dist/theme/icon-slot.d.ts +44 -0
  262. package/dist/theme/icon-slot.d.ts.map +1 -0
  263. package/dist/theme/icon-slot.js +21 -0
  264. package/dist/theme/icon-slot.module.css +47 -0
  265. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  266. package/dist/theme/icon-slot.test.d.ts +2 -0
  267. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  268. package/dist/theme/index.d.ts +1 -0
  269. package/dist/theme/index.d.ts.map +1 -1
  270. package/dist/theme/index.js +82 -5
  271. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  272. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  273. package/dist/theme/segmented-control.module.css +20 -0
  274. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  275. package/dist/tokens/index.d.ts +12 -0
  276. package/dist/tokens/index.d.ts.map +1 -1
  277. package/dist/tokens/index.js +38 -8
  278. package/dist/tokens/palette.d.ts +97 -61
  279. package/dist/tokens/palette.d.ts.map +1 -1
  280. package/dist/tokens/palette.js +76 -38
  281. package/dist/tokens.css +10 -2
  282. package/dist/widget-header/delta-badge.d.ts +13 -0
  283. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  284. package/dist/widget-header/delta-badge.js +29 -0
  285. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  286. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  287. package/dist/widget-header/index.d.ts +3 -0
  288. package/dist/widget-header/index.d.ts.map +1 -0
  289. package/dist/widget-header/index.js +6 -0
  290. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  291. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  292. package/dist/widget-header/widget-header.d.ts +37 -0
  293. package/dist/widget-header/widget-header.d.ts.map +1 -0
  294. package/dist/widget-header/widget-header.js +77 -0
  295. package/dist/widget-header/widget-header.module.css +335 -0
  296. package/dist/widget-header/widget-header.module.css.d.ts +24 -0
  297. package/dist/widget-header/widget-header.test.d.ts +2 -0
  298. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  299. package/llms.txt +30 -17
  300. package/package.json +14 -1
  301. package/src/charts/cursor/resolve.test.ts +29 -1
  302. package/src/charts/cursor/resolve.ts +38 -6
  303. package/src/charts/cursor/scope.tsx +6 -3
  304. package/src/charts/cursor/store.test.ts +19 -1
  305. package/src/charts/cursor/store.ts +10 -5
  306. package/src/charts/hooks/useChartCursor.ts +16 -8
  307. package/src/charts/index.ts +2 -2
  308. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  309. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  310. package/src/charts/primitives/ChartCard.tsx +86 -146
  311. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  312. package/src/cli/check-coverage.test.ts +297 -0
  313. package/src/cli/check-theme.test.ts +38 -14
  314. package/src/cli/index.ts +392 -16
  315. package/src/cli/init-sync.test.ts +97 -0
  316. package/src/connectivity/connectivity-indicator.tsx +16 -9
  317. package/src/content/index.ts +16 -3
  318. package/src/content/prose.module.css +7 -28
  319. package/src/content/prose.module.css.test.ts +8 -24
  320. package/src/controls/actions.module.css +62 -0
  321. package/src/controls/actions.module.css.d.ts +13 -0
  322. package/src/controls/actions.test.tsx +252 -0
  323. package/src/controls/actions.tsx +655 -0
  324. package/src/controls/actions.type-guard.test.ts +63 -0
  325. package/src/controls/compare-filter.tsx +64 -0
  326. package/src/controls/control-group.test.tsx +229 -0
  327. package/src/controls/control-group.tsx +63 -0
  328. package/src/controls/controls.module.css +542 -0
  329. package/src/controls/controls.module.css.d.ts +35 -0
  330. package/src/controls/controls.router.test.tsx +637 -0
  331. package/src/controls/enum-filter.tsx +159 -0
  332. package/src/controls/filter-context.tsx +153 -0
  333. package/src/controls/filter-pill.tsx +120 -0
  334. package/src/controls/filter-set.test.tsx +472 -0
  335. package/src/controls/filter-set.tsx +225 -0
  336. package/src/controls/filter-sheet.tsx +285 -0
  337. package/src/controls/glyphs.tsx +81 -0
  338. package/src/controls/index.ts +48 -0
  339. package/src/controls/multi-select-filter.tsx +140 -0
  340. package/src/controls/peer-boundary.test.ts +85 -0
  341. package/src/controls/props-contract.test.tsx +106 -0
  342. package/src/controls/range-filter.tsx +184 -0
  343. package/src/controls/search-filter.tsx +123 -0
  344. package/src/controls/select-filter.tsx +89 -0
  345. package/src/controls/sync-button.module.css +63 -0
  346. package/src/controls/sync-button.module.css.d.ts +11 -0
  347. package/src/controls/sync-button.test.tsx +183 -0
  348. package/src/controls/sync-button.tsx +197 -0
  349. package/src/controls/toggle-filter.tsx +60 -0
  350. package/src/controls/view-tabs.tsx +110 -0
  351. package/src/controls-dates/date-range-picker.tsx +42 -0
  352. package/src/controls-dates/index.ts +8 -0
  353. package/src/dashboard/delta-badge.tsx +4 -74
  354. package/src/dashboard/settings-section.module.css +10 -0
  355. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  356. package/src/dashboard/settings-section.test.tsx +56 -0
  357. package/src/dashboard/settings-section.tsx +33 -30
  358. package/src/dashboard/stat-card.module.css +82 -0
  359. package/src/dashboard/stat-card.module.css.d.ts +11 -0
  360. package/src/dashboard/stat-card.test.tsx +67 -1
  361. package/src/dashboard/stat-card.tsx +209 -68
  362. package/src/data/data-table.test.tsx +96 -11
  363. package/src/data/data-table.tsx +220 -84
  364. package/src/guard/check-source.test.ts +126 -13
  365. package/src/guard/grace.test.ts +149 -0
  366. package/src/guard/index.ts +172 -29
  367. package/src/guard/types.ts +4 -2
  368. package/src/index.ts +46 -5
  369. package/src/nav/types.ts +84 -2
  370. package/src/notifications/bell.tsx +6 -3
  371. package/src/notifications/index.ts +1 -1
  372. package/src/router-tanstack/field.ts +31 -0
  373. package/src/router-tanstack/index.ts +24 -2
  374. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  375. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  376. package/src/router-tanstack/search-param-store.ts +42 -159
  377. package/src/router-tanstack/search-store.router.test.tsx +661 -0
  378. package/src/router-tanstack/search-store.test.ts +418 -0
  379. package/src/router-tanstack/search-store.ts +380 -0
  380. package/src/router-tanstack/use-nav.test.tsx +187 -0
  381. package/src/router-tanstack/use-nav.ts +72 -14
  382. package/src/section/index.ts +1 -0
  383. package/src/section/section.module.css +104 -0
  384. package/src/section/section.module.css.d.ts +11 -0
  385. package/src/section/section.test.tsx +204 -0
  386. package/src/section/section.tsx +220 -0
  387. package/src/shell/app-breadcrumbs.tsx +44 -31
  388. package/src/shell/app-header.module.css +77 -37
  389. package/src/shell/app-header.module.css.d.ts +2 -1
  390. package/src/shell/app-mobile-nav.module.css +8 -0
  391. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  392. package/src/shell/app-mobile-nav.tsx +147 -8
  393. package/src/shell/app-sidebar-account.tsx +2 -2
  394. package/src/shell/app-sidebar.module.css +303 -20
  395. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  396. package/src/shell/app-sidebar.test.tsx +456 -41
  397. package/src/shell/app-sidebar.tsx +276 -140
  398. package/src/shell/index.test.tsx +291 -5
  399. package/src/shell/index.tsx +137 -38
  400. package/src/shell/mobile-nav-model.test.ts +228 -7
  401. package/src/shell/mobile-nav-model.ts +53 -3
  402. package/src/shell/nav-count-badge.tsx +1 -1
  403. package/src/shell/page-bar.module.css +178 -0
  404. package/src/shell/page-bar.module.css.d.ts +17 -0
  405. package/src/shell/page-bar.test.tsx +316 -0
  406. package/src/shell/page-bar.tsx +315 -0
  407. package/src/shell/sidebar-block-model.ts +83 -0
  408. package/src/shell/sidebar-blocks.tsx +301 -0
  409. package/src/shell/sidebar-search.module.css +26 -0
  410. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  411. package/src/shell/sidebar-search.tsx +71 -2
  412. package/src/state/fields.test.tsx +258 -0
  413. package/src/state/fields.ts +892 -0
  414. package/src/state/persisted.ts +211 -0
  415. package/src/state.ts +46 -240
  416. package/src/styles.css +13 -0
  417. package/src/surfaces.ts +203 -36
  418. package/src/theme/controls.module.css +16 -1
  419. package/src/theme/ctl-theme.test.tsx +275 -0
  420. package/src/theme/ctl-theme.tsx +138 -0
  421. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  422. package/src/theme/density-relations.test.ts +106 -51
  423. package/src/theme/divider-law.test.ts +259 -0
  424. package/src/theme/icon-slot.module.css +47 -0
  425. package/src/theme/icon-slot.module.css.d.ts +8 -0
  426. package/src/theme/icon-slot.test.tsx +275 -0
  427. package/src/theme/icon-slot.tsx +59 -0
  428. package/src/theme/index.ts +165 -3
  429. package/src/theme/layout-rhythm-css.test.ts +72 -0
  430. package/src/theme/radius.test.ts +11 -1
  431. package/src/theme/segmented-control.module.css +20 -0
  432. package/src/theme/shadow-surfaces.test.ts +20 -7
  433. package/src/theme/spacing.test.ts +75 -27
  434. package/src/theme/use-basalt-spacing.ts +2 -2
  435. package/src/tokens/density.test.ts +7 -25
  436. package/src/tokens/index.ts +48 -8
  437. package/src/tokens/palette.ts +107 -73
  438. package/src/widget-header/delta-badge.test.tsx +60 -0
  439. package/src/widget-header/delta-badge.tsx +57 -0
  440. package/src/widget-header/index.ts +2 -0
  441. package/src/widget-header/no-mantine.test.ts +33 -0
  442. package/src/widget-header/widget-header.module.css +335 -0
  443. package/src/widget-header/widget-header.module.css.d.ts +24 -0
  444. package/src/widget-header/widget-header.test.tsx +187 -0
  445. package/src/widget-header/widget-header.tsx +168 -0
  446. package/agent/rules/basalt-agent.md +0 -452
  447. package/agent/rules/basalt-app.md +0 -246
  448. package/agent/rules/basalt-commands.md +0 -320
  449. package/agent/rules/basalt-content.md +0 -409
  450. package/agent/rules/basalt-data.md +0 -329
  451. package/agent/rules/basalt-forms.md +0 -186
  452. package/agent/rules/basalt-notifications.md +0 -264
  453. package/agent/rules/basalt-query.md +0 -187
  454. package/agent/rules/basalt-router.md +0 -336
  455. package/dist/content/article-filter-bar.d.ts +0 -40
  456. package/dist/content/article-filter-bar.d.ts.map +0 -1
  457. package/dist/content/article-filter-bar.js +0 -56
  458. package/dist/shell/page-header.d.ts +0 -13
  459. package/dist/shell/page-header.d.ts.map +0 -1
  460. package/dist/shell/page-header.js +0 -23
  461. package/src/content/article-filter-bar.tsx +0 -88
  462. package/src/shell/page-header.tsx +0 -35
@@ -1,53 +1,4 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Badge } from "@mantine/core";
3
- import { alpha, VX } from "../tokens/index.js";
4
- const defaultFormat = (value) => `${Math.abs(value).toFixed(1)}%`;
5
- function DeltaBadge({
6
- value,
7
- format = defaultFormat,
8
- withGlyph = true,
9
- period
10
- }) {
11
- const tone = value > 0 ? VX.status.good : value < 0 ? VX.status.bad : VX.faint;
12
- const glyph = value > 0 ? "\u25B2" : value < 0 ? "\u25BC" : void 0;
13
- const showGlyph = withGlyph && glyph !== void 0;
14
- return /* @__PURE__ */ jsxs(
15
- Badge,
16
- {
17
- styles: {
18
- root: {
19
- backgroundColor: alpha(tone, 0.13),
20
- padding: "var(--vx-space-badge-inset-y) var(--vx-space-badge-inset-x)",
21
- height: "auto"
22
- },
23
- label: {
24
- display: "inline-flex",
25
- // theme-allow inline-display — a Badge `styles.label` part, not a layout node Flex could replace
26
- alignItems: "center",
27
- gap: 3,
28
- // theme-allow inline-spacing — sub-scale glyph-to-label gap, below the smallest scale stop
29
- fontFamily: "var(--basalt-font-mono)",
30
- fontSize: VX.text.xs,
31
- fontWeight: 600,
32
- textTransform: "none",
33
- letterSpacing: "normal",
34
- color: tone
35
- }
36
- },
37
- children: [
38
- showGlyph && /* @__PURE__ */ jsx("span", { style: { fontSize: "0.8em" }, children: glyph }),
39
- format(value),
40
- period && /* @__PURE__ */ jsx(
41
- "span",
42
- {
43
- style: { marginLeft: 4, fontWeight: 500, opacity: 0.6 },
44
- children: period
45
- }
46
- )
47
- ]
48
- }
49
- );
50
- }
1
+ import { DeltaBadge } from "../widget-header/delta-badge.js";
51
2
  export {
52
3
  DeltaBadge
53
4
  };
@@ -1,13 +1,15 @@
1
1
  import type { ReactNode } from 'react';
2
2
  export type SettingsSectionProps = {
3
- /** Head-font section title (15px, weight 550, ink). */
3
+ /** Head-font section title, rendered via `WidgetHeader tier="section"`. */
4
4
  title: string;
5
5
  /** Optional 13px muted description below the title. */
6
- description?: string;
6
+ subtitle?: string;
7
+ /** Header-right slot — wrapped in `CtlSlot` (C1/C5). */
8
+ actions?: ReactNode;
7
9
  /** Section body — typically a stack of `SettingsRow`s. */
8
10
  children: ReactNode;
9
11
  };
10
- export declare function SettingsSection({ title, description, children }: SettingsSectionProps): import("react").JSX.Element;
12
+ export declare function SettingsSection({ title, subtitle, actions, children }: SettingsSectionProps): import("react").JSX.Element;
11
13
  export type SettingsRowProps = {
12
14
  /** 13px ink label on the left. */
13
15
  label: string;
@@ -20,5 +22,5 @@ export type SettingsRowProps = {
20
22
  };
21
23
  export declare function SettingsRow({ label, description, control, children }: SettingsRowProps): import("react").JSX.Element;
22
24
  export type DangerZoneProps = SettingsSectionProps;
23
- export declare function DangerZone({ title, description, children }: DangerZoneProps): import("react").JSX.Element;
25
+ export declare function DangerZone({ title, subtitle, actions, children }: DangerZoneProps): import("react").JSX.Element;
24
26
  //# sourceMappingURL=settings-section.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings-section.d.ts","sourceRoot":"","sources":["../../src/dashboard/settings-section.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAqBtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,oBAAoB,+BAYrF;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAYtF;AAED,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAA;AAElD,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,eAAe,+BAkB3E"}
1
+ {"version":3,"file":"settings-section.d.ts","sourceRoot":"","sources":["../../src/dashboard/settings-section.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAetC,MAAM,MAAM,oBAAoB,GAAG;IACjC,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,oBAAoB,+BAc3F;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAYtF;AAED,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAA;AAElD,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,+BAoBjF"}
@@ -1,14 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Card, Stack } from "@mantine/core";
3
+ import { WidgetHeader } from "../widget-header/index.js";
4
+ import { CtlSlot } from "../theme/index.js";
3
5
  import { alpha, VX } from "../tokens/index.js";
4
6
  import classes from "./settings-section.module.css";
5
- const titleStyle = {
6
- fontFamily: "var(--basalt-font-head)",
7
- fontStretch: "88%",
8
- fontSize: VX.text.md,
9
- fontWeight: 550,
10
- color: VX.ink
11
- };
12
7
  const eyebrowStyle = {
13
8
  fontFamily: "var(--basalt-font-mono)",
14
9
  fontSize: VX.text.micro,
@@ -17,12 +12,17 @@ const eyebrowStyle = {
17
12
  textTransform: "uppercase",
18
13
  color: VX.status.bad
19
14
  };
20
- function SettingsSection({ title, description, children }) {
15
+ function SettingsSection({ title, subtitle, actions, children }) {
21
16
  return /* @__PURE__ */ jsxs(Card, { style: { padding: "var(--mantine-spacing-xs) var(--mantine-spacing-sm)" }, children: [
22
- /* @__PURE__ */ jsxs(Stack, { gap: 2, mb: "sm", children: [
23
- /* @__PURE__ */ jsx("span", { style: titleStyle, children: title }),
24
- description && /* @__PURE__ */ jsx("span", { style: { fontSize: VX.text.sm, color: VX.muted }, children: description })
25
- ] }),
17
+ /* @__PURE__ */ jsx("div", { className: classes.header, children: /* @__PURE__ */ jsx(
18
+ WidgetHeader,
19
+ {
20
+ tier: "section",
21
+ title,
22
+ ...subtitle !== void 0 && { subtitle },
23
+ ...actions !== void 0 && { actions: /* @__PURE__ */ jsx(CtlSlot, { children: actions }) }
24
+ }
25
+ ) }),
26
26
  /* @__PURE__ */ jsx("div", { className: classes.rows, children })
27
27
  ] });
28
28
  }
@@ -35,7 +35,7 @@ function SettingsRow({ label, description, control, children }) {
35
35
  /* @__PURE__ */ jsx("div", { className: classes.control, children: control ?? children })
36
36
  ] });
37
37
  }
38
- function DangerZone({ title, description, children }) {
38
+ function DangerZone({ title, subtitle, actions, children }) {
39
39
  return /* @__PURE__ */ jsxs(
40
40
  Card,
41
41
  {
@@ -44,10 +44,17 @@ function DangerZone({ title, description, children }) {
44
44
  boxShadow: `${VX.shadowCard}, 0 0 0 1px ${alpha(VX.status.bad, 0.25)}`
45
45
  },
46
46
  children: [
47
- /* @__PURE__ */ jsxs(Stack, { gap: 2, mb: "sm", children: [
47
+ /* @__PURE__ */ jsxs("div", { className: classes.header, children: [
48
48
  /* @__PURE__ */ jsx("span", { style: eyebrowStyle, children: "Danger Zone" }),
49
- /* @__PURE__ */ jsx("span", { style: titleStyle, children: title }),
50
- description && /* @__PURE__ */ jsx("span", { style: { fontSize: VX.text.sm, color: VX.muted }, children: description })
49
+ /* @__PURE__ */ jsx(
50
+ WidgetHeader,
51
+ {
52
+ tier: "section",
53
+ title,
54
+ ...subtitle !== void 0 && { subtitle },
55
+ ...actions !== void 0 && { actions: /* @__PURE__ */ jsx(CtlSlot, { children: actions }) }
56
+ }
57
+ )
51
58
  ] }),
52
59
  /* @__PURE__ */ jsx("div", { className: classes.rows, children })
53
60
  ]
@@ -3,6 +3,16 @@
3
3
  * vertical padding and a 1px `--vx-divider` rule below it, EXCEPT the last row in the section —
4
4
  * `:not(:last-child)` strips the divider there so the section doesn't end on a trailing line.
5
5
  */
6
+
7
+ /* The WidgetHeader block (plus DangerZone's eyebrow, when present) sits above `.rows` with the
8
+ * same gap the old inline `Stack gap={2} mb="sm"` produced. */
9
+ .header {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 2px;
13
+ margin-bottom: var(--mantine-spacing-sm);
14
+ }
15
+
6
16
  .rows > * {
7
17
  padding: var(--vx-space-settings-row-inset-y) 0;
8
18
  }
@@ -3,6 +3,7 @@
3
3
  * signature — so dot access stays verbatim under the package's strict flags.
4
4
  */
5
5
  declare const classes: {
6
+ readonly header: string
6
7
  readonly rows: string
7
8
  readonly row: string
8
9
  readonly control: string
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=settings-section.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-section.test.d.ts","sourceRoot":"","sources":["../../src/dashboard/settings-section.test.tsx"],"names":[],"mappings":""}
@@ -1,25 +1,60 @@
1
1
  import type { ReactNode } from 'react';
2
2
  /** A threshold verdict on the card's value. `undefined` is never tinted — see the module docblock. */
3
3
  export type StatCardTone = 'good' | 'warn' | 'bad';
4
+ /**
5
+ * The measured box a `sparkline` render prop receives.
6
+ *
7
+ * A `LineSparkline`/`BarSparkline` takes `width`/`height` as NUMBERS (they are SVG attributes — an
8
+ * svg cannot size itself from CSS and then scale its scales), so "full bleed" was never expressible
9
+ * as a `ReactNode`: the caller had to guess a pixel width, or write its own `useChartSize` wrapper
10
+ * component. Four consumers wrote that wrapper; two of them hardcoded a width and drew a sparkline
11
+ * that stopped short of the card edge on every viewport but the one they built on.
12
+ */
13
+ export type StatCardSparklineSize = {
14
+ width: number;
15
+ height: number;
16
+ };
4
17
  export type StatCardProps = {
5
- /** Mono uppercase micro-label (docs/DESIGN-SPEC.md §3: 12.5px, tracking 0.06em, faint). */
6
- label: string;
7
- /** Pre-formatted KPI value string (mono ~24px, weight 600, ink). */
18
+ /** Head-font title, rendered via `WidgetHeader tier="widget"`. */
19
+ title: string;
20
+ /** Optional leading icon, forwarded to `WidgetHeader`. */
21
+ icon?: ReactNode;
22
+ /** Muted line under the hero row, forwarded to `WidgetHeader` — the unit or basis a
23
+ * pre-formatted `value` cannot carry (`per day`, `of 40 planned`). Not a second metric. */
24
+ subtitle?: string;
25
+ /** Info tooltip beside the title, forwarded to `WidgetHeader` — how the number is computed. Never
26
+ * part of the heading's accessible name; see `WidgetHeaderProps.info`. */
27
+ info?: string;
28
+ /** Pre-formatted KPI value string (mono ~24px, weight 600, ink) — the hero-row value. */
8
29
  value: string;
9
30
  /** Signed delta rendered via `DeltaBadge`; omit to hide the trend chip entirely. */
10
31
  delta?: number;
11
32
  /** Optional comparison timeframe shown after the delta (e.g. `MoM`) — forwarded to `DeltaBadge`. */
12
33
  deltaPeriod?: string;
13
- /** Optional trend visual — a slot; pass a `LineSparkline`/`BarSparkline` from `basalt-ui/charts`.
14
- * Rendered full-bleed to the card's left/right/bottom edges, clipped to the corner radius. */
15
- sparkline?: ReactNode;
16
- /** Optional header-right slot (e.g. a ghost "..." menu trigger) — consumer-owned. */
17
- menu?: ReactNode;
34
+ /**
35
+ * Optional trend visual. Either a node, or a RENDER PROP receiving the slot's measured box.
36
+ *
37
+ * Prefer the render prop for anything from `basalt-ui/charts`: those take numeric `width`/`height`
38
+ * (SVG attributes), so a plain node has to hardcode a width, and a hardcoded width is not
39
+ * full-bleed on any viewport but the one it was typed on. With `sparklinePlacement="bleed"` the
40
+ * measured width is the card's own inner width including the bled inset; with `'right'` it is the
41
+ * fixed 72×26 slot (see {@link SPARKLINE_RIGHT_WIDTH} for why that one is not measured).
42
+ *
43
+ * @example
44
+ * sparkline={({ width, height }) => <BarSparkline data={history} width={width} height={height} />}
45
+ */
46
+ sparkline?: ReactNode | ((size: StatCardSparklineSize) => ReactNode);
47
+ /** Where `sparkline` sits. `'bleed'` (default) is today's full-width row bled to the card edges;
48
+ * `'right'` sits it beside the hero value row. Collapses to `'bleed'` below `sm`. */
49
+ sparklinePlacement?: 'bleed' | 'right';
50
+ /** Header-right slot (e.g. a ghost "..." menu trigger) — wrapped in `CtlSlot tier="widget"`, so a
51
+ * raw `ActionIcon` with no `size` lands on the 24px step the 28px header row holds (C1/C5). */
52
+ actions?: ReactNode;
18
53
  /** Threshold verdict — draws an accent rail down the card's leading edge and announces itself to
19
54
  * assistive tech. Omitting it is NOT `'good'`: omitted covers a reading that is fine AND one that
20
55
  * is absent, and stays untinted so a card with nothing measured can never render green. Pass
21
56
  * `'good'` only to assert a measured value that has earned the verdict. */
22
57
  tone?: StatCardTone;
23
58
  };
24
- export declare function StatCard({ label, value, delta, deltaPeriod, sparkline, menu, tone, }: StatCardProps): import("react").JSX.Element;
59
+ export declare function StatCard({ title, icon, subtitle, info, value, delta, deltaPeriod, sparkline, sparklinePlacement, actions, tone, }: StatCardProps): import("react").JSX.Element;
25
60
  //# sourceMappingURL=stat-card.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"AAsDA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,sGAAsG;AACtG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAYlD,MAAM,MAAM,aAAa,GAAG;IAC1B,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;kGAC8F;IAC9F,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,qFAAqF;IACrF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;+EAG2E;IAC3E,IAAI,CAAC,EAAE,YAAY,CAAA;CACpB,CAAA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,KAAK,EACL,WAAW,EACX,SAAS,EACT,IAAI,EACJ,IAAI,GACL,EAAE,aAAa,+BAyFf"}
1
+ {"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"AA6FA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAMrD,sGAAsG;AACtG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAclD;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAkDD,MAAM,MAAM,aAAa,GAAG;IAC1B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;+FAC2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;8EAC0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,qBAAqB,KAAK,SAAS,CAAC,CAAA;IACpE;yFACqF;IACrF,kBAAkB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACtC;mGAC+F;IAC/F,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB;;;+EAG2E;IAC3E,IAAI,CAAC,EAAE,YAAY,CAAA;CACpB,CAAA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,WAAW,EACX,SAAS,EACT,kBAA4B,EAC5B,OAAO,EACP,IAAI,GACL,EAAE,aAAa,+BAmGf"}
@@ -1,7 +1,36 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Box, Card, Group, VisuallyHidden } from "@mantine/core";
3
- import { DeltaBadge } from "./delta-badge.js";
2
+ import { Box, Card, VisuallyHidden } from "@mantine/core";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { WidgetHeader } from "../widget-header/index.js";
5
+ import { CtlSlot } from "../theme/index.js";
4
6
  import { VX } from "../tokens/index.js";
7
+ import classes from "./stat-card.module.css";
8
+ const SPARKLINE_RIGHT_WIDTH = 72;
9
+ const SPARKLINE_RIGHT_HEIGHT = 26;
10
+ function useSlotWidth(active) {
11
+ const [width, setWidth] = useState(0);
12
+ const nodeRef = useRef(null);
13
+ useEffect(() => {
14
+ const node = nodeRef.current;
15
+ if (!active || node === null) return;
16
+ const measure = () => {
17
+ setWidth(node.getBoundingClientRect().width);
18
+ };
19
+ measure();
20
+ if (typeof ResizeObserver === "undefined") return;
21
+ const observer = new ResizeObserver(measure);
22
+ observer.observe(node);
23
+ return () => {
24
+ observer.disconnect();
25
+ };
26
+ }, [active]);
27
+ return {
28
+ ref: (node) => {
29
+ nodeRef.current = node;
30
+ },
31
+ width
32
+ };
33
+ }
5
34
  const TONE_RAIL_WIDTH = 3;
6
35
  const TONE_LABEL = {
7
36
  good: "Within the good threshold",
@@ -9,14 +38,22 @@ const TONE_LABEL = {
9
38
  bad: "Past the severe threshold"
10
39
  };
11
40
  function StatCard({
12
- label,
41
+ title,
42
+ icon,
43
+ subtitle,
44
+ info,
13
45
  value,
14
46
  delta,
15
47
  deltaPeriod,
16
48
  sparkline,
17
- menu,
49
+ sparklinePlacement = "bleed",
50
+ actions,
18
51
  tone
19
52
  }) {
53
+ const isRender = typeof sparkline === "function";
54
+ const bleeds = sparklinePlacement === "bleed";
55
+ const slot = useSlotWidth(isRender);
56
+ const measured = { width: slot.width, height: SPARKLINE_RIGHT_HEIGHT };
20
57
  return /* @__PURE__ */ jsxs(
21
58
  Card,
22
59
  {
@@ -27,12 +64,13 @@ function StatCard({
27
64
  // shadow-card ring is unaffected (verified — only an ANCESTOR's overflow clips a
28
65
  // descendant's shadow). Mantine's Card root already sets `overflow: hidden`; this is
29
66
  // explicit for intent.
67
+ //
68
+ // NO `minHeight`. It was 118px, which is ~25px of dead space under a card holding a title
69
+ // row, a hero row and a 26px trend — and it was dead space the card could not use, since the
70
+ // body is `height: 100%` and the sparkline is `margin-top: auto`. The content is what states
71
+ // the height now (~88-96px), and four cards in a `SimpleGrid` still match because a grid row
72
+ // stretches its items.
30
73
  padding: "var(--mantine-spacing-xs) var(--mantine-spacing-sm)",
31
- minHeight: 118,
32
- display: "flex",
33
- // theme-allow inline-display — Card root as a flex column; Mantine Card takes no layout props
34
- flexDirection: "column",
35
- gap: "var(--vx-space-stat-card-gap)",
36
74
  overflow: "hidden",
37
75
  // Anchors the tone rail. Set unconditionally so a card's stacking context does not change
38
76
  // depending on whether it happens to have crossed a threshold this render.
@@ -60,50 +98,44 @@ function StatCard({
60
98
  }
61
99
  )
62
100
  ] }),
63
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "nowrap", gap: "xs", children: [
64
- /* @__PURE__ */ jsx(
65
- "span",
101
+ /* @__PURE__ */ jsxs("div", { className: classes.body, "data-placement": sparklinePlacement, children: [
102
+ /* @__PURE__ */ jsx("div", { className: classes.header, children: /* @__PURE__ */ jsx(
103
+ WidgetHeader,
66
104
  {
67
- style: {
68
- fontFamily: "var(--basalt-font-mono)",
69
- fontSize: VX.text.xs,
70
- fontWeight: 500,
71
- letterSpacing: "0.06em",
72
- textTransform: "uppercase",
73
- color: VX.faint
74
- },
75
- children: label
105
+ tier: "widget",
106
+ title,
107
+ ...icon !== void 0 && { icon },
108
+ ...subtitle !== void 0 && { subtitle },
109
+ ...info !== void 0 && { info },
110
+ value,
111
+ ...delta !== void 0 && { delta },
112
+ ...deltaPeriod !== void 0 && { deltaPeriod },
113
+ ...actions !== void 0 && {
114
+ actions: /* @__PURE__ */ jsx(CtlSlot, { tier: "widget", children: actions })
115
+ }
76
116
  }
77
- ),
78
- menu
79
- ] }),
80
- /* @__PURE__ */ jsxs(Group, { align: "center", gap: 8, wrap: "nowrap", children: [
81
- /* @__PURE__ */ jsx(
82
- "span",
117
+ ) }),
118
+ sparkline !== void 0 && /* @__PURE__ */ jsx(
119
+ "div",
83
120
  {
84
- style: {
85
- fontFamily: "var(--basalt-font-mono)",
86
- fontSize: VX.text.kpi,
87
- fontWeight: 600,
88
- letterSpacing: "-0.02em",
89
- color: VX.ink
121
+ ref: slot.ref,
122
+ className: bleeds ? classes.sparklineBleed : classes.sparklineRight,
123
+ ...!bleeds && {
124
+ // CUSTOM PROPERTIES, not `flexBasis`/`height` directly. A React inline style beats
125
+ // every stylesheet rule, so an inline `flexBasis: 72` also applied below `sm` — where
126
+ // `'right'` collapses to a COLUMN and flex-basis is therefore the main-axis HEIGHT.
127
+ // The mobile sparkline rendered in a 72px-tall box holding 26px of bars, which is the
128
+ // dead band under every KPI value on a phone. Handed to CSS as values instead, the
129
+ // media query can reset the box and the numbers still live in one place.
130
+ style: {
131
+ "--basalt-stat-sparkline-w": `${SPARKLINE_RIGHT_WIDTH}px`,
132
+ "--basalt-stat-sparkline-h": `${SPARKLINE_RIGHT_HEIGHT}px`
133
+ }
90
134
  },
91
- children: value
135
+ children: isRender ? measured.width > 0 && sparkline(measured) : sparkline
92
136
  }
93
- ),
94
- delta !== void 0 && /* @__PURE__ */ jsx(DeltaBadge, { value: delta, period: deltaPeriod })
95
- ] }),
96
- sparkline && /* @__PURE__ */ jsx(
97
- "div",
98
- {
99
- style: {
100
- marginTop: "auto",
101
- marginInline: "calc(-1 * var(--mantine-spacing-sm))",
102
- marginBottom: "calc(-1 * var(--mantine-spacing-xs))"
103
- },
104
- children: sparkline
105
- }
106
- )
137
+ )
138
+ ] })
107
139
  ]
108
140
  }
109
141
  );
@@ -0,0 +1,82 @@
1
+ /*
2
+ * StatCard's sparkline placement (docs/CONTROLS-SPEC.md §2.2). `bleed` (default) keeps the historic
3
+ * full-width row beneath the hero value, bled past the card's own inset padding — same negative
4
+ * margins the pre-WidgetHeader version drew inline. `right` sits the sparkline beside the
5
+ * `WidgetHeader`'s hero-value row instead, the reference-design look, and collapses back to
6
+ * `bleed`'s stacked layout below `sm` — CSS only, no JS branch (`47.99375em` is Mantine's own
7
+ * complement of `min-width: 48em`, same value `content/prose.module.css` derives it from).
8
+ */
9
+
10
+ .body {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: var(--vx-space-stat-card-gap);
14
+ height: 100%;
15
+ }
16
+
17
+ /*
18
+ * `flex-end`, not `center`: the sparkline belongs to the HERO ROW, and the hero row is the header
19
+ * block's last line. Centred against the whole body it floated between the title and the value,
20
+ * which reads as a third, unrelated element; bottom-aligned to the header block it sits beside the
21
+ * number it describes, the reference layout. `gap` is the card's own control gap so the trend never
22
+ * touches a long value.
23
+ */
24
+ .body[data-placement='right'] {
25
+ flex-direction: row;
26
+ align-items: flex-end;
27
+ gap: var(--vx-space-control-gap);
28
+ }
29
+
30
+ .header {
31
+ min-width: 0;
32
+ }
33
+
34
+ /*
35
+ * `flex-shrink: 0` on the header, `1` on the sparkline slot below: the hero value and its delta keep
36
+ * their width and the TREND gives ground. The other way round, a 4-up KPI grid on a 1440 viewport
37
+ * ellipsized `$1,294,900` to `$1294.…` in order to keep 72px of bars intact, which is the wrong
38
+ * thing to protect. The value still carries `overflow: hidden` as the last resort, and `Card` clips.
39
+ */
40
+ .body[data-placement='right'] .header {
41
+ flex: 1 0 auto;
42
+ min-width: 0;
43
+ }
44
+
45
+ .sparklineBleed {
46
+ margin-top: auto;
47
+ margin-inline: calc(-1 * var(--mantine-spacing-sm));
48
+ margin-bottom: calc(-1 * var(--mantine-spacing-xs));
49
+ }
50
+
51
+ /* Asks for `StatCard`'s `SPARKLINE_RIGHT_WIDTH`/`_HEIGHT` (handed down as custom properties, so this
52
+ * rule and the `< sm` reset below can both reach them) and shrinks below the width before the hero
53
+ * row does — see the `.header` note above. `min-width: 0` is what lets it. */
54
+ .sparklineRight {
55
+ display: flex;
56
+ flex: 0 1 var(--basalt-stat-sparkline-w, 4.5rem);
57
+ min-width: 0;
58
+ height: var(--basalt-stat-sparkline-h, 1.625rem);
59
+ align-items: flex-end;
60
+ overflow: hidden;
61
+ }
62
+
63
+ @media (max-width: 47.99375em) {
64
+ .body[data-placement='right'] {
65
+ flex-direction: column;
66
+ align-items: stretch;
67
+ }
68
+
69
+ .body[data-placement='right'] .header {
70
+ flex: initial;
71
+ }
72
+
73
+ /* The row became a COLUMN, so `flex-basis` is now the main-axis HEIGHT — the 72px width would
74
+ * become a 72px-tall box around 26px of bars. `flex: none` + the height keeps the bled trend the
75
+ * same 26px it is beside a hero row. */
76
+ .body[data-placement='right'] .sparklineRight {
77
+ flex: none;
78
+ margin-top: auto;
79
+ margin-inline: calc(-1 * var(--mantine-spacing-sm));
80
+ margin-bottom: calc(-1 * var(--mantine-spacing-xs));
81
+ }
82
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Typed CSS-module declaration (the S4 CSS-modules path). Named string props — not an index
3
+ * signature — so dot access stays verbatim under the package's strict flags.
4
+ */
5
+ declare const classes: {
6
+ readonly body: string
7
+ readonly header: string
8
+ readonly sparklineBleed: string
9
+ readonly sparklineRight: string
10
+ }
11
+ export default classes
@@ -24,16 +24,16 @@ declare module '@tanstack/react-table' {
24
24
  numeral?: boolean;
25
25
  }
26
26
  }
27
- /** A single selectable option inside a {@link DataTableFacet}'s Select/MultiSelect. */
27
+ /** A single selectable option inside a {@link DataTableFacet}'s `EnumFilter`/`MultiSelectFilter` pill. */
28
28
  export type DataTableFacetOption = {
29
29
  /** The raw filter value, compared against the column's stringified cell value. */
30
30
  value: string;
31
- /** Label shown in the Select/MultiSelect dropdown. */
31
+ /** Label shown in the pill's popover. */
32
32
  label: string;
33
33
  };
34
34
  /**
35
- * Declares one faceted column filter, rendered as a Mantine Select (or MultiSelect when
36
- * `multiple` is set) inside the toolbar.
35
+ * Declares one faceted column filter, rendered as an `EnumFilter` pill (or `MultiSelectFilter`
36
+ * when `multiple` is set) inside a `FilterSet` in the toolbar (`docs/CONTROLS-SPEC.md` §3).
37
37
  *
38
38
  * @example
39
39
  * const facets: DataTableFacet[] = [
@@ -45,11 +45,11 @@ export type DataTableFacetOption = {
45
45
  export type DataTableFacet = {
46
46
  /** The TanStack column id — the column's manual `id`, or its `accessorKey` string. */
47
47
  columnId: string;
48
- /** Label shown as the Select/MultiSelect placeholder. */
48
+ /** The pill's label — read at rest (single-select) and as the `Filters`/count fallback (multi). */
49
49
  label: string;
50
50
  /** Selectable options for this facet. */
51
51
  options: DataTableFacetOption[];
52
- /** Render a MultiSelect (any-of match) instead of a single Select (exact match). @default false */
52
+ /** Render a `MultiSelectFilter` (any-of match) instead of an `EnumFilter` (exact match). @default false */
53
53
  multiple?: boolean;
54
54
  };
55
55
  /**
@@ -66,6 +66,16 @@ export type DataTableFacet = {
66
66
  * }
67
67
  */
68
68
  export type BasaltDataTableProps<T> = {
69
+ /**
70
+ * Optional heading rendered above the toolbar via `WidgetHeader tier="widget"`. `count` always
71
+ * reads `table.getRowCount()` (C11) — never a raw `data.length` — so it tracks the row model
72
+ * (post-filter/-pagination) rather than the unfiltered input.
73
+ */
74
+ title?: string;
75
+ /** Optional leading icon, forwarded to `WidgetHeader`. */
76
+ icon?: ReactNode;
77
+ /** Optional muted line rendered below the title row. */
78
+ subtitle?: string;
69
79
  /** Row data array. */
70
80
  data: T[];
71
81
  /**
@@ -162,9 +172,9 @@ export type BasaltDataTableProps<T> = {
162
172
  /** Called whenever the internal global filter value changes. */
163
173
  onGlobalFilterChange?: (value: string) => void;
164
174
  /**
165
- * Faceted column filters, rendered in the toolbar as Mantine Select/MultiSelect controls wired
166
- * to TanStack's per-column `columnFilters` state. The toolbar renders whenever this array is
167
- * non-empty, `enableGlobalFilter` is set, or `toolbarActions` is passed.
175
+ * Faceted column filters, rendered in the toolbar as `EnumFilter`/`MultiSelectFilter` pills
176
+ * inside a `FilterSet`, wired to TanStack's per-column `columnFilters` state. The toolbar
177
+ * renders whenever this array is non-empty, `enableGlobalFilter` is set, or `actions` is passed.
168
178
  * @example
169
179
  * facets={[{ columnId: 'department', label: 'Department', options: departmentOptions }]}
170
180
  */
@@ -183,9 +193,10 @@ export type BasaltDataTableProps<T> = {
183
193
  manualFiltering?: boolean;
184
194
  /**
185
195
  * Right-aligned toolbar slot (e.g. an "Export" button). Renders the toolbar row even when no
186
- * search input or facets are configured.
196
+ * search input or facets are configured. Renamed from `toolbarActions` — the toolbar (search +
197
+ * facets + this slot) is wrapped in `CtlSlot` (C1/C5), so its controls resolve to the `ctl` tier.
187
198
  */
188
- toolbarActions?: ReactNode;
199
+ actions?: ReactNode;
189
200
  /**
190
201
  * Enables the bottom pagination bar ("Showing X–Y of N", rows-per-page `Select`, Mantine
191
202
  * `Pagination`) wired to TanStack's `pagination` state. Client-side by default — see
@@ -280,9 +291,20 @@ export type BasaltDataTableProps<T> = {
280
291
  /** Row separators. Forwarded to Mantine `Table`. @default true (Mantine's own default) */
281
292
  withRowBorders?: boolean;
282
293
  /**
283
- * Outer table border. Turn it OFF for a table that already sits inside a `Card`/`ChartCard`,
284
- * where the card owns the frame and a second one reads as a nested box.
285
- * @default true
294
+ * Outer table border — a full box around the table.
295
+ *
296
+ * **`false` by default since this minor** (it was `true`). Three separate faults, all visible on
297
+ * the playground's `Controls (mobile)` page at 390px: its TOP edge sat directly under the table's
298
+ * own `WidgetHeader`, separating a header from its own content; its BOTTOM edge landed on the last
299
+ * row's own hairline, two rules at zero distance; and its left/right edges carried no information
300
+ * at all on a table that fills the page (`docs/DESIGN-SPEC.md` §8 — a border is a layout divider,
301
+ * and there is nothing here to divide). The head-row rule and the between-row rules stay, so the
302
+ * table still reads as a table.
303
+ *
304
+ * Pass `true` for a table that genuinely needs a frame — a small table floating in whitespace
305
+ * beside other content, where the box IS the grouping.
306
+ *
307
+ * @default false
286
308
  */
287
309
  withTableBorder?: boolean;
288
310
  };
@@ -311,5 +333,5 @@ export type BasaltDataTableProps<T> = {
311
333
  * emptyState={<Text c="dimmed">No users found.</Text>}
312
334
  * />
313
335
  */
314
- export declare function BasaltDataTable<T>({ data, columns, enableSorting, manualSorting, striped, highlightOnHover, emptyState, isLoading, skeletonRows, initialSorting, onSortingChange, enableGlobalFilter, globalFilterPlaceholder, searchIcon, initialGlobalFilter, onGlobalFilterChange, facets, onColumnFiltersChange, manualFiltering, toolbarActions, enablePagination, pageSizeOptions, initialPagination, onPaginationChange, manualPagination, rowCount, pageCount, enablePinning, initialColumnPinning, maxHeight, minWidth, stickyHeader, stickyHeaderOffset, verticalSpacing, horizontalSpacing, withRowBorders, withTableBorder, }: BasaltDataTableProps<T>): import("react").JSX.Element;
336
+ export declare function BasaltDataTable<T>({ title, icon, subtitle, data, columns, enableSorting, manualSorting, striped, highlightOnHover, emptyState, isLoading, skeletonRows, initialSorting, onSortingChange, enableGlobalFilter, globalFilterPlaceholder, searchIcon, initialGlobalFilter, onGlobalFilterChange, facets, onColumnFiltersChange, manualFiltering, actions, enablePagination, pageSizeOptions, initialPagination, onPaginationChange, manualPagination, rowCount, pageCount, enablePinning, initialColumnPinning, maxHeight, minWidth, stickyHeader, stickyHeaderOffset, verticalSpacing, horizontalSpacing, withRowBorders, withTableBorder, }: BasaltDataTableProps<T>): import("react").JSX.Element;
315
337
  //# sourceMappingURL=data-table.d.ts.map