basalt-ui 1.24.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 (467) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +25 -0
  3. package/MIGRATING.md +547 -21
  4. package/README.md +46 -33
  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/query-state.d.ts +1 -1
  134. package/dist/dashboard/settings-section.d.ts +6 -4
  135. package/dist/dashboard/settings-section.d.ts.map +1 -1
  136. package/dist/dashboard/settings-section.js +23 -16
  137. package/dist/dashboard/settings-section.module.css +10 -0
  138. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  139. package/dist/dashboard/settings-section.test.d.ts +2 -0
  140. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  141. package/dist/dashboard/stat-card.d.ts +44 -9
  142. package/dist/dashboard/stat-card.d.ts.map +1 -1
  143. package/dist/dashboard/stat-card.js +80 -48
  144. package/dist/dashboard/stat-card.module.css +82 -0
  145. package/dist/dashboard/stat-card.module.css.d.ts +11 -0
  146. package/dist/data/data-table.d.ts +102 -20
  147. package/dist/data/data-table.d.ts.map +1 -1
  148. package/dist/data/data-table.js +166 -65
  149. package/dist/data/table.d.ts +1 -1
  150. package/dist/data/table.d.ts.map +1 -1
  151. package/dist/guard/grace.test.d.ts +20 -0
  152. package/dist/guard/grace.test.d.ts.map +1 -0
  153. package/dist/guard/index.d.ts +66 -3
  154. package/dist/guard/index.d.ts.map +1 -1
  155. package/dist/guard/index.js +82 -7
  156. package/dist/guard/types.d.ts +3 -3
  157. package/dist/guard/types.d.ts.map +1 -1
  158. package/dist/index.d.ts +12 -2
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +13 -10
  161. package/dist/nav/types.d.ts +78 -2
  162. package/dist/nav/types.d.ts.map +1 -1
  163. package/dist/notifications/bell.d.ts +4 -1
  164. package/dist/notifications/bell.d.ts.map +1 -1
  165. package/dist/notifications/index.d.ts +1 -1
  166. package/dist/router-tanstack/field.d.ts +14 -0
  167. package/dist/router-tanstack/field.d.ts.map +1 -0
  168. package/dist/router-tanstack/field.js +4 -0
  169. package/dist/router-tanstack/index.d.ts +6 -2
  170. package/dist/router-tanstack/index.d.ts.map +1 -1
  171. package/dist/router-tanstack/index.js +4 -0
  172. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  173. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  174. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  175. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  176. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  177. package/dist/router-tanstack/search-param-store.js +16 -45
  178. package/dist/router-tanstack/search-store.d.ts +81 -0
  179. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  180. package/dist/router-tanstack/search-store.js +187 -0
  181. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  182. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  183. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  184. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  185. package/dist/router-tanstack/use-nav.d.ts +0 -2
  186. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  187. package/dist/router-tanstack/use-nav.js +34 -12
  188. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  189. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  190. package/dist/section/index.d.ts +2 -0
  191. package/dist/section/index.d.ts.map +1 -0
  192. package/dist/section/index.js +4 -0
  193. package/dist/section/section.d.ts +24 -0
  194. package/dist/section/section.d.ts.map +1 -0
  195. package/dist/section/section.js +126 -0
  196. package/dist/section/section.module.css +104 -0
  197. package/dist/section/section.module.css.d.ts +11 -0
  198. package/dist/section/section.test.d.ts +2 -0
  199. package/dist/section/section.test.d.ts.map +1 -0
  200. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  201. package/dist/shell/app-breadcrumbs.js +18 -15
  202. package/dist/shell/app-header.module.css +77 -37
  203. package/dist/shell/app-header.module.css.d.ts +2 -1
  204. package/dist/shell/app-mobile-nav.d.ts +9 -2
  205. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  206. package/dist/shell/app-mobile-nav.js +98 -11
  207. package/dist/shell/app-mobile-nav.module.css +8 -0
  208. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  209. package/dist/shell/app-sidebar.d.ts +55 -28
  210. package/dist/shell/app-sidebar.d.ts.map +1 -1
  211. package/dist/shell/app-sidebar.js +116 -67
  212. package/dist/shell/app-sidebar.module.css +303 -20
  213. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  214. package/dist/shell/index.d.ts +55 -21
  215. package/dist/shell/index.d.ts.map +1 -1
  216. package/dist/shell/index.js +40 -18
  217. package/dist/shell/mobile-nav-model.d.ts +13 -2
  218. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  219. package/dist/shell/mobile-nav-model.js +15 -1
  220. package/dist/shell/nav-count-badge.d.ts +1 -1
  221. package/dist/shell/page-bar.d.ts +51 -0
  222. package/dist/shell/page-bar.d.ts.map +1 -0
  223. package/dist/shell/page-bar.js +154 -0
  224. package/dist/shell/page-bar.module.css +178 -0
  225. package/dist/shell/page-bar.module.css.d.ts +17 -0
  226. package/dist/shell/page-bar.test.d.ts +2 -0
  227. package/dist/shell/page-bar.test.d.ts.map +1 -0
  228. package/dist/shell/sidebar-block-model.d.ts +48 -0
  229. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  230. package/dist/shell/sidebar-block-model.js +39 -0
  231. package/dist/shell/sidebar-blocks.d.ts +32 -0
  232. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  233. package/dist/shell/sidebar-blocks.js +169 -0
  234. package/dist/shell/sidebar-search.d.ts +13 -1
  235. package/dist/shell/sidebar-search.d.ts.map +1 -1
  236. package/dist/shell/sidebar-search.js +34 -1
  237. package/dist/shell/sidebar-search.module.css +26 -0
  238. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  239. package/dist/state/fields.d.ts +392 -0
  240. package/dist/state/fields.d.ts.map +1 -0
  241. package/dist/state/fields.js +369 -0
  242. package/dist/state/fields.test.d.ts +2 -0
  243. package/dist/state/fields.test.d.ts.map +1 -0
  244. package/dist/state/persisted.d.ts +48 -0
  245. package/dist/state/persisted.d.ts.map +1 -0
  246. package/dist/state/persisted.js +105 -0
  247. package/dist/state.d.ts +11 -56
  248. package/dist/state.d.ts.map +1 -1
  249. package/dist/state.js +14 -120
  250. package/dist/styles.css +13 -0
  251. package/dist/surfaces.d.ts +106 -24
  252. package/dist/surfaces.d.ts.map +1 -1
  253. package/dist/surfaces.js +155 -19
  254. package/dist/theme/controls.module.css +16 -1
  255. package/dist/theme/ctl-theme.d.ts +32 -0
  256. package/dist/theme/ctl-theme.d.ts.map +1 -0
  257. package/dist/theme/ctl-theme.js +54 -0
  258. package/dist/theme/ctl-theme.test.d.ts +2 -0
  259. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  260. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  261. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  262. package/dist/theme/divider-law.test.d.ts +2 -0
  263. package/dist/theme/divider-law.test.d.ts.map +1 -0
  264. package/dist/theme/icon-slot.d.ts +44 -0
  265. package/dist/theme/icon-slot.d.ts.map +1 -0
  266. package/dist/theme/icon-slot.js +21 -0
  267. package/dist/theme/icon-slot.module.css +47 -0
  268. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  269. package/dist/theme/icon-slot.test.d.ts +2 -0
  270. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  271. package/dist/theme/index.d.ts +1 -0
  272. package/dist/theme/index.d.ts.map +1 -1
  273. package/dist/theme/index.js +82 -5
  274. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  275. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  276. package/dist/theme/segmented-control.module.css +20 -0
  277. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  278. package/dist/tokens/index.d.ts +12 -0
  279. package/dist/tokens/index.d.ts.map +1 -1
  280. package/dist/tokens/index.js +38 -8
  281. package/dist/tokens/palette.d.ts +97 -61
  282. package/dist/tokens/palette.d.ts.map +1 -1
  283. package/dist/tokens/palette.js +76 -38
  284. package/dist/tokens.css +10 -2
  285. package/dist/widget-header/delta-badge.d.ts +13 -0
  286. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  287. package/dist/widget-header/delta-badge.js +29 -0
  288. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  289. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  290. package/dist/widget-header/index.d.ts +3 -0
  291. package/dist/widget-header/index.d.ts.map +1 -0
  292. package/dist/widget-header/index.js +6 -0
  293. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  294. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  295. package/dist/widget-header/widget-header.d.ts +37 -0
  296. package/dist/widget-header/widget-header.d.ts.map +1 -0
  297. package/dist/widget-header/widget-header.js +77 -0
  298. package/dist/widget-header/widget-header.module.css +335 -0
  299. package/dist/widget-header/widget-header.module.css.d.ts +24 -0
  300. package/dist/widget-header/widget-header.test.d.ts +2 -0
  301. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  302. package/llms.txt +30 -17
  303. package/package.json +14 -1
  304. package/src/charts/cursor/resolve.test.ts +29 -1
  305. package/src/charts/cursor/resolve.ts +38 -6
  306. package/src/charts/cursor/scope.tsx +6 -3
  307. package/src/charts/cursor/store.test.ts +19 -1
  308. package/src/charts/cursor/store.ts +10 -5
  309. package/src/charts/hooks/useChartCursor.ts +16 -8
  310. package/src/charts/index.ts +2 -2
  311. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  312. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  313. package/src/charts/primitives/ChartCard.tsx +86 -146
  314. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  315. package/src/cli/check-coverage.test.ts +297 -0
  316. package/src/cli/check-theme.test.ts +38 -14
  317. package/src/cli/index.ts +392 -16
  318. package/src/cli/init-sync.test.ts +97 -0
  319. package/src/connectivity/connectivity-indicator.tsx +16 -9
  320. package/src/content/index.ts +16 -3
  321. package/src/content/prose.module.css +7 -28
  322. package/src/content/prose.module.css.test.ts +8 -24
  323. package/src/controls/actions.module.css +62 -0
  324. package/src/controls/actions.module.css.d.ts +13 -0
  325. package/src/controls/actions.test.tsx +252 -0
  326. package/src/controls/actions.tsx +655 -0
  327. package/src/controls/actions.type-guard.test.ts +63 -0
  328. package/src/controls/compare-filter.tsx +64 -0
  329. package/src/controls/control-group.test.tsx +229 -0
  330. package/src/controls/control-group.tsx +63 -0
  331. package/src/controls/controls.module.css +542 -0
  332. package/src/controls/controls.module.css.d.ts +35 -0
  333. package/src/controls/controls.router.test.tsx +637 -0
  334. package/src/controls/enum-filter.tsx +159 -0
  335. package/src/controls/filter-context.tsx +153 -0
  336. package/src/controls/filter-pill.tsx +120 -0
  337. package/src/controls/filter-set.test.tsx +472 -0
  338. package/src/controls/filter-set.tsx +225 -0
  339. package/src/controls/filter-sheet.tsx +285 -0
  340. package/src/controls/glyphs.tsx +81 -0
  341. package/src/controls/index.ts +48 -0
  342. package/src/controls/multi-select-filter.tsx +140 -0
  343. package/src/controls/peer-boundary.test.ts +85 -0
  344. package/src/controls/props-contract.test.tsx +106 -0
  345. package/src/controls/range-filter.tsx +184 -0
  346. package/src/controls/search-filter.tsx +123 -0
  347. package/src/controls/select-filter.tsx +89 -0
  348. package/src/controls/sync-button.module.css +63 -0
  349. package/src/controls/sync-button.module.css.d.ts +11 -0
  350. package/src/controls/sync-button.test.tsx +183 -0
  351. package/src/controls/sync-button.tsx +197 -0
  352. package/src/controls/toggle-filter.tsx +60 -0
  353. package/src/controls/view-tabs.tsx +110 -0
  354. package/src/controls-dates/date-range-picker.tsx +42 -0
  355. package/src/controls-dates/index.ts +8 -0
  356. package/src/dashboard/delta-badge.tsx +4 -74
  357. package/src/dashboard/query-state.tsx +1 -1
  358. package/src/dashboard/settings-section.module.css +10 -0
  359. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  360. package/src/dashboard/settings-section.test.tsx +56 -0
  361. package/src/dashboard/settings-section.tsx +33 -30
  362. package/src/dashboard/stat-card.module.css +82 -0
  363. package/src/dashboard/stat-card.module.css.d.ts +11 -0
  364. package/src/dashboard/stat-card.test.tsx +67 -1
  365. package/src/dashboard/stat-card.tsx +209 -68
  366. package/src/data/data-table.test.tsx +282 -6
  367. package/src/data/data-table.tsx +429 -94
  368. package/src/data/table.ts +2 -0
  369. package/src/guard/check-source.test.ts +126 -13
  370. package/src/guard/grace.test.ts +149 -0
  371. package/src/guard/index.ts +172 -29
  372. package/src/guard/types.ts +4 -2
  373. package/src/index.ts +46 -5
  374. package/src/nav/types.ts +84 -2
  375. package/src/notifications/bell.tsx +6 -3
  376. package/src/notifications/index.ts +1 -1
  377. package/src/router-tanstack/field.ts +31 -0
  378. package/src/router-tanstack/index.ts +24 -2
  379. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  380. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  381. package/src/router-tanstack/search-param-store.ts +42 -159
  382. package/src/router-tanstack/search-store.router.test.tsx +661 -0
  383. package/src/router-tanstack/search-store.test.ts +418 -0
  384. package/src/router-tanstack/search-store.ts +380 -0
  385. package/src/router-tanstack/use-nav.test.tsx +187 -0
  386. package/src/router-tanstack/use-nav.ts +72 -14
  387. package/src/section/index.ts +1 -0
  388. package/src/section/section.module.css +104 -0
  389. package/src/section/section.module.css.d.ts +11 -0
  390. package/src/section/section.test.tsx +204 -0
  391. package/src/section/section.tsx +220 -0
  392. package/src/shell/app-breadcrumbs.tsx +44 -31
  393. package/src/shell/app-header.module.css +77 -37
  394. package/src/shell/app-header.module.css.d.ts +2 -1
  395. package/src/shell/app-mobile-nav.module.css +8 -0
  396. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  397. package/src/shell/app-mobile-nav.tsx +147 -8
  398. package/src/shell/app-sidebar-account.tsx +2 -2
  399. package/src/shell/app-sidebar.module.css +303 -20
  400. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  401. package/src/shell/app-sidebar.test.tsx +456 -41
  402. package/src/shell/app-sidebar.tsx +276 -140
  403. package/src/shell/index.test.tsx +291 -5
  404. package/src/shell/index.tsx +137 -38
  405. package/src/shell/mobile-nav-model.test.ts +228 -7
  406. package/src/shell/mobile-nav-model.ts +53 -3
  407. package/src/shell/nav-count-badge.tsx +1 -1
  408. package/src/shell/page-bar.module.css +178 -0
  409. package/src/shell/page-bar.module.css.d.ts +17 -0
  410. package/src/shell/page-bar.test.tsx +316 -0
  411. package/src/shell/page-bar.tsx +315 -0
  412. package/src/shell/sidebar-block-model.ts +83 -0
  413. package/src/shell/sidebar-blocks.tsx +301 -0
  414. package/src/shell/sidebar-search.module.css +26 -0
  415. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  416. package/src/shell/sidebar-search.tsx +71 -2
  417. package/src/state/fields.test.tsx +258 -0
  418. package/src/state/fields.ts +892 -0
  419. package/src/state/persisted.ts +211 -0
  420. package/src/state.ts +46 -240
  421. package/src/styles.css +13 -0
  422. package/src/surfaces.ts +203 -36
  423. package/src/theme/controls.module.css +16 -1
  424. package/src/theme/ctl-theme.test.tsx +275 -0
  425. package/src/theme/ctl-theme.tsx +138 -0
  426. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  427. package/src/theme/density-relations.test.ts +106 -51
  428. package/src/theme/divider-law.test.ts +259 -0
  429. package/src/theme/icon-slot.module.css +47 -0
  430. package/src/theme/icon-slot.module.css.d.ts +8 -0
  431. package/src/theme/icon-slot.test.tsx +275 -0
  432. package/src/theme/icon-slot.tsx +59 -0
  433. package/src/theme/index.ts +165 -3
  434. package/src/theme/layout-rhythm-css.test.ts +72 -0
  435. package/src/theme/radius.test.ts +11 -1
  436. package/src/theme/segmented-control.module.css +20 -0
  437. package/src/theme/shadow-surfaces.test.ts +20 -7
  438. package/src/theme/spacing.test.ts +75 -27
  439. package/src/theme/use-basalt-spacing.ts +2 -2
  440. package/src/tokens/density.test.ts +7 -25
  441. package/src/tokens/index.ts +48 -8
  442. package/src/tokens/palette.ts +107 -73
  443. package/src/widget-header/delta-badge.test.tsx +60 -0
  444. package/src/widget-header/delta-badge.tsx +57 -0
  445. package/src/widget-header/index.ts +2 -0
  446. package/src/widget-header/no-mantine.test.ts +33 -0
  447. package/src/widget-header/widget-header.module.css +335 -0
  448. package/src/widget-header/widget-header.module.css.d.ts +24 -0
  449. package/src/widget-header/widget-header.test.tsx +187 -0
  450. package/src/widget-header/widget-header.tsx +168 -0
  451. package/agent/rules/basalt-agent.md +0 -452
  452. package/agent/rules/basalt-app.md +0 -246
  453. package/agent/rules/basalt-commands.md +0 -320
  454. package/agent/rules/basalt-content.md +0 -409
  455. package/agent/rules/basalt-data.md +0 -269
  456. package/agent/rules/basalt-forms.md +0 -186
  457. package/agent/rules/basalt-notifications.md +0 -264
  458. package/agent/rules/basalt-query.md +0 -187
  459. package/agent/rules/basalt-router.md +0 -336
  460. package/dist/content/article-filter-bar.d.ts +0 -40
  461. package/dist/content/article-filter-bar.d.ts.map +0 -1
  462. package/dist/content/article-filter-bar.js +0 -56
  463. package/dist/shell/page-header.d.ts +0 -13
  464. package/dist/shell/page-header.d.ts.map +0 -1
  465. package/dist/shell/page-header.js +0 -23
  466. package/src/content/article-filter-bar.tsx +0 -88
  467. 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
  };
@@ -5,7 +5,7 @@ export type QueryStateVariant = 'page' | 'section';
5
5
  * The structural subset of a TanStack `UseQueryResult` these components read.
6
6
  *
7
7
  * Typed as a SUBSET rather than as `UseQueryResult<T>` on purpose: a composed, derived or
8
- * hand-rolled result must be passable without a cast (3 of image-share's 10 call sites are), and
8
+ * hand-rolled result must be passable without a cast, and
9
9
  * these components stay independent of which `@tanstack/react-query` major a consumer pins. This
10
10
  * is NOT a claim that the root barrel is free of that package — `./connectivity` imports
11
11
  * `onlineManager` as a value and is re-exported from `src/index.ts`. The cost of the subset is
@@ -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