basalt-ui 1.30.2 → 1.32.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 (357) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +54 -0
  3. package/MIGRATING.md +256 -12
  4. package/agent/rules/basalt-controls.md +19 -19
  5. package/agent/rules/basalt-mantine.md +5 -5
  6. package/configs/oxlint-plugin.js +371 -4
  7. package/configs/oxlint-plugin.test.ts +327 -2
  8. package/configs/oxlint.json +2 -1
  9. package/dist/agent-chat/composer.d.ts.map +1 -1
  10. package/dist/agent-chat/composer.js +41 -43
  11. package/dist/agent-chat/thread-workspace.d.ts +2 -2
  12. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  13. package/dist/agent-chat/thread-workspace.js +3 -4
  14. package/dist/agent-chat/use-keyboard-inset.d.ts +2 -0
  15. package/dist/agent-chat/use-keyboard-inset.d.ts.map +1 -0
  16. package/dist/agent-chat/use-keyboard-inset.js +34 -0
  17. package/dist/charts/cursor/touch-pin.d.ts +45 -0
  18. package/dist/charts/cursor/touch-pin.d.ts.map +1 -0
  19. package/dist/charts/cursor/touch-pin.js +79 -0
  20. package/dist/charts/hooks/use-discrete-cursor.d.ts +66 -0
  21. package/dist/charts/hooks/use-discrete-cursor.d.ts.map +1 -0
  22. package/dist/charts/hooks/use-discrete-cursor.js +198 -0
  23. package/dist/charts/hooks/use-hysteresis.d.ts +18 -0
  24. package/dist/charts/hooks/use-hysteresis.d.ts.map +1 -0
  25. package/dist/charts/hooks/use-hysteresis.js +12 -0
  26. package/dist/charts/hooks/useBandPlot.d.ts +7 -0
  27. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  28. package/dist/charts/hooks/useBandPlot.js +62 -18
  29. package/dist/charts/hooks/useChartCursor.d.ts +40 -11
  30. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  31. package/dist/charts/hooks/useChartCursor.js +102 -11
  32. package/dist/charts/index.d.ts +0 -1
  33. package/dist/charts/index.d.ts.map +1 -1
  34. package/dist/charts/index.js +0 -10
  35. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  36. package/dist/charts/kinds/BandStrip.js +47 -82
  37. package/dist/charts/kinds/Donut.d.ts +9 -3
  38. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  39. package/dist/charts/kinds/Donut.js +110 -48
  40. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  41. package/dist/charts/kinds/DualPanel.js +138 -145
  42. package/dist/charts/kinds/Heatmap.d.ts +7 -0
  43. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  44. package/dist/charts/kinds/Heatmap.js +37 -16
  45. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  46. package/dist/charts/kinds/MirroredBars.js +68 -111
  47. package/dist/charts/kinds/band-chrome.d.ts +31 -0
  48. package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
  49. package/dist/charts/kinds/band-chrome.js +71 -0
  50. package/dist/charts/kinds/donut-layout.d.ts +47 -0
  51. package/dist/charts/kinds/donut-layout.d.ts.map +1 -0
  52. package/dist/charts/kinds/donut-layout.js +52 -0
  53. package/dist/charts/layout/auto-margin.d.ts +7 -2
  54. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  55. package/dist/charts/layout/auto-margin.js +12 -3
  56. package/dist/charts/layout/end-labels.d.ts +64 -0
  57. package/dist/charts/layout/end-labels.d.ts.map +1 -0
  58. package/dist/charts/layout/end-labels.js +76 -0
  59. package/dist/charts/primitives/Axes.d.ts +32 -4
  60. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  61. package/dist/charts/primitives/Axes.js +62 -16
  62. package/dist/charts/primitives/CartesianChart.d.ts +7 -6
  63. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  64. package/dist/charts/primitives/CartesianChart.js +264 -145
  65. package/dist/charts/primitives/ChartCard.d.ts +0 -14
  66. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  67. package/dist/charts/primitives/ChartCard.js +56 -21
  68. package/dist/charts/primitives/ChartFrame.d.ts +28 -22
  69. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  70. package/dist/charts/primitives/ChartFrame.js +116 -34
  71. package/dist/charts/primitives/ChartLegend.d.ts +19 -15
  72. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  73. package/dist/charts/primitives/ChartLegend.js +86 -73
  74. package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
  75. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  76. package/dist/charts/primitives/ChartTooltip.js +20 -4
  77. package/dist/charts/primitives/Crosshair.js +2 -2
  78. package/dist/charts/primitives/HoverOverlay.d.ts +29 -9
  79. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  80. package/dist/charts/primitives/HoverOverlay.js +54 -27
  81. package/dist/charts/primitives/HoverOverlay.module.css +13 -0
  82. package/dist/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
  83. package/dist/charts/primitives/chart-card-context.d.ts +33 -0
  84. package/dist/charts/primitives/chart-card-context.d.ts.map +1 -0
  85. package/dist/charts/primitives/chart-card-context.js +18 -0
  86. package/dist/charts/primitives/chart-frame-layout.d.ts +93 -113
  87. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
  88. package/dist/charts/primitives/chart-frame-layout.js +75 -34
  89. package/dist/charts/primitives/chart-layout.d.ts +112 -0
  90. package/dist/charts/primitives/chart-layout.d.ts.map +1 -0
  91. package/dist/charts/primitives/chart-layout.js +175 -0
  92. package/dist/charts/primitives/chart-tier.d.ts +13 -11
  93. package/dist/charts/primitives/chart-tier.d.ts.map +1 -1
  94. package/dist/charts/primitives/chart-tier.js +42 -13
  95. package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
  96. package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
  97. package/dist/charts/primitives/legend-disclosure.js +153 -0
  98. package/dist/charts/series.d.ts +4 -1
  99. package/dist/charts/series.d.ts.map +1 -1
  100. package/dist/charts/utils/ticks.d.ts +27 -8
  101. package/dist/charts/utils/ticks.d.ts.map +1 -1
  102. package/dist/charts/utils/ticks.js +35 -11
  103. package/dist/common/errors.d.ts +11 -1
  104. package/dist/common/errors.d.ts.map +1 -1
  105. package/dist/common/errors.js +16 -2
  106. package/dist/common/hit-floor.d.ts +10 -0
  107. package/dist/common/hit-floor.d.ts.map +1 -0
  108. package/dist/common/hit-floor.js +6 -0
  109. package/dist/{shell → common}/isomorphic-layout-effect.d.ts +4 -3
  110. package/dist/common/isomorphic-layout-effect.d.ts.map +1 -0
  111. package/dist/common/use-measured-widths.d.ts +37 -0
  112. package/dist/common/use-measured-widths.d.ts.map +1 -0
  113. package/dist/common/use-measured-widths.js +51 -0
  114. package/dist/common/use-media-query.d.ts +6 -0
  115. package/dist/common/use-media-query.d.ts.map +1 -0
  116. package/dist/common/use-media-query.js +40 -0
  117. package/dist/common/validate.d.ts +9 -0
  118. package/dist/common/validate.d.ts.map +1 -1
  119. package/dist/common/validate.js +9 -4
  120. package/dist/content/article-layout.d.ts +4 -2
  121. package/dist/content/article-layout.d.ts.map +1 -1
  122. package/dist/content/article-layout.js +43 -3
  123. package/dist/content/article-layout.module.css +45 -4
  124. package/dist/content/article-layout.module.css.d.ts +3 -0
  125. package/dist/content/code-block.d.ts.map +1 -1
  126. package/dist/content/code-block.js +40 -2
  127. package/dist/content/code-block.module.css +32 -2
  128. package/dist/content/prose.module.css +13 -8
  129. package/dist/controls/actions.d.ts +29 -6
  130. package/dist/controls/actions.d.ts.map +1 -1
  131. package/dist/controls/actions.js +158 -27
  132. package/dist/controls/actions.module.css +14 -1
  133. package/dist/controls/actions.module.css.d.ts +1 -0
  134. package/dist/controls/controls.module.css +13 -31
  135. package/dist/controls/enum-filter.js +2 -2
  136. package/dist/controls/filter-context.d.ts +10 -0
  137. package/dist/controls/filter-context.d.ts.map +1 -1
  138. package/dist/controls/filter-context.js +2 -0
  139. package/dist/controls/filter-pill.d.ts.map +1 -1
  140. package/dist/controls/filter-pill.js +1 -0
  141. package/dist/controls/index.d.ts +1 -1
  142. package/dist/controls/index.d.ts.map +1 -1
  143. package/dist/controls/index.js +0 -2
  144. package/dist/controls/multi-select-filter.js +2 -2
  145. package/dist/controls/sync-button.module.css +2 -1
  146. package/dist/dashboard/section.d.ts.map +1 -1
  147. package/dist/dashboard/section.js +1 -0
  148. package/dist/dashboard/section.module.css +1 -26
  149. package/dist/dashboard/stat-card.d.ts +1 -1
  150. package/dist/dashboard/stat-card.d.ts.map +1 -1
  151. package/dist/dashboard/stat-card.js +4 -1
  152. package/dist/dashboard/stat-card.module.css +6 -9
  153. package/dist/dashboard/stat-group.module.css +33 -66
  154. package/dist/dashboard/widget-grid.d.ts.map +1 -1
  155. package/dist/dashboard/widget-grid.module.css +11 -38
  156. package/dist/dashboard/widget-header.d.ts +14 -4
  157. package/dist/dashboard/widget-header.d.ts.map +1 -1
  158. package/dist/dashboard/widget-header.js +23 -6
  159. package/dist/dashboard/widget-header.module.css +145 -133
  160. package/dist/dashboard/widget-header.module.css.d.ts +2 -0
  161. package/dist/data/data-table-state.d.ts +83 -0
  162. package/dist/data/data-table-state.d.ts.map +1 -0
  163. package/dist/data/data-table-state.js +139 -0
  164. package/dist/data/data-table.d.ts +56 -2
  165. package/dist/data/data-table.d.ts.map +1 -1
  166. package/dist/data/data-table.js +436 -336
  167. package/dist/data/data-table.module.css +94 -0
  168. package/dist/data/data-table.module.css.d.ts +11 -0
  169. package/dist/data/virtual-list.d.ts.map +1 -1
  170. package/dist/data/virtual-list.js +10 -0
  171. package/dist/forms/form-layout.d.ts.map +1 -1
  172. package/dist/forms/form-layout.js +3 -3
  173. package/dist/forms/form-layout.module.css +20 -5
  174. package/dist/forms/form-layout.module.css.d.ts +1 -0
  175. package/dist/guard/index.d.ts +8 -2
  176. package/dist/guard/index.d.ts.map +1 -1
  177. package/dist/guard/index.js +68 -2
  178. package/dist/guard/types.d.ts +3 -3
  179. package/dist/guard/types.d.ts.map +1 -1
  180. package/dist/index.d.ts +1 -1
  181. package/dist/index.d.ts.map +1 -1
  182. package/dist/index.js +3 -1
  183. package/dist/notifications/center.d.ts.map +1 -1
  184. package/dist/provider/index.d.ts +20 -1
  185. package/dist/provider/index.d.ts.map +1 -1
  186. package/dist/provider/index.js +30 -8
  187. package/dist/shell/app-brand.d.ts.map +1 -1
  188. package/dist/shell/app-brand.js +1 -0
  189. package/dist/shell/app-brand.module.css +2 -2
  190. package/dist/shell/app-header.module.css +18 -8
  191. package/dist/shell/app-main.module.css +54 -3
  192. package/dist/shell/app-main.module.css.d.ts +1 -0
  193. package/dist/shell/app-mobile-nav.module.css +16 -4
  194. package/dist/shell/app-sidebar.module.css +4 -6
  195. package/dist/shell/aside-docking.d.ts +31 -0
  196. package/dist/shell/aside-docking.d.ts.map +1 -0
  197. package/dist/shell/aside-docking.js +19 -0
  198. package/dist/shell/index.d.ts +1 -0
  199. package/dist/shell/index.d.ts.map +1 -1
  200. package/dist/shell/index.js +56 -16
  201. package/dist/shell/mobile-nav-model.d.ts +1 -1
  202. package/dist/shell/page-aside.d.ts +22 -3
  203. package/dist/shell/page-aside.d.ts.map +1 -1
  204. package/dist/shell/page-aside.js +27 -13
  205. package/dist/shell/page-bar.d.ts.map +1 -1
  206. package/dist/shell/page-bar.js +10 -5
  207. package/dist/shell/page-bar.module.css +10 -34
  208. package/dist/shell/use-breakpoint.d.ts +3 -4
  209. package/dist/shell/use-breakpoint.d.ts.map +1 -1
  210. package/dist/shell/use-breakpoint.js +3 -25
  211. package/dist/shell/use-size-class.d.ts +6 -0
  212. package/dist/shell/use-size-class.d.ts.map +1 -0
  213. package/dist/shell/use-size-class.js +19 -0
  214. package/dist/state/persisted-or-local.d.ts +4 -1
  215. package/dist/state/persisted-or-local.d.ts.map +1 -1
  216. package/dist/state/persisted-or-local.js +4 -3
  217. package/dist/state/persisted.d.ts +10 -0
  218. package/dist/state/persisted.d.ts.map +1 -1
  219. package/dist/state/persisted.js +44 -14
  220. package/dist/styles.css +41 -0
  221. package/dist/surfaces.d.ts +4 -4
  222. package/dist/surfaces.d.ts.map +1 -1
  223. package/dist/surfaces.js +11 -3
  224. package/dist/theme/controls.module.css +7 -0
  225. package/dist/theme/ctl-theme.d.ts +1 -0
  226. package/dist/theme/ctl-theme.d.ts.map +1 -1
  227. package/dist/theme/ctl-theme.js +12 -4
  228. package/dist/theme/floating.module.css +37 -0
  229. package/dist/theme/floating.module.css.d.ts +3 -0
  230. package/dist/theme/index.d.ts +2 -2
  231. package/dist/theme/index.d.ts.map +1 -1
  232. package/dist/theme/index.js +34 -5
  233. package/dist/theme/nav-link.module.css +7 -0
  234. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  235. package/dist/tokens/index.d.ts +7 -16
  236. package/dist/tokens/index.d.ts.map +1 -1
  237. package/dist/tokens/index.js +23 -28
  238. package/dist/tokens/palette.d.ts +5 -27
  239. package/dist/tokens/palette.d.ts.map +1 -1
  240. package/dist/tokens/palette.js +8 -30
  241. package/dist/tokens/size-classes.d.ts +40 -0
  242. package/dist/tokens/size-classes.d.ts.map +1 -0
  243. package/dist/tokens/size-classes.js +29 -0
  244. package/dist/tokens.css +8 -4
  245. package/llms.txt +3 -3
  246. package/package.json +18 -17
  247. package/src/agent-chat/composer.tsx +74 -47
  248. package/src/agent-chat/thread-workspace.tsx +8 -11
  249. package/src/agent-chat/use-keyboard-inset.ts +68 -0
  250. package/src/charts/cursor/touch-pin.ts +138 -0
  251. package/src/charts/hooks/use-discrete-cursor.ts +335 -0
  252. package/src/charts/hooks/use-hysteresis.ts +26 -0
  253. package/src/charts/hooks/useBandPlot.ts +90 -27
  254. package/src/charts/hooks/useChartCursor.ts +211 -26
  255. package/src/charts/index.ts +0 -8
  256. package/src/charts/kinds/BandStrip.tsx +25 -61
  257. package/src/charts/kinds/Donut.tsx +153 -62
  258. package/src/charts/kinds/DualPanel.tsx +21 -26
  259. package/src/charts/kinds/Heatmap.tsx +61 -22
  260. package/src/charts/kinds/MirroredBars.tsx +23 -65
  261. package/src/charts/kinds/band-chrome.tsx +91 -0
  262. package/src/charts/kinds/donut-layout.ts +82 -0
  263. package/src/charts/layout/auto-margin.ts +24 -5
  264. package/src/charts/layout/end-labels.ts +135 -0
  265. package/src/charts/primitives/Axes.tsx +112 -21
  266. package/src/charts/primitives/CartesianChart.tsx +282 -111
  267. package/src/charts/primitives/ChartCard.tsx +119 -48
  268. package/src/charts/primitives/ChartFrame.tsx +204 -60
  269. package/src/charts/primitives/ChartLegend.tsx +115 -78
  270. package/src/charts/primitives/ChartTooltip.tsx +24 -3
  271. package/src/charts/primitives/Crosshair.tsx +2 -2
  272. package/src/charts/primitives/HoverOverlay.module.css +13 -0
  273. package/src/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
  274. package/src/charts/primitives/HoverOverlay.tsx +62 -20
  275. package/src/charts/primitives/chart-card-context.ts +46 -0
  276. package/src/charts/primitives/chart-frame-layout.ts +207 -182
  277. package/src/charts/primitives/chart-layout.ts +318 -0
  278. package/src/charts/primitives/chart-tier.tsx +66 -21
  279. package/src/charts/primitives/legend-disclosure.tsx +177 -0
  280. package/src/charts/series.ts +4 -1
  281. package/src/charts/utils/ticks.ts +95 -19
  282. package/src/common/errors.ts +25 -1
  283. package/src/common/hit-floor.ts +9 -0
  284. package/src/{shell → common}/isomorphic-layout-effect.ts +4 -3
  285. package/src/common/use-measured-widths.ts +88 -0
  286. package/src/common/use-media-query.ts +56 -0
  287. package/src/common/validate.ts +17 -4
  288. package/src/content/article-layout.module.css +45 -4
  289. package/src/content/article-layout.module.css.d.ts +3 -0
  290. package/src/content/article-layout.tsx +60 -5
  291. package/src/content/code-block.module.css +32 -2
  292. package/src/content/code-block.tsx +47 -3
  293. package/src/content/prose.module.css +13 -8
  294. package/src/controls/actions.module.css +14 -1
  295. package/src/controls/actions.module.css.d.ts +1 -0
  296. package/src/controls/actions.tsx +223 -75
  297. package/src/controls/controls.module.css +13 -31
  298. package/src/controls/enum-filter.tsx +2 -2
  299. package/src/controls/filter-context.tsx +11 -0
  300. package/src/controls/filter-pill.tsx +1 -0
  301. package/src/controls/index.ts +0 -1
  302. package/src/controls/multi-select-filter.tsx +2 -2
  303. package/src/controls/sync-button.module.css +2 -1
  304. package/src/dashboard/section.module.css +1 -26
  305. package/src/dashboard/section.tsx +1 -0
  306. package/src/dashboard/stat-card.module.css +6 -9
  307. package/src/dashboard/stat-card.tsx +6 -3
  308. package/src/dashboard/stat-group.module.css +33 -66
  309. package/src/dashboard/widget-grid.module.css +11 -38
  310. package/src/dashboard/widget-grid.tsx +7 -6
  311. package/src/dashboard/widget-header.module.css +145 -133
  312. package/src/dashboard/widget-header.module.css.d.ts +2 -0
  313. package/src/dashboard/widget-header.tsx +45 -11
  314. package/src/data/data-table-state.ts +218 -0
  315. package/src/data/data-table.module.css +94 -0
  316. package/src/data/data-table.module.css.d.ts +11 -0
  317. package/src/data/data-table.tsx +547 -299
  318. package/src/data/virtual-list.tsx +31 -0
  319. package/src/forms/form-layout.module.css +20 -5
  320. package/src/forms/form-layout.module.css.d.ts +1 -0
  321. package/src/forms/form-layout.tsx +12 -10
  322. package/src/guard/index.ts +188 -3
  323. package/src/guard/types.ts +3 -2
  324. package/src/index.ts +2 -0
  325. package/src/notifications/center.tsx +3 -0
  326. package/src/provider/index.tsx +60 -7
  327. package/src/shell/app-brand.module.css +2 -2
  328. package/src/shell/app-brand.tsx +1 -0
  329. package/src/shell/app-header.module.css +18 -8
  330. package/src/shell/app-main.module.css +54 -3
  331. package/src/shell/app-main.module.css.d.ts +1 -0
  332. package/src/shell/app-mobile-nav.module.css +16 -4
  333. package/src/shell/app-sidebar.module.css +4 -6
  334. package/src/shell/aside-docking.ts +54 -0
  335. package/src/shell/index.tsx +178 -129
  336. package/src/shell/mobile-nav-model.ts +1 -1
  337. package/src/shell/page-aside.tsx +70 -21
  338. package/src/shell/page-bar.module.css +10 -34
  339. package/src/shell/page-bar.tsx +10 -5
  340. package/src/shell/use-breakpoint.ts +9 -45
  341. package/src/shell/use-size-class.ts +31 -0
  342. package/src/state/persisted-or-local.ts +10 -4
  343. package/src/state/persisted.ts +76 -17
  344. package/src/styles.css +41 -0
  345. package/src/surfaces.ts +9 -1
  346. package/src/theme/controls.module.css +7 -0
  347. package/src/theme/ctl-theme.tsx +15 -3
  348. package/src/theme/floating.module.css +37 -0
  349. package/src/theme/floating.module.css.d.ts +3 -0
  350. package/src/theme/index.ts +46 -8
  351. package/src/theme/nav-link.module.css +7 -0
  352. package/src/theme-lab/derive-controls.tsx +2 -0
  353. package/src/tokens/index.ts +20 -30
  354. package/src/tokens/palette.ts +6 -28
  355. package/src/tokens/size-classes.ts +46 -0
  356. package/dist/shell/isomorphic-layout-effect.d.ts.map +0 -1
  357. /package/dist/{shell → common}/isomorphic-layout-effect.js +0 -0
@@ -31,7 +31,7 @@ import type { ReactNode } from 'react'
31
31
  import type { BasaltProps } from '../common/props'
32
32
  import type { FieldOption } from '../state'
33
33
  import classes from './controls.module.css'
34
- import { useFilterRegistration, useFilterSurface } from './filter-context'
34
+ import { POPOVER_ROW_HIT_GAP, useFilterRegistration, useFilterSurface } from './filter-context'
35
35
  import { FilterPill } from './filter-pill'
36
36
  import { useControlName } from './filter-sheet'
37
37
  import { PanelChoice, PanelRow } from './panel-row'
@@ -147,7 +147,7 @@ export function EnumFilter<T extends string>({
147
147
  setValue(next as T)
148
148
  }}
149
149
  >
150
- <Stack gap={2}>
150
+ <Stack gap={POPOVER_ROW_HIT_GAP} style={{ '--vx-hit-gap': 'var(--stack-gap)' }}>
151
151
  {options.map((option) => (
152
152
  <Radio
153
153
  key={option.value}
@@ -32,6 +32,17 @@ import {
32
32
  } from 'react'
33
33
  import type { ReactNode } from 'react'
34
34
 
35
+ /**
36
+ * The real gap a popover row (`EnumFilter`'s `Radio.Group`, `MultiSelectFilter`'s `Checkbox.Group`)
37
+ * needs so its `[data-basalt-hit]::after` overlay can reach the full 44px coarse floor with no
38
+ * overlap into the next row — same derivation as the chart legend's dots mode (`DOTS_HIT_GAP`):
39
+ * `hit - rowHeight - 2 + 4` headroom, rowHeight being Mantine's default `Radio`/`Checkbox` label row
40
+ * (~21px). A bare `gap={2}` left the `--vx-hit-gap` cap able to buy only ~4px of the 44
41
+ * (code-foundations F1). Shared rather than duplicated per file: both popover rows share the exact
42
+ * same row height and floor, so one drift point beats two hand-kept copies.
43
+ */
44
+ export const POPOVER_ROW_HIT_GAP = 25
45
+
35
46
  /**
36
47
  * Which form a control renders. Provided by `FilterSet` (`'pill'` / `'sheet'`) and by `PageAside`
37
48
  * (`'panel'`); `'pill'` when there is neither.
@@ -71,6 +71,7 @@ export function FilterPill({
71
71
  variant="default"
72
72
  size="ctl"
73
73
  className={cx(classes.pill, className)}
74
+ data-basalt-hit
74
75
  classNames={{ label: classes.pillLabel }}
75
76
  {...(style !== undefined && { style })}
76
77
  {...(active === true && { 'data-active': true })}
@@ -8,7 +8,6 @@
8
8
  export {
9
9
  ActionGroup,
10
10
  OverflowMenu,
11
- DESKTOP_SECONDARY_MAX,
12
11
  MOBILE_GLOBAL_BAR_MAX,
13
12
  barActionMobile,
14
13
  globalActionMobile,
@@ -30,7 +30,7 @@ import { assertRequiredProps } from '../common/validate'
30
30
  import type { FieldHandle, MultiField } from '../state'
31
31
  import type { FilterOption } from './select-filter'
32
32
  import classes from './controls.module.css'
33
- import { useFilterRegistration, useFilterSurface } from './filter-context'
33
+ import { POPOVER_ROW_HIT_GAP, useFilterRegistration, useFilterSurface } from './filter-context'
34
34
  import { FilterPill } from './filter-pill'
35
35
  import { CheckGlyph, useControlName } from './filter-sheet'
36
36
  import { PanelRow } from './panel-row'
@@ -143,7 +143,7 @@ export function MultiSelectFilter<T extends string>(props: MultiSelectFilterProp
143
143
  setValue(next as readonly T[])
144
144
  }}
145
145
  >
146
- <Stack gap={2}>
146
+ <Stack gap={POPOVER_ROW_HIT_GAP} style={{ '--vx-hit-gap': 'var(--stack-gap)' }}>
147
147
  {options.map((option) => (
148
148
  <Checkbox
149
149
  key={option.value}
@@ -45,7 +45,8 @@
45
45
  * width itself, pinned to the `ctl` height so the box is square. This module is unlayered, so it
46
46
  * outranks `@layer mantine` regardless of specificity.
47
47
  */
48
- @media (max-width: 47.99375em) {
48
+ /* theme-allow raw-media-query — the control's own C9 icon-only swap at the compact size class */
49
+ @media (max-width: 52.49375em) {
49
50
  .label,
50
51
  .age {
51
52
  display: none;
@@ -18,6 +18,7 @@
18
18
  * query at all).
19
19
  */
20
20
  .root {
21
+ container: basalt-card / inline-size;
21
22
  display: flex;
22
23
  flex-direction: column;
23
24
  gap: var(--vx-space-section-gap, 0.75rem);
@@ -95,29 +96,3 @@
95
96
  .chevron[data-open='false'] svg {
96
97
  transform: rotate(-90deg);
97
98
  }
98
-
99
- /*
100
- * Law C15 — the chevron is a 12px glyph in a 20px box, which is a fine mouse target and an
101
- * unusable thumb one. Below `sm` an `::after` overlay lifts the HIT area to
102
- * `--vx-space-touch-control-height` (36px at level 0) while the visible glyph stays put — the same
103
- * overlay `shell/page-bar.module.css` draws for the bar's own controls. `overflow: visible` is part
104
- * of the fix, not tidying: an overlay that extends past the box is clipped by any `overflow:
105
- * hidden` on the host (which is what made the identical overlay a no-op on Mantine's Button roots).
106
- */
107
- @media (max-width: 47.99375em) {
108
- .chevron {
109
- overflow: visible;
110
- }
111
-
112
- .chevron::after {
113
- content: '';
114
- position: absolute;
115
- top: 50%;
116
- left: 50%;
117
- translate: -50% -50%;
118
- width: 100%;
119
- height: 100%;
120
- min-width: var(--vx-space-touch-control-height, 2.25rem);
121
- min-height: var(--vx-space-touch-control-height, 2.25rem);
122
- }
123
- }
@@ -156,6 +156,7 @@ function ChevronToggle({
156
156
  <button
157
157
  type="button"
158
158
  className={classes.chevron}
159
+ data-basalt-hit
159
160
  data-open={open}
160
161
  aria-expanded={open}
161
162
  aria-controls={controls}
@@ -1,11 +1,8 @@
1
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
- * BREAKDOWN row instead, the reference-design look, and collapses back to `bleed`'s stacked layout
6
- * below `sm` — CSS only for the sparkline's own box, `.metricsRow` below for the row it sits in
7
- * (`47.99375em` is Mantine's own complement of `min-width: 48em`, same value
8
- * `content/prose.module.css` derives it from).
2
+ * StatCard's sparkline placement (docs/CONTROLS-SPEC.md §2.2). `bleed` (default) keeps the full-width
3
+ * row beneath the hero value, bled past the card's inset padding. `right` sits the sparkline beside
4
+ * the BREAKDOWN row and collapses back to `bleed`'s stacked layout when the card itself is narrower
5
+ * than 480px (`container: basalt-card`, set on the Card root in `stat-card.tsx`).
9
6
  */
10
7
 
11
8
  .body {
@@ -131,7 +128,7 @@
131
128
  }
132
129
 
133
130
  /* Asks for `StatCard`'s `SPARKLINE_RIGHT_WIDTH`/`_HEIGHT` (handed down as custom properties, so this
134
- * rule and the `< sm` reset below can both reach them). `align-self: flex-end` is the one alignment
131
+ * rule and the narrow-card reset below can both reach them). `align-self: flex-end` is the one alignment
135
132
  * this restructure keeps at the SPARKLINE's own level rather than the row's: `.metricsRow` does not
136
133
  * set `align-items: flex-end` itself, because a card with no breakdown would then need nothing to
137
134
  * anchor against — only the sparkline opts into sitting at the row's bottom edge, which matters once
@@ -146,7 +143,7 @@
146
143
  overflow: hidden;
147
144
  }
148
145
 
149
- @media (max-width: 47.99375em) {
146
+ @container basalt-card (max-width: 479.9px) {
150
147
  .metricsRow {
151
148
  flex-direction: column;
152
149
  align-items: stretch;
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * `sparklinePlacement` decides where the trend visual sits: `'bleed'` (default) keeps the historic
13
13
  * full-width row beneath the hero value, bled past the card's own inset padding; `'right'` sits it
14
- * beside the hero row, the reference-design look. Below `sm`, `'right'` collapses back to the
14
+ * beside the hero row, the reference-design look. In a card narrower than 480px, `'right'` collapses back to the
15
15
  * `'bleed'` layout — CSS only, no JS branch (`stat-card.module.css`).
16
16
  *
17
17
  * `info` and `subtitle` are forwarded to that same `WidgetHeader`, and a hero KPI usually wants
@@ -277,7 +277,7 @@ export type StatCardProps = BasaltProps &
277
277
  */
278
278
  sparkline?: ReactNode | ((size: StatCardSparklineSize) => ReactNode)
279
279
  /** Where `sparkline` sits. `'bleed'` (default) is today's full-width row bled to the card edges;
280
- * `'right'` sits it beside the hero value row. Collapses to `'bleed'` below `sm`. */
280
+ * `'right'` sits it beside the hero value row. Collapses to `'bleed'` in a card narrower than 480px. */
281
281
  sparklinePlacement?: 'bleed' | 'right'
282
282
  /** Header-right slot (e.g. a ghost "..." menu trigger) — wrapped in `CtlSlot tier="widget"`, so a
283
283
  * raw `ActionIcon` with no `size` lands on the 24px step the 28px header row holds (C1/C5). */
@@ -357,7 +357,7 @@ export function StatCard({
357
357
  )}
358
358
  {...(!bleeds && {
359
359
  // CUSTOM PROPERTIES, not `flexBasis`/`height` directly. A React inline style beats every
360
- // stylesheet rule, so an inline `flexBasis: 72` also applied below `sm` — where `'right'`
360
+ // stylesheet rule, so an inline `flexBasis: 72` also applied in a narrow card — where `'right'`
361
361
  // collapses `.metricsRow` to a COLUMN and flex-basis is therefore the main-axis HEIGHT.
362
362
  // The mobile sparkline rendered in a 72px-tall box holding 26px of bars, which is the dead
363
363
  // band under every KPI value on a phone. Handed to CSS as values instead, the media query
@@ -400,6 +400,9 @@ export function StatCard({
400
400
  // Anchors the tone rail. Set unconditionally so a card's stacking context does not change
401
401
  // depending on whether it happens to have crossed a threshold this render.
402
402
  position: 'relative',
403
+ // The card's own width drives its chrome (`widget-header.module.css`, `.metricsRow`).
404
+ containerType: 'inline-size',
405
+ containerName: 'basalt-card',
403
406
  ...style,
404
407
  }}
405
408
  data-tone={tone}
@@ -2,8 +2,8 @@
2
2
  * StatGroup's column law and its optional rail, in CSS (law C9).
3
3
  *
4
4
  * **The law keys on the CONTAINER, not the viewport.** A viewport breakpoint answers the wrong
5
- * question — the same one `resolveChartTier` refuses to ask (`charts/primitives/chart-frame-layout.
6
- * ts`): the shell has regions that change the AVAILABLE width with the viewport held still (a
5
+ * question — the same one `resolveContainerClass` refuses to ask (`tokens/size-classes.ts`): the
6
+ * shell has regions that change the AVAILABLE width with the viewport held still (a
7
7
  * `PageAside` claiming its 300px, the sidebar collapsing 256px → its 48px rail, a consumer's own
8
8
  * split layout), and `@media` cannot see any of them. Measured defect: a `PageAside` mounted on a
9
9
  * 1512px desktop left the KPI row ~956px wide, `@media` still resolved `lg`, and four cells of
@@ -15,25 +15,24 @@
15
15
  * one: a query with no container resolves to false, so a shell-only context would have silently
16
16
  * pinned every non-shell consumer to the base count.
17
17
  *
18
- * The `@supports not` block below it is the pre-2023 fallback and the ONLY place the old viewport
19
- * law survives. It is written as an exclusive alternative rather than as a base the container
20
- * queries override, because the rail's `:nth-child` suppressors are MORE specific than the rule
21
- * that draws the border: two overlapping laws would each suppress a different set of leading cells
22
- * and the rail would lose hairlines in the middle of a row. One law at a time, chosen by support.
18
+ * The literals are the container-class law (`CONTAINER_CLASSES`, `tokens/size-classes.ts`): the
19
+ * `sm` count opens at `wide` (800px), not at the viewport-era 768. Two are StatGroup's own and say
20
+ * so: 260px (one column, below) and 1200px, where the full `cols` count opens. The class table
21
+ * stops at 800, and 800 is too early for a full KPI row — four cells of ~190px clamp a value a real
22
+ * dashboard prints (`tests/layout/page-aside.layout.test.ts` measures ~195px against the 844px row
23
+ * a claimed aside leaves). Conditions are px, never `em`: `em` inside a `@container` condition
24
+ * resolves against the CONTAINER's font-size, so a consumer panel with smaller text would silently
25
+ * move the boundary. A condition cannot read a custom property, so the numbers are written; the
26
+ * COUNTS are custom properties the component sets per instance.
23
27
  *
24
- * The literals: `@container` conditions are stated in px, `@media` ones in Mantine's own `em`
25
- * (`--mantine-breakpoint-sm` 48em, `--mantine-breakpoint-lg` 75em). Both spellings name the same
26
- * boundary at the 16px initial font size — 768px and 1200px — but `em` inside a `@container`
27
- * condition resolves against the CONTAINER's font-size rather than the root's, so a consumer panel
28
- * with smaller text would silently move the breakpoint. px is the only spelling that cannot drift.
29
- * (`47.99375em` → `767.9px`, `74.99375em` → `1199.9px`; the same complements `stat-card.module.css`
30
- * uses.) A condition still cannot read a custom property either way, so the numbers are written;
31
- * the COUNTS are custom properties the component sets per instance.
28
+ * No viewport fallback: basalt's own CSS already requires `color-mix()` (Safari 16.2, Chrome 111,
29
+ * Firefox 113), and every one of those parses `@container` — a `@supports not (container-type)`
30
+ * block is unreachable in any browser that renders basalt at all.
32
31
  */
33
32
 
34
33
  .container {
35
34
  container-type: inline-size;
36
- container-name: basalt-stat-group;
35
+ container-name: basalt-grid;
37
36
  }
38
37
 
39
38
  .root {
@@ -44,13 +43,26 @@
44
43
  gap: var(--vx-space-stack-md);
45
44
  }
46
45
 
47
- @container basalt-stat-group (min-width: 768px) {
46
+ /* 260px is StatGroup's own literal (docs/DESIGN-CORE.md § Layout, elevation, shapes), not a container class: one column below it,
47
+ * so every cell leads a row and the rail draws no leading hairline at all. */
48
+ @container basalt-grid (max-width: 259.9px) {
49
+ .root {
50
+ grid-template-columns: minmax(0, 1fr);
51
+ }
52
+
53
+ .root[data-divided] > *:nth-child(n) {
54
+ border-inline-start: 0;
55
+ padding-inline-start: 0;
56
+ }
57
+ }
58
+
59
+ @container basalt-grid (min-width: 800px) {
48
60
  .root {
49
61
  grid-template-columns: repeat(var(--basalt-stat-group-cols-sm), minmax(0, 1fr));
50
62
  }
51
63
  }
52
64
 
53
- @container basalt-stat-group (min-width: 1200px) {
65
+ @container basalt-grid (min-width: 1200px) {
54
66
  .root {
55
67
  grid-template-columns: repeat(var(--basalt-stat-group-cols-lg), minmax(0, 1fr));
56
68
  }
@@ -71,14 +83,14 @@
71
83
  padding-inline-start: var(--vx-space-stack-md);
72
84
  }
73
85
 
74
- @container basalt-stat-group (max-width: 767.9px) {
86
+ @container basalt-grid (max-width: 799.9px) {
75
87
  .root[data-divided] > *:nth-child(2n + 1) {
76
88
  border-inline-start: 0;
77
89
  padding-inline-start: 0;
78
90
  }
79
91
  }
80
92
 
81
- @container basalt-stat-group (min-width: 768px) and (max-width: 1199.9px) {
93
+ @container basalt-grid (min-width: 800px) and (max-width: 1199.9px) {
82
94
  .root[data-divided][data-sm-cols='2'] > *:nth-child(2n + 1),
83
95
  .root[data-divided][data-sm-cols='3'] > *:nth-child(3n + 1) {
84
96
  border-inline-start: 0;
@@ -86,7 +98,7 @@
86
98
  }
87
99
  }
88
100
 
89
- @container basalt-stat-group (min-width: 1200px) {
101
+ @container basalt-grid (min-width: 1200px) {
90
102
  .root[data-divided][data-cols='2'] > *:nth-child(2n + 1),
91
103
  .root[data-divided][data-cols='3'] > *:nth-child(3n + 1),
92
104
  .root[data-divided][data-cols='4'] > *:nth-child(4n + 1),
@@ -95,48 +107,3 @@
95
107
  padding-inline-start: 0;
96
108
  }
97
109
  }
98
-
99
- /*
100
- * The pre-container-query fallback — Safari < 16, Chrome < 106, Firefox < 110. Verbatim the law
101
- * this file shipped before, keyed on the viewport, and reachable ONLY where `@container` is not.
102
- * A browser that cannot parse `container-type` also cannot parse the `@container` blocks above, so
103
- * the two sets are never live at once.
104
- */
105
- @supports not (container-type: inline-size) {
106
- @media (min-width: 48em) {
107
- .root {
108
- grid-template-columns: repeat(var(--basalt-stat-group-cols-sm), minmax(0, 1fr));
109
- }
110
- }
111
-
112
- @media (min-width: 75em) {
113
- .root {
114
- grid-template-columns: repeat(var(--basalt-stat-group-cols-lg), minmax(0, 1fr));
115
- }
116
- }
117
-
118
- @media (max-width: 47.99375em) {
119
- .root[data-divided] > *:nth-child(2n + 1) {
120
- border-inline-start: 0;
121
- padding-inline-start: 0;
122
- }
123
- }
124
-
125
- @media (min-width: 48em) and (max-width: 74.99375em) {
126
- .root[data-divided][data-sm-cols='2'] > *:nth-child(2n + 1),
127
- .root[data-divided][data-sm-cols='3'] > *:nth-child(3n + 1) {
128
- border-inline-start: 0;
129
- padding-inline-start: 0;
130
- }
131
- }
132
-
133
- @media (min-width: 75em) {
134
- .root[data-divided][data-cols='2'] > *:nth-child(2n + 1),
135
- .root[data-divided][data-cols='3'] > *:nth-child(3n + 1),
136
- .root[data-divided][data-cols='4'] > *:nth-child(4n + 1),
137
- .root[data-divided][data-cols='5'] > *:nth-child(5n + 1) {
138
- border-inline-start: 0;
139
- padding-inline-start: 0;
140
- }
141
- }
142
- }
@@ -15,18 +15,19 @@
15
15
  * container. A span resolved against the viewport while the tracks follow the container is exactly
16
16
  * the phantom-track defect `WidgetGrid.Item`'s context clamp exists to prevent.
17
17
  *
18
- * The literals: px in `@container`, Mantine's own `em` in the `@supports not` fallback — same
19
- * boundary (768px / 1200px at the 16px initial font size), but `em` inside a `@container`
20
- * condition resolves against the CONTAINER's font-size, so px is the only spelling that cannot
21
- * drift. A condition cannot read a custom property either way, so the numbers are written; the
22
- * COUNTS are custom properties, set once per instance by the component — three hand-written
23
- * `repeat()` blocks would have to enumerate every `cols` value, and the whole point of this
24
- * primitive is that a consumer states one desktop number and basalt owns the rest.
18
+ * The literals are the container-class law, as in `stat-group.module.css`: the `sm` count opens
19
+ * at `wide` (800px); 1200px, where the full `cols` count opens, is the grid's own boundary above
20
+ * the class table's top step. px, never `em` (an `em` condition resolves against the container's
21
+ * font-size). A condition cannot read a custom property, so the numbers are written; the COUNTS
22
+ * are custom properties, set once per instance by the component — three hand-written `repeat()`
23
+ * blocks would have to enumerate every `cols` value, and the whole point of this primitive is that
24
+ * a consumer states one desktop number and basalt owns the rest. No viewport fallback, for the
25
+ * reason `stat-group.module.css` states: nothing that renders basalt lacks `@container`.
25
26
  */
26
27
 
27
28
  .container {
28
29
  container-type: inline-size;
29
- container-name: basalt-widget-grid;
30
+ container-name: basalt-grid;
30
31
  }
31
32
 
32
33
  .root {
@@ -47,7 +48,7 @@
47
48
  min-width: 0;
48
49
  }
49
50
 
50
- @container basalt-widget-grid (min-width: 768px) {
51
+ @container basalt-grid (min-width: 800px) {
51
52
  .root {
52
53
  grid-template-columns: repeat(var(--basalt-widget-grid-cols-sm), minmax(0, 1fr));
53
54
  }
@@ -57,7 +58,7 @@
57
58
  }
58
59
  }
59
60
 
60
- @container basalt-widget-grid (min-width: 1200px) {
61
+ @container basalt-grid (min-width: 1200px) {
61
62
  .root {
62
63
  grid-template-columns: repeat(var(--basalt-widget-grid-cols-lg), minmax(0, 1fr));
63
64
  }
@@ -66,31 +67,3 @@
66
67
  grid-column: span var(--basalt-widget-grid-span-lg);
67
68
  }
68
69
  }
69
-
70
- /*
71
- * The pre-container-query fallback — Safari < 16, Chrome < 106, Firefox < 110. Verbatim the law
72
- * this file shipped before, keyed on the viewport, and reachable ONLY where `@container` is not: a
73
- * browser that cannot parse `container-type` cannot parse the blocks above either, so the two sets
74
- * are never live at once.
75
- */
76
- @supports not (container-type: inline-size) {
77
- @media (min-width: 48em) {
78
- .root {
79
- grid-template-columns: repeat(var(--basalt-widget-grid-cols-sm), minmax(0, 1fr));
80
- }
81
-
82
- .item {
83
- grid-column: span var(--basalt-widget-grid-span-sm);
84
- }
85
- }
86
-
87
- @media (min-width: 75em) {
88
- .root {
89
- grid-template-columns: repeat(var(--basalt-widget-grid-cols-lg), minmax(0, 1fr));
90
- }
91
-
92
- .item {
93
- grid-column: span var(--basalt-widget-grid-span-lg);
94
- }
95
- }
96
- }
@@ -10,7 +10,7 @@
10
10
  * **`cols` is the DESKTOP count, and it is the only number a consumer states.** The law is
11
11
  * `base 1 → sm min(cols, 2) → lg cols`: a phone gets one column because a dashboard card holds a
12
12
  * chart; a tablet gets at most two because a third makes every chart narrower than
13
- * `VX.phoneChartWidth`; the full count lands at `lg`. Mantine's `cols={{ … }}` object is
13
+ * `CONTAINER_CLASSES.regular`; the full count lands at `lg`. Mantine's `cols={{ … }}` object is
14
14
  * deliberately NOT forwarded — a responsive object on the public prop is exactly the seam that let
15
15
  * five call sites disagree.
16
16
  *
@@ -19,12 +19,13 @@
19
19
  * ACTUALLY has. The shell moves that width without moving the viewport — a `PageAside` claiming its
20
20
  * 300px, the sidebar collapsing 256px → its 48px rail, a consumer's split pane — and a `@media`
21
21
  * query sees none of it: it kept three chart columns in a 956px content box and every chart in the
22
- * row went narrower than `VX.phoneChartWidth`, which is the exact condition the `lg` rung exists to
23
- * avoid. See `widget-grid.module.css`'s header for the fallback.
22
+ * row went narrower than `CONTAINER_CLASSES.regular`, which is the exact condition the `lg` rung
23
+ * exists to avoid. See `widget-grid.module.css`'s header for the fallback.
24
24
  *
25
- * **This is the sanctioned place `lg` enters the package.** `sm` is still the only breakpoint a
26
- * consumer writes (`docs/CONTROLS-SPEC.md` §2); `WidgetGrid` and `StatGroup` own `lg` internally so
27
- * nobody else has to.
25
+ * **This is the sanctioned place `lg` enters the package.** A consumer never writes a
26
+ * breakpoint — component layout keys on the container's own width
27
+ * (`docs/DESIGN-CORE.md` § Layout, elevation, shapes); `WidgetGrid` and `StatGroup` own their `lg`
28
+ * container rung internally so nobody else has to.
28
29
  *
29
30
  * `WidgetGrid.Item` is for the one card that is wider than the rest — a hero chart beside two
30
31
  * stacked panels. `span` is likewise the DESKTOP span and is CLAMPED to the live column count at