basalt-ui 1.25.0 → 1.27.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 (476) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +26 -0
  3. package/MIGRATING.md +815 -23
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +160 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +118 -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 +1465 -36
  17. package/configs/oxlint-plugin.test.ts +1190 -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 +39 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +93 -0
  75. package/dist/controls/filter-context.d.ts +60 -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 +20 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +46 -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/number-filter.d.ts +26 -0
  99. package/dist/controls/number-filter.d.ts.map +1 -0
  100. package/dist/controls/number-filter.js +143 -0
  101. package/dist/controls/peer-boundary.test.d.ts +2 -0
  102. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  103. package/dist/controls/props-contract.test.d.ts +2 -0
  104. package/dist/controls/props-contract.test.d.ts.map +1 -0
  105. package/dist/controls/range-filter.d.ts +43 -0
  106. package/dist/controls/range-filter.d.ts.map +1 -0
  107. package/dist/controls/range-filter.js +135 -0
  108. package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
  109. package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -0
  110. package/dist/controls/search-filter.d.ts +10 -0
  111. package/dist/controls/search-filter.d.ts.map +1 -0
  112. package/dist/controls/search-filter.js +81 -0
  113. package/dist/controls/select-filter.d.ts +74 -0
  114. package/dist/controls/select-filter.d.ts.map +1 -0
  115. package/dist/controls/select-filter.js +10 -0
  116. package/dist/controls/sync-button.d.ts +38 -0
  117. package/dist/controls/sync-button.d.ts.map +1 -0
  118. package/dist/controls/sync-button.js +119 -0
  119. package/dist/controls/sync-button.module.css +63 -0
  120. package/dist/controls/sync-button.module.css.d.ts +11 -0
  121. package/dist/controls/sync-button.test.d.ts +2 -0
  122. package/dist/controls/sync-button.test.d.ts.map +1 -0
  123. package/dist/controls/toggle-filter.d.ts +9 -0
  124. package/dist/controls/toggle-filter.d.ts.map +1 -0
  125. package/dist/controls/toggle-filter.js +42 -0
  126. package/dist/controls/view-tabs.d.ts +23 -0
  127. package/dist/controls/view-tabs.d.ts.map +1 -0
  128. package/dist/controls/view-tabs.js +59 -0
  129. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  130. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  131. package/dist/controls-dates/date-range-picker.js +18 -0
  132. package/dist/controls-dates/index.d.ts +9 -0
  133. package/dist/controls-dates/index.d.ts.map +1 -0
  134. package/dist/controls-dates/index.js +4 -0
  135. package/dist/dashboard/delta-badge.d.ts +6 -12
  136. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  137. package/dist/dashboard/delta-badge.js +1 -50
  138. package/dist/dashboard/index.d.ts +1 -1
  139. package/dist/dashboard/index.d.ts.map +1 -1
  140. package/dist/dashboard/index.js +3 -1
  141. package/dist/dashboard/settings-section.d.ts +6 -4
  142. package/dist/dashboard/settings-section.d.ts.map +1 -1
  143. package/dist/dashboard/settings-section.js +23 -16
  144. package/dist/dashboard/settings-section.module.css +10 -0
  145. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  146. package/dist/dashboard/settings-section.test.d.ts +2 -0
  147. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  148. package/dist/dashboard/stat-card.d.ts +95 -9
  149. package/dist/dashboard/stat-card.d.ts.map +1 -1
  150. package/dist/dashboard/stat-card.js +108 -49
  151. package/dist/dashboard/stat-card.module.css +153 -0
  152. package/dist/dashboard/stat-card.module.css.d.ts +15 -0
  153. package/dist/data/data-table.d.ts +37 -15
  154. package/dist/data/data-table.d.ts.map +1 -1
  155. package/dist/data/data-table.js +96 -58
  156. package/dist/guard/grace.test.d.ts +20 -0
  157. package/dist/guard/grace.test.d.ts.map +1 -0
  158. package/dist/guard/index.d.ts +66 -3
  159. package/dist/guard/index.d.ts.map +1 -1
  160. package/dist/guard/index.js +78 -7
  161. package/dist/guard/types.d.ts +3 -3
  162. package/dist/guard/types.d.ts.map +1 -1
  163. package/dist/index.d.ts +13 -3
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js +13 -10
  166. package/dist/nav/types.d.ts +78 -2
  167. package/dist/nav/types.d.ts.map +1 -1
  168. package/dist/notifications/bell.d.ts +4 -1
  169. package/dist/notifications/bell.d.ts.map +1 -1
  170. package/dist/notifications/index.d.ts +1 -1
  171. package/dist/router-tanstack/field.d.ts +14 -0
  172. package/dist/router-tanstack/field.d.ts.map +1 -0
  173. package/dist/router-tanstack/field.js +4 -0
  174. package/dist/router-tanstack/index.d.ts +6 -2
  175. package/dist/router-tanstack/index.d.ts.map +1 -1
  176. package/dist/router-tanstack/index.js +4 -0
  177. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  178. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  179. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  180. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  181. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  182. package/dist/router-tanstack/search-param-store.js +16 -45
  183. package/dist/router-tanstack/search-store.d.ts +84 -0
  184. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  185. package/dist/router-tanstack/search-store.js +271 -0
  186. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  187. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  188. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  189. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  190. package/dist/router-tanstack/use-nav.d.ts +0 -2
  191. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  192. package/dist/router-tanstack/use-nav.js +34 -12
  193. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  194. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  195. package/dist/section/index.d.ts +2 -0
  196. package/dist/section/index.d.ts.map +1 -0
  197. package/dist/section/index.js +4 -0
  198. package/dist/section/section.d.ts +24 -0
  199. package/dist/section/section.d.ts.map +1 -0
  200. package/dist/section/section.js +126 -0
  201. package/dist/section/section.module.css +104 -0
  202. package/dist/section/section.module.css.d.ts +11 -0
  203. package/dist/section/section.test.d.ts +2 -0
  204. package/dist/section/section.test.d.ts.map +1 -0
  205. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  206. package/dist/shell/app-breadcrumbs.js +18 -15
  207. package/dist/shell/app-header.module.css +77 -37
  208. package/dist/shell/app-header.module.css.d.ts +2 -1
  209. package/dist/shell/app-mobile-nav.d.ts +9 -2
  210. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  211. package/dist/shell/app-mobile-nav.js +98 -11
  212. package/dist/shell/app-mobile-nav.module.css +8 -0
  213. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  214. package/dist/shell/app-sidebar.d.ts +55 -28
  215. package/dist/shell/app-sidebar.d.ts.map +1 -1
  216. package/dist/shell/app-sidebar.js +116 -67
  217. package/dist/shell/app-sidebar.module.css +303 -20
  218. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  219. package/dist/shell/index.d.ts +55 -21
  220. package/dist/shell/index.d.ts.map +1 -1
  221. package/dist/shell/index.js +40 -18
  222. package/dist/shell/mobile-nav-model.d.ts +13 -2
  223. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  224. package/dist/shell/mobile-nav-model.js +15 -1
  225. package/dist/shell/nav-count-badge.d.ts +1 -1
  226. package/dist/shell/page-bar.d.ts +51 -0
  227. package/dist/shell/page-bar.d.ts.map +1 -0
  228. package/dist/shell/page-bar.js +154 -0
  229. package/dist/shell/page-bar.module.css +178 -0
  230. package/dist/shell/page-bar.module.css.d.ts +17 -0
  231. package/dist/shell/page-bar.test.d.ts +2 -0
  232. package/dist/shell/page-bar.test.d.ts.map +1 -0
  233. package/dist/shell/sidebar-block-model.d.ts +48 -0
  234. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  235. package/dist/shell/sidebar-block-model.js +39 -0
  236. package/dist/shell/sidebar-blocks.d.ts +32 -0
  237. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  238. package/dist/shell/sidebar-blocks.js +169 -0
  239. package/dist/shell/sidebar-search.d.ts +13 -1
  240. package/dist/shell/sidebar-search.d.ts.map +1 -1
  241. package/dist/shell/sidebar-search.js +34 -1
  242. package/dist/shell/sidebar-search.module.css +26 -0
  243. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  244. package/dist/state/fields.d.ts +565 -0
  245. package/dist/state/fields.d.ts.map +1 -0
  246. package/dist/state/fields.js +405 -0
  247. package/dist/state/fields.test.d.ts +2 -0
  248. package/dist/state/fields.test.d.ts.map +1 -0
  249. package/dist/state/persisted.d.ts +60 -0
  250. package/dist/state/persisted.d.ts.map +1 -0
  251. package/dist/state/persisted.js +123 -0
  252. package/dist/state/persisted.test.d.ts +2 -0
  253. package/dist/state/persisted.test.d.ts.map +1 -0
  254. package/dist/state.d.ts +11 -56
  255. package/dist/state.d.ts.map +1 -1
  256. package/dist/state.js +14 -120
  257. package/dist/styles.css +13 -0
  258. package/dist/surfaces.d.ts +106 -24
  259. package/dist/surfaces.d.ts.map +1 -1
  260. package/dist/surfaces.js +156 -19
  261. package/dist/theme/controls.module.css +16 -1
  262. package/dist/theme/ctl-theme.d.ts +32 -0
  263. package/dist/theme/ctl-theme.d.ts.map +1 -0
  264. package/dist/theme/ctl-theme.js +54 -0
  265. package/dist/theme/ctl-theme.test.d.ts +2 -0
  266. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  267. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  268. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  269. package/dist/theme/divider-law.test.d.ts +2 -0
  270. package/dist/theme/divider-law.test.d.ts.map +1 -0
  271. package/dist/theme/icon-slot.d.ts +44 -0
  272. package/dist/theme/icon-slot.d.ts.map +1 -0
  273. package/dist/theme/icon-slot.js +21 -0
  274. package/dist/theme/icon-slot.module.css +47 -0
  275. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  276. package/dist/theme/icon-slot.test.d.ts +2 -0
  277. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  278. package/dist/theme/index.d.ts +1 -0
  279. package/dist/theme/index.d.ts.map +1 -1
  280. package/dist/theme/index.js +82 -5
  281. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  282. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  283. package/dist/theme/segmented-control.module.css +20 -0
  284. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  285. package/dist/tokens/index.d.ts +12 -0
  286. package/dist/tokens/index.d.ts.map +1 -1
  287. package/dist/tokens/index.js +38 -8
  288. package/dist/tokens/palette.d.ts +97 -61
  289. package/dist/tokens/palette.d.ts.map +1 -1
  290. package/dist/tokens/palette.js +76 -38
  291. package/dist/tokens.css +10 -2
  292. package/dist/widget-header/delta-badge.d.ts +13 -0
  293. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  294. package/dist/widget-header/delta-badge.js +29 -0
  295. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  296. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  297. package/dist/widget-header/index.d.ts +3 -0
  298. package/dist/widget-header/index.d.ts.map +1 -0
  299. package/dist/widget-header/index.js +6 -0
  300. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  301. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  302. package/dist/widget-header/widget-header.d.ts +67 -0
  303. package/dist/widget-header/widget-header.d.ts.map +1 -0
  304. package/dist/widget-header/widget-header.js +89 -0
  305. package/dist/widget-header/widget-header.module.css +350 -0
  306. package/dist/widget-header/widget-header.module.css.d.ts +25 -0
  307. package/dist/widget-header/widget-header.test.d.ts +2 -0
  308. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  309. package/llms.txt +30 -17
  310. package/package.json +14 -1
  311. package/src/charts/cursor/resolve.test.ts +29 -1
  312. package/src/charts/cursor/resolve.ts +38 -6
  313. package/src/charts/cursor/scope.tsx +6 -3
  314. package/src/charts/cursor/store.test.ts +19 -1
  315. package/src/charts/cursor/store.ts +10 -5
  316. package/src/charts/hooks/useChartCursor.ts +16 -8
  317. package/src/charts/index.ts +2 -2
  318. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  319. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  320. package/src/charts/primitives/ChartCard.tsx +86 -146
  321. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  322. package/src/cli/check-coverage.test.ts +297 -0
  323. package/src/cli/check-theme.test.ts +38 -14
  324. package/src/cli/index.ts +392 -16
  325. package/src/cli/init-sync.test.ts +97 -0
  326. package/src/connectivity/connectivity-indicator.tsx +16 -9
  327. package/src/content/index.ts +16 -3
  328. package/src/content/prose.module.css +7 -28
  329. package/src/content/prose.module.css.test.ts +8 -24
  330. package/src/controls/actions.module.css +62 -0
  331. package/src/controls/actions.module.css.d.ts +13 -0
  332. package/src/controls/actions.test.tsx +252 -0
  333. package/src/controls/actions.tsx +655 -0
  334. package/src/controls/actions.type-guard.test.ts +63 -0
  335. package/src/controls/compare-filter.tsx +64 -0
  336. package/src/controls/control-group.test.tsx +229 -0
  337. package/src/controls/control-group.tsx +63 -0
  338. package/src/controls/controls.module.css +542 -0
  339. package/src/controls/controls.module.css.d.ts +35 -0
  340. package/src/controls/controls.router.test.tsx +1053 -0
  341. package/src/controls/enum-filter.tsx +170 -0
  342. package/src/controls/filter-context.tsx +158 -0
  343. package/src/controls/filter-pill.tsx +120 -0
  344. package/src/controls/filter-set.test.tsx +472 -0
  345. package/src/controls/filter-set.tsx +225 -0
  346. package/src/controls/filter-sheet.tsx +285 -0
  347. package/src/controls/glyphs.tsx +81 -0
  348. package/src/controls/index.ts +49 -0
  349. package/src/controls/multi-select-filter.tsx +140 -0
  350. package/src/controls/number-filter.tsx +291 -0
  351. package/src/controls/peer-boundary.test.ts +85 -0
  352. package/src/controls/props-contract.test.tsx +137 -0
  353. package/src/controls/range-filter.tsx +237 -0
  354. package/src/controls/range-filter.type-guard.test.tsx +122 -0
  355. package/src/controls/search-filter.tsx +123 -0
  356. package/src/controls/select-filter.tsx +89 -0
  357. package/src/controls/sync-button.module.css +63 -0
  358. package/src/controls/sync-button.module.css.d.ts +11 -0
  359. package/src/controls/sync-button.test.tsx +183 -0
  360. package/src/controls/sync-button.tsx +197 -0
  361. package/src/controls/toggle-filter.tsx +60 -0
  362. package/src/controls/view-tabs.tsx +110 -0
  363. package/src/controls-dates/date-range-picker.tsx +42 -0
  364. package/src/controls-dates/index.ts +8 -0
  365. package/src/dashboard/delta-badge.tsx +4 -74
  366. package/src/dashboard/index.ts +6 -1
  367. package/src/dashboard/settings-section.module.css +10 -0
  368. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  369. package/src/dashboard/settings-section.test.tsx +56 -0
  370. package/src/dashboard/settings-section.tsx +33 -30
  371. package/src/dashboard/stat-card.module.css +153 -0
  372. package/src/dashboard/stat-card.module.css.d.ts +15 -0
  373. package/src/dashboard/stat-card.test.tsx +298 -1
  374. package/src/dashboard/stat-card.tsx +289 -67
  375. package/src/data/data-table.test.tsx +96 -11
  376. package/src/data/data-table.tsx +228 -84
  377. package/src/guard/check-source.test.ts +159 -13
  378. package/src/guard/grace.test.ts +158 -0
  379. package/src/guard/index.ts +185 -29
  380. package/src/guard/types.ts +4 -2
  381. package/src/index.ts +51 -5
  382. package/src/nav/types.ts +84 -2
  383. package/src/notifications/bell.tsx +6 -3
  384. package/src/notifications/index.ts +1 -1
  385. package/src/router-tanstack/field.ts +35 -0
  386. package/src/router-tanstack/index.ts +28 -2
  387. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  388. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  389. package/src/router-tanstack/search-param-store.ts +42 -159
  390. package/src/router-tanstack/search-store.router.test.tsx +882 -0
  391. package/src/router-tanstack/search-store.test.ts +578 -0
  392. package/src/router-tanstack/search-store.ts +546 -0
  393. package/src/router-tanstack/use-nav.test.tsx +187 -0
  394. package/src/router-tanstack/use-nav.ts +72 -14
  395. package/src/section/index.ts +1 -0
  396. package/src/section/section.module.css +104 -0
  397. package/src/section/section.module.css.d.ts +11 -0
  398. package/src/section/section.test.tsx +204 -0
  399. package/src/section/section.tsx +220 -0
  400. package/src/shell/app-breadcrumbs.tsx +44 -31
  401. package/src/shell/app-header.module.css +77 -37
  402. package/src/shell/app-header.module.css.d.ts +2 -1
  403. package/src/shell/app-mobile-nav.module.css +8 -0
  404. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  405. package/src/shell/app-mobile-nav.tsx +147 -8
  406. package/src/shell/app-sidebar-account.tsx +2 -2
  407. package/src/shell/app-sidebar.module.css +303 -20
  408. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  409. package/src/shell/app-sidebar.test.tsx +456 -41
  410. package/src/shell/app-sidebar.tsx +276 -140
  411. package/src/shell/index.test.tsx +291 -5
  412. package/src/shell/index.tsx +137 -38
  413. package/src/shell/mobile-nav-model.test.ts +228 -7
  414. package/src/shell/mobile-nav-model.ts +53 -3
  415. package/src/shell/nav-count-badge.tsx +1 -1
  416. package/src/shell/page-bar.module.css +178 -0
  417. package/src/shell/page-bar.module.css.d.ts +17 -0
  418. package/src/shell/page-bar.test.tsx +316 -0
  419. package/src/shell/page-bar.tsx +315 -0
  420. package/src/shell/sidebar-block-model.ts +83 -0
  421. package/src/shell/sidebar-blocks.tsx +301 -0
  422. package/src/shell/sidebar-search.module.css +26 -0
  423. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  424. package/src/shell/sidebar-search.tsx +71 -2
  425. package/src/state/fields.test.tsx +512 -0
  426. package/src/state/fields.ts +1150 -0
  427. package/src/state/persisted.test.ts +91 -0
  428. package/src/state/persisted.ts +261 -0
  429. package/src/state.ts +52 -240
  430. package/src/styles.css +13 -0
  431. package/src/surfaces.ts +204 -36
  432. package/src/theme/controls.module.css +16 -1
  433. package/src/theme/ctl-theme.test.tsx +275 -0
  434. package/src/theme/ctl-theme.tsx +138 -0
  435. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  436. package/src/theme/density-relations.test.ts +106 -51
  437. package/src/theme/divider-law.test.ts +259 -0
  438. package/src/theme/icon-slot.module.css +47 -0
  439. package/src/theme/icon-slot.module.css.d.ts +8 -0
  440. package/src/theme/icon-slot.test.tsx +275 -0
  441. package/src/theme/icon-slot.tsx +59 -0
  442. package/src/theme/index.ts +165 -3
  443. package/src/theme/layout-rhythm-css.test.ts +72 -0
  444. package/src/theme/radius.test.ts +11 -1
  445. package/src/theme/segmented-control.module.css +20 -0
  446. package/src/theme/shadow-surfaces.test.ts +20 -7
  447. package/src/theme/spacing.test.ts +75 -27
  448. package/src/theme/use-basalt-spacing.ts +2 -2
  449. package/src/tokens/density.test.ts +7 -25
  450. package/src/tokens/index.ts +48 -8
  451. package/src/tokens/palette.ts +107 -73
  452. package/src/widget-header/delta-badge.test.tsx +60 -0
  453. package/src/widget-header/delta-badge.tsx +57 -0
  454. package/src/widget-header/index.ts +2 -0
  455. package/src/widget-header/no-mantine.test.ts +33 -0
  456. package/src/widget-header/widget-header.module.css +350 -0
  457. package/src/widget-header/widget-header.module.css.d.ts +25 -0
  458. package/src/widget-header/widget-header.test.tsx +187 -0
  459. package/src/widget-header/widget-header.tsx +213 -0
  460. package/agent/rules/basalt-agent.md +0 -452
  461. package/agent/rules/basalt-app.md +0 -246
  462. package/agent/rules/basalt-commands.md +0 -320
  463. package/agent/rules/basalt-content.md +0 -409
  464. package/agent/rules/basalt-data.md +0 -329
  465. package/agent/rules/basalt-forms.md +0 -186
  466. package/agent/rules/basalt-notifications.md +0 -264
  467. package/agent/rules/basalt-query.md +0 -187
  468. package/agent/rules/basalt-router.md +0 -336
  469. package/dist/content/article-filter-bar.d.ts +0 -40
  470. package/dist/content/article-filter-bar.d.ts.map +0 -1
  471. package/dist/content/article-filter-bar.js +0 -56
  472. package/dist/shell/page-header.d.ts +0 -13
  473. package/dist/shell/page-header.d.ts.map +0 -1
  474. package/dist/shell/page-header.js +0 -23
  475. package/src/content/article-filter-bar.tsx +0 -88
  476. package/src/shell/page-header.tsx +0 -35
@@ -0,0 +1,20 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Box } from "@mantine/core";
3
+ import classes from "./controls.module.css";
4
+ function ControlGroup({ children, gap = "none", className }) {
5
+ return (
6
+ // A Mantine `Box`, not a raw `<div>` — `basalt/raw-html-layout` flags layout styling on a bare
7
+ // HTML tag, the same reason `CtlSlot` uses one.
8
+ /* @__PURE__ */ jsx(
9
+ Box,
10
+ {
11
+ className: className === void 0 ? classes.group : `${classes.group} ${className}`,
12
+ "data-gap": gap,
13
+ children
14
+ }
15
+ )
16
+ );
17
+ }
18
+ export {
19
+ ControlGroup
20
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=control-group.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-group.test.d.ts","sourceRoot":"","sources":["../../src/controls/control-group.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,542 @@
1
+ /*
2
+ * `basalt-ui/controls` — the filter/tab home surface (docs/CONTROLS-SPEC.md §3).
3
+ *
4
+ * Two form factors per control, ONE mount each, switched by CSS only (C9): the `pill` surface
5
+ * (bordered 30px chip + popover) and the `sheet` surface (44px full-width rows inside the mobile
6
+ * `Filters (n)` Drawer). `48em` / `47.99375em` are the `sm` breakpoint in the exact literal form
7
+ * every other basalt CSS module uses — a media query cannot resolve a custom property, so the
8
+ * breakpoint cannot be a token (see `content/prose.module.css`'s note).
9
+ */
10
+
11
+ /* ── The pill row (FilterSet, desktop + the mobile inline head) ───────────────────────────────── */
12
+
13
+ /*
14
+ * `flex: 1 1 auto` is what makes `useOverflowFit`'s measurement HONEST, and its absence is what made
15
+ * the fold fire at 1440px with 600px of free space. Left at the flex default (`0 1 auto`) the row
16
+ * sizes to its own CONTENT, so `row.clientWidth` returned exactly Σ(slot widths + gaps) — the fold
17
+ * compared the pills against themselves, went over by a subpixel, and folded the tail on every
18
+ * viewport. Growing the row to the space its container actually offers is the only way the measure
19
+ * can ever mean "does this fit". `min-width: 0` keeps it shrinkable so an OVERFLOWING row still
20
+ * reports the available width rather than the content width. Together with `.pillSlot`'s
21
+ * `flex-shrink: 0` (see below) the two sides of the comparison are now measured independently.
22
+ */
23
+ .pillRow {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--vx-space-control-gap, 0.375rem);
27
+ flex: 1 1 auto;
28
+ min-width: 0;
29
+ flex-wrap: nowrap;
30
+ }
31
+
32
+ /*
33
+ * Every child stays MOUNTED in the row on every viewport — folding and the mobile `inline` budget
34
+ * hide slots with `display: none` instead of unmounting them. That is what keeps FilterSet's active
35
+ * count and `Reset all` complete: the row is the single registration surface, so a filter that
36
+ * scrolled out of the `inline` budget still reports itself (see `filter-context.tsx`).
37
+ */
38
+ /*
39
+ * `flex-shrink: 0` is what makes the fold WORK, not a cosmetic choice. A shrinkable slot lets flex
40
+ * squeeze every pill until Σ widths == the row width, so `useOverflowFit` would sum
41
+ * already-compressed boxes, conclude everything fits, and never show `+N` — precisely when the row
42
+ * is overflowing. Incompressible slots report their natural width, so the sum honestly exceeds the
43
+ * budget and the tail folds on the same measure tick. `min-width: 0` stays off deliberately for the
44
+ * same reason.
45
+ */
46
+ .pillSlot {
47
+ display: inline-flex;
48
+ flex-shrink: 0;
49
+ }
50
+
51
+ /* Below `sm` only the first `inline` slots are pills; the rest live in the sheet. */
52
+ @media (max-width: 47.99375em) {
53
+ .pillSlot:not([data-inline]) {
54
+ display: none;
55
+ }
56
+ }
57
+
58
+ /*
59
+ * Folding is a DESKTOP concern only — scoped to `min-width` so a width measured on a wide viewport
60
+ * can never hide a slot the mobile `inline` budget is supposed to keep.
61
+ */
62
+ @media (min-width: 48em) {
63
+ .pillSlot[data-folded] {
64
+ display: none;
65
+ }
66
+ }
67
+
68
+ /* `+N` (desktop fold) and `Filters (n)` (mobile sheet opener) are each one pill, never both. */
69
+ .foldPill {
70
+ display: none;
71
+ }
72
+
73
+ @media (min-width: 48em) {
74
+ .foldPill[data-shown] {
75
+ display: inline-flex;
76
+ }
77
+ }
78
+
79
+ .sheetPill {
80
+ display: none;
81
+ }
82
+
83
+ @media (max-width: 47.99375em) {
84
+ .sheetPill {
85
+ display: inline-flex;
86
+ }
87
+ }
88
+
89
+ /* ── FilterPill ───────────────────────────────────────────────────────────────────────────────── */
90
+
91
+ /*
92
+ * The bordered chip. `variant="default" size="ctl"` carries the 30px height, the 10px inset
93
+ * (`--button-padding-x-ctl`) and the control radius from the theme; this module owns what the
94
+ * variant cannot express.
95
+ *
96
+ * **The border is one of those.** `cssVariablesResolver` points
97
+ * `--mantine-color-default-border` at `transparent` on purpose (depth is a shadow, never a hairline
98
+ * — `docs/DESIGN-SPEC.md` §8), so a `variant="default"` Button ships no visible edge at all. A
99
+ * filter pill is the one control that NEEDS one: it is a chip in a row of chips, and without an
100
+ * outline the row reads as loose text. Declared here rather than by relaxing the resolver, which
101
+ * would put a hairline back on every default button in the app.
102
+ *
103
+ * The ≥36px touch target below `sm` (C15) is drawn as a `::before` overlay so the visible box stays
104
+ * 30px.
105
+ */
106
+ .pill {
107
+ position: relative;
108
+ max-width: 15rem;
109
+ border-color: color-mix(in srgb, var(--vx-ink) 12%, transparent);
110
+ }
111
+
112
+ /*
113
+ * The inset stays 10px on BOTH edges, section or no section. Mantine divides `--button-padding-x` by
114
+ * 1.5 whenever a Button carries a left/right section (`Button.css`), which is right for a labelled
115
+ * `md` button and wrong here: every pill has a trailing `⇅` and most have a leading glyph, so the
116
+ * division applied to all of them and the whole row rendered at a 6.7px inset — visibly tighter than
117
+ * the buttons in the header above it, for no reason a reader could see. This module is unlayered, so
118
+ * it outranks `@layer mantine` regardless of specificity.
119
+ */
120
+ .pill[data-with-left-section],
121
+ .pill[data-with-right-section] {
122
+ padding-inline: var(--button-padding-x-ctl);
123
+ }
124
+
125
+ .pill[data-active] {
126
+ border-color: var(--vx-accent);
127
+ color: var(--vx-ink);
128
+ }
129
+
130
+ /*
131
+ * Mantine spends `--mantine-spacing-xs` (11px) between a Button's section and its label, which is
132
+ * the BODY tier's gap; a `ctl` chip wants the tier's own 6px so the icon reads as part of the label
133
+ * rather than a separate mark. Both sections, so the trailing `⇅` sits the same distance out.
134
+ */
135
+ .pill :global(.mantine-Button-section[data-position='left']) {
136
+ margin-inline-end: var(--vx-space-control-gap, 0.375rem);
137
+ }
138
+
139
+ .pill :global(.mantine-Button-section[data-position='right']) {
140
+ margin-inline-start: var(--vx-space-control-gap, 0.375rem);
141
+ }
142
+
143
+ /*
144
+ * The leading-icon box carries COLOUR ONLY now. The 16px box, the `display: block` reset on the
145
+ * glyph and the optical centring all live in `theme/icon-slot.module.css`, which every `icon` prop
146
+ * in the framework renders through — this class is composed onto that slot, not instead of it. The
147
+ * `> svg { width: 1rem }` pair that used to live here was the same mechanism written once per
148
+ * component, which is exactly how `BarAction`'s icon slot went un-boxed and let a consumer's
149
+ * `width="24"` glyph set a header row's height.
150
+ */
151
+ .pillIcon {
152
+ color: var(--vx-muted);
153
+ }
154
+
155
+ .pill[data-active] .pillIcon {
156
+ color: var(--vx-ink);
157
+ }
158
+
159
+ @media (max-width: 47.99375em) {
160
+ /* Mantine's Button root clips to its box; the overlay must escape it to be hit-testable. Loading
161
+ * buttons keep the clip their overlay depends on (same pairing as page-bar.module.css). */
162
+ .pill:not([data-loading]) {
163
+ overflow: visible;
164
+ }
165
+
166
+ .pill::before {
167
+ content: '';
168
+ position: absolute;
169
+ /* Both operands scale with `--mantine-scale` (same reconstruction the theme uses for every
170
+ * other ctl-tier box, `theme/index.ts`'s `ctlSizeVars`) so the overlay stays centred on — and
171
+ * sized off — the ACTUAL scaled pill box, not its unscaled rem value. */
172
+ inset-block: calc(
173
+ (
174
+ calc(var(--vx-space-control-height-ctl, 1.875rem) * var(--mantine-scale, 1)) -
175
+ calc(var(--vx-space-touch-control-height, 2.25rem) * var(--mantine-scale, 1))
176
+ ) /
177
+ 2
178
+ );
179
+ inset-inline: 0;
180
+ }
181
+ }
182
+
183
+ /* Label at the tier's own step and weight (`docs/CONTROLS-SPEC.md` §3) — Mantine's Button label
184
+ * inherits the root's `--button-fz` but carries the body weight, which reads light against the
185
+ * bordered box. */
186
+ .pillLabel {
187
+ overflow: hidden;
188
+ text-overflow: ellipsis;
189
+ white-space: nowrap;
190
+ font-size: var(--vx-text-sm);
191
+ font-weight: 500;
192
+ }
193
+
194
+ /* Numeric pill labels read mono at the smaller step. The FACE comes from the package-wide
195
+ * `[data-numeric]` law in `styles.css`; the `font-size` is the pill's own decision, which is why
196
+ * this rule exists at all. */
197
+ .pill[data-numeric] .pillLabel {
198
+ font-family: var(--basalt-font-mono);
199
+ font-size: var(--vx-text-xs);
200
+ }
201
+
202
+ /*
203
+ * Every pill's trailing affordance is the ONE icon in the tier that is deliberately smaller than the
204
+ * leading glyph — 14px, muted (`docs/CONTROLS-SPEC.md` §3). It gets there by overriding the icon
205
+ * slot's box var, never by sizing the SVG: the slot is the only thing that decides a glyph's
206
+ * geometry, so a smaller mark is a smaller BOX.
207
+ */
208
+ .glyph {
209
+ --vx-space-icon-size: 0.875rem;
210
+
211
+ color: var(--vx-faint);
212
+ }
213
+
214
+ /* ── Popover bodies ───────────────────────────────────────────────────────────────────────────── */
215
+
216
+ .dropdown {
217
+ min-width: 11rem;
218
+ max-width: 22rem;
219
+ }
220
+
221
+ .optionList {
222
+ display: flex;
223
+ flex-direction: column;
224
+ gap: 2px;
225
+ min-width: 0;
226
+ }
227
+
228
+ /* ── The sheet surface (inside the bottom Drawer) ─────────────────────────────────────────────── */
229
+
230
+ /*
231
+ * One rhythm, one kind of separator. Groups are 12px apart (`--vx-space-stack-md`) and NOTHING
232
+ * between them draws a line: the only hairline in the sheet is the one between two option rows of
233
+ * the SAME group (`.sheetOption`, below), which is what makes it read as a list rather than as a
234
+ * stack of boxed sections. The pre-1.28 sheet had a footer rule, a Drawer header rule and a
235
+ * `Radio.Group` per filter, so a six-filter sheet drew seven lines and none of them grouped
236
+ * anything.
237
+ */
238
+ .sheetBody {
239
+ display: flex;
240
+ flex-direction: column;
241
+ gap: var(--vx-space-stack-md, 0.75rem);
242
+ }
243
+
244
+ /* The Drawer header holds the title AND `Reset all` (see `FilterSheet`'s doc), so it is a row that
245
+ * spreads rather than Mantine's default title-plus-close pair. 44px, like every sheet row. */
246
+ .sheetHeader {
247
+ min-height: var(--vx-space-sheet-row-height, 2.75rem);
248
+ padding-block: 0;
249
+ }
250
+
251
+ .sheetTitle {
252
+ display: flex;
253
+ flex: 1;
254
+ align-items: center;
255
+ justify-content: space-between;
256
+ gap: var(--vx-space-control-gap, 0.375rem);
257
+ min-width: 0;
258
+ }
259
+
260
+ .sheetTitleText {
261
+ font-family: var(--basalt-font-head);
262
+ font-stretch: 88%;
263
+ font-weight: 550;
264
+ font-size: var(--vx-text-xl);
265
+ color: var(--vx-ink);
266
+ }
267
+
268
+ .sheetField {
269
+ display: flex;
270
+ flex-direction: column;
271
+ gap: 2px;
272
+ min-width: 0;
273
+ }
274
+
275
+ /* ── SheetOptionList / SheetDisclosure ────────────────────────────────────────────────────────── */
276
+
277
+ /* A `<fieldset>` in the option-list case (see `SheetOptionList`), a plain div in the disclosure
278
+ * case — the reset covers both, since a div ignores all of it. */
279
+ .sheetList {
280
+ display: flex;
281
+ flex-direction: column;
282
+ margin: 0;
283
+ padding: 0;
284
+ border: none;
285
+ min-inline-size: 0;
286
+ min-width: 0;
287
+ }
288
+
289
+ /*
290
+ * The 44px option row (C15). `<label>` in a list, `<button>` in a disclosure — one class, because
291
+ * the two must be indistinguishable: the disclosure IS the list's last entry.
292
+ *
293
+ * `appearance`/`margin`/`border`/`background`/`text-align` reset the `<button>` form back to the
294
+ * `<label>`'s box; a `<label>` ignores all five, so there is no need for a second selector.
295
+ */
296
+ .sheetOption {
297
+ position: relative;
298
+ display: flex;
299
+ align-items: center;
300
+ gap: var(--mantine-spacing-xs, 0.625rem);
301
+ width: 100%;
302
+ min-height: var(--vx-space-sheet-row-height, 2.75rem);
303
+ appearance: none;
304
+ margin: 0;
305
+ padding: 0;
306
+ border: none;
307
+ background: none;
308
+ text-align: start;
309
+ color: var(--vx-muted);
310
+ cursor: pointer;
311
+ }
312
+
313
+ /* Hairline BETWEEN rows only — a trailing border on the last row would be the group separator this
314
+ * sheet deliberately does not draw. */
315
+ .sheetOption + .sheetOption,
316
+ .sheetDisclosureBody + .sheetOption,
317
+ .sheetOption + .sheetDisclosureBody {
318
+ border-top: 1px solid color-mix(in srgb, var(--vx-ink) 6%, transparent);
319
+ }
320
+
321
+ .sheetOption[data-selected] {
322
+ color: var(--vx-ink);
323
+ }
324
+
325
+ .sheetOption[data-disabled] {
326
+ color: var(--vx-faint);
327
+ cursor: not-allowed;
328
+ }
329
+
330
+ @media (hover: hover) {
331
+ .sheetOption:not([data-disabled]):hover {
332
+ color: var(--vx-ink);
333
+ }
334
+ }
335
+
336
+ /*
337
+ * The real native radio/checkbox, visually hidden — the row's semantics, not decoration (see
338
+ * `SheetOptionList`'s doc). Not `display: none`: a hidden input is unfocusable, which would take the
339
+ * arrow-key group behaviour with it. Standard visually-hidden clip, and `:focus-visible` on it is
340
+ * what draws the ROW's focus ring below.
341
+ */
342
+ .sheetOptionInput {
343
+ position: absolute;
344
+ width: 1px;
345
+ height: 1px;
346
+ margin: -1px;
347
+ padding: 0;
348
+ overflow: hidden;
349
+ clip-path: inset(50%);
350
+ white-space: nowrap;
351
+ border: 0;
352
+ }
353
+
354
+ .sheetOption:has(.sheetOptionInput:focus-visible),
355
+ .sheetOption:focus-visible {
356
+ outline: 2px solid var(--vx-accent);
357
+ outline-offset: -2px;
358
+ border-radius: var(--vx-radius-ctrl);
359
+ }
360
+
361
+ .sheetOptionLabel {
362
+ flex: 1;
363
+ min-width: 0;
364
+ font-size: var(--vx-text-md);
365
+ overflow: hidden;
366
+ text-overflow: ellipsis;
367
+ white-space: nowrap;
368
+ }
369
+
370
+ .sheetOptionCheck {
371
+ display: inline-flex;
372
+ flex: none;
373
+ align-items: center;
374
+ color: var(--vx-accent);
375
+ }
376
+
377
+ .sheetDisclosureBody {
378
+ padding-bottom: var(--vx-space-stack-sm, 0.5rem);
379
+ }
380
+
381
+ /*
382
+ * The group heading — the package's MONO MICRO label (`docs/DESIGN-SPEC.md` §3), the same treatment
383
+ * `shell/app-sidebar.module.css`'s `.sectionLabel` draws for a nav section. It read as a head-font
384
+ * 13.5px line before, which is the same typography as the option rows it labels, so a group heading
385
+ * and its first option were indistinguishable — and the sheet's only remaining structure IS the
386
+ * heading, since it draws no rules between groups.
387
+ */
388
+ .sheetLabel {
389
+ font-family: var(--basalt-font-mono);
390
+ font-size: var(--vx-text-micro);
391
+ font-weight: 500;
392
+ text-transform: uppercase;
393
+ letter-spacing: 0.08em;
394
+ color: var(--vx-faint);
395
+ }
396
+
397
+ /*
398
+ * The heading a `Radio.Group`/`Checkbox.Group` renders for itself (Mantine's `Input.Label`) — same
399
+ * typography as `.sheetLabel`, which is what the other sheet fields use. Both surfaces get it: in
400
+ * the popover it is the only place the filter's NAME appears (the pill reads the value), and in the
401
+ * sheet it replaces a `SheetField` heading that could never be programmatically associated —
402
+ * `Radio.Group` overwrites any `aria-labelledby` with its own.
403
+ */
404
+ .groupLabel {
405
+ font-family: var(--basalt-font-head);
406
+ font-stretch: 88%;
407
+ font-weight: 550;
408
+ font-size: var(--vx-text-sm);
409
+ color: var(--vx-muted);
410
+ }
411
+
412
+ /* The sheet's `SheetField` heading sits 2px off its list — the mono micro step above is small
413
+ * enough that the default flow gap read as part of the first row. */
414
+ .sheetField > .sheetLabel {
415
+ margin-bottom: 2px;
416
+ }
417
+
418
+ /* 44px rows — the one sheet dimension C15 pins. */
419
+ .sheetRow {
420
+ display: flex;
421
+ align-items: center;
422
+ gap: var(--mantine-spacing-xs, 0.625rem);
423
+ min-height: var(--vx-space-sheet-row-height, 2.75rem);
424
+ width: 100%;
425
+ border-bottom: 1px solid var(--vx-surface-hairline);
426
+ }
427
+
428
+ .sheetRow:last-child {
429
+ border-bottom: none;
430
+ }
431
+
432
+ /*
433
+ * A 44px row is only a 44px TOUCH TARGET if the `<label>` fills it. Mantine centres a Radio/Checkbox
434
+ * `body` in its root and the label line-height is ~20px, so without these the tappable strip is the
435
+ * middle 20px of the row and the outer 12px top and bottom do nothing (C15's number, not delivered).
436
+ * Passed through `classNames`, never as a global `.sheetRow label` selector — a descendant selector
437
+ * here would also capture a nested control's own labels.
438
+ */
439
+ .sheetRowBody {
440
+ flex: 1;
441
+ align-items: center;
442
+ min-height: inherit;
443
+ }
444
+
445
+ .sheetRowLabel {
446
+ display: flex;
447
+ flex: 1;
448
+ align-items: center;
449
+ min-height: inherit;
450
+ }
451
+
452
+ /* ── ControlGroup ─────────────────────────────────────────────────────────────────────────────── */
453
+
454
+ /*
455
+ * Adjacent controls joined into one box (`docs/CONTROLS-SPEC.md` §3, `control-group.tsx`).
456
+ *
457
+ * `flex: none` and `nowrap`: a joined set that wrapped would break in the middle of the box it is
458
+ * pretending to be, which is worse than overflowing. It is the same reasoning `.pillSlot` uses —
459
+ * a group is one unit for the purposes of the row's fold arithmetic.
460
+ */
461
+ .group {
462
+ display: inline-flex;
463
+ align-items: center;
464
+ flex: none;
465
+ flex-wrap: nowrap;
466
+ }
467
+
468
+ .group[data-gap='tight'] {
469
+ gap: var(--vx-space-control-gap, 0.375rem);
470
+ }
471
+
472
+ .group[data-gap='none'] {
473
+ gap: 0;
474
+ }
475
+
476
+ /*
477
+ * The hairline BETWEEN two joined children is the one divider in the control tier, and it has to be
478
+ * drawn here because `cssVariablesResolver` points `--mantine-color-default-border` at `transparent`
479
+ * on purpose (depth is a shadow, `docs/DESIGN-SPEC.md` §8). Same 12%-ink value `.pill` uses, so a
480
+ * joined pill row and a joined button row read as the same family.
481
+ *
482
+ * Scoped to `[data-variant='default']` so a `filled` child keeps its fill edge: a border drawn on a
483
+ * filled button paints a rim in a colour that is neither the fill nor the surface.
484
+ */
485
+ .group[data-gap='none'] > [data-variant='default'] {
486
+ border-color: color-mix(in srgb, var(--vx-ink) 12%, transparent);
487
+ }
488
+
489
+ /*
490
+ * `-1px` collapses the two adjacent 1px borders into ONE. Without it a joined pair carries a 2px
491
+ * rule down the middle, which reads as a gap that someone forgot to remove rather than as a divider.
492
+ */
493
+ .group[data-gap='none'] > * + * {
494
+ margin-inline-start: -1px;
495
+ }
496
+
497
+ /*
498
+ * The radius lives on the OUTER ends only — that single rule is what turns three boxes into one box.
499
+ * `:only-child` is excluded on both ends so a one-child group (a conditional render that dropped the
500
+ * other two) keeps its normal shape instead of losing half its corners.
501
+ */
502
+ .group[data-gap='none'] > *:not(:first-child):not(:last-child) {
503
+ border-radius: 0;
504
+ }
505
+
506
+ .group[data-gap='none'] > *:first-child:not(:only-child) {
507
+ border-start-end-radius: 0;
508
+ border-end-end-radius: 0;
509
+ }
510
+
511
+ .group[data-gap='none'] > *:last-child:not(:only-child) {
512
+ border-start-start-radius: 0;
513
+ border-end-start-radius: 0;
514
+ }
515
+
516
+ /*
517
+ * A hovered or focused child has to paint its FULL border, including the edge it is sharing — the
518
+ * negative margin means the next sibling is drawn over it otherwise, so a focus ring lost its right
519
+ * side and read as a rendering glitch. `position: relative` is what makes `z-index` apply.
520
+ */
521
+ .group[data-gap='none'] > *:hover,
522
+ .group[data-gap='none'] > *:focus-visible,
523
+ .group[data-gap='none'] > *[data-active] {
524
+ position: relative;
525
+ z-index: 1;
526
+ }
527
+
528
+ /*
529
+ * `ViewTabs`' phone form past `PHONE_TRACK_MAX` options is a `Select`, and a Mantine input resolves
530
+ * its own width from its container — which in a `WidgetHeader`'s `actions` slot (a `flex: 0 0 auto`
531
+ * inline-flex box) came out at 216px. Measured on the playground's `Controls (mobile)` page at 390px:
532
+ * the section header's title row was 364px wide, the Select took 252px of it with the count tag, and
533
+ * `Customers` rendered as `Cust…`. A tab control that truncates the heading it sits beside has the
534
+ * priority backwards — the heading names the section, the tabs are one of its controls.
535
+ *
536
+ * The bound is the widest single option label plus the chevron, not a fraction of the row: it must be
537
+ * the same box regardless of what is to its left.
538
+ */
539
+ .phoneSelect {
540
+ max-width: 9rem;
541
+ flex: 0 1 auto;
542
+ }
@@ -0,0 +1,35 @@
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 group: string
7
+ readonly pillRow: string
8
+ readonly pillSlot: string
9
+ readonly foldPill: string
10
+ readonly sheetPill: string
11
+ readonly pill: string
12
+ readonly pillIcon: string
13
+ readonly pillLabel: string
14
+ readonly glyph: string
15
+ readonly dropdown: string
16
+ readonly optionList: string
17
+ readonly sheetBody: string
18
+ readonly sheetHeader: string
19
+ readonly sheetTitle: string
20
+ readonly sheetTitleText: string
21
+ readonly sheetField: string
22
+ readonly sheetList: string
23
+ readonly sheetOption: string
24
+ readonly sheetDisclosureBody: string
25
+ readonly sheetOptionInput: string
26
+ readonly sheetOptionLabel: string
27
+ readonly sheetOptionCheck: string
28
+ readonly sheetLabel: string
29
+ readonly groupLabel: string
30
+ readonly sheetRow: string
31
+ readonly sheetRowBody: string
32
+ readonly sheetRowLabel: string
33
+ readonly phoneSelect: string
34
+ }
35
+ export default classes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=controls.router.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controls.router.test.d.ts","sourceRoot":"","sources":["../../src/controls/controls.router.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { FieldOption } from '../state.js';
3
+ import type { FilterOption } from './select-filter.js';
4
+ /**
5
+ * The slice of a `FieldHandle` this body actually binds to, generic over the VALUE rather than over
6
+ * the field kind. `FieldHandle<EnumField<T>>` satisfies it at `T` and `FieldHandle<StringField>` at
7
+ * `string`, which is what lets `SelectFilter` take either without this file knowing about kinds —
8
+ * and it stays a structural subset, so it can never widen into a second, hand-constructible handle.
9
+ */
10
+ export type ChoiceHandle<T extends string> = {
11
+ readonly fallback: T;
12
+ readonly options: readonly FieldOption[];
13
+ use(): readonly [T, (next: T) => void];
14
+ isDefault(v: T): boolean;
15
+ /** Removes the value on whichever lane the field is on — what both reset paths call, never a write
16
+ * of the fallback back over it. See `useFilterRegistration`'s doc for why. */
17
+ clear(): void;
18
+ };
19
+ export type EnumFilterProps<T extends string> = {
20
+ readonly field: ChoiceHandle<T>;
21
+ readonly label: string;
22
+ readonly icon?: ReactNode;
23
+ /** Adds a `Clear` action that clears the field — the same `field.clear()` `Reset all` calls. */
24
+ readonly clearable?: boolean;
25
+ /**
26
+ * Overrides `field.options` at render — a runtime catalogue whose labels carry live data, or the
27
+ * only row source a `StringField` has. `| undefined` explicitly: an internal component is spread
28
+ * into, and `exactOptionalPropertyTypes` rejects a possibly-undefined member on a bare optional.
29
+ */
30
+ readonly options?: readonly FilterOption[] | undefined;
31
+ /**
32
+ * Mono pill label — forwarded to `FilterPill.numeric`. Set by `NumberFilter`, whose values ARE
33
+ * numbers (`1` / `2` / `7`), and by nothing else: the same law `[data-numeric]` applies to a
34
+ * numeric SegmentedControl label. A word-valued filter never sets it.
35
+ */
36
+ readonly numeric?: boolean | undefined;
37
+ };
38
+ export declare function EnumFilter<T extends string>({ field, label, icon, clearable, numeric, options: optionsProp, }: EnumFilterProps<T>): ReactNode;
39
+ //# sourceMappingURL=enum-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enum-filter.d.ts","sourceRoot":"","sources":["../../src/controls/enum-filter.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAK3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI;IAC3C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAA;IACpB,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;IACxC,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,CAAA;IACtC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAA;IACxB;kFAC8E;IAC9E,KAAK,IAAI,IAAI,CAAA;CACd,CAAA;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,IAAI;IAC9C,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,gGAAgG;IAChG,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,CAAA;IACtD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACvC,CAAA;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,EAC3C,KAAK,EACL,KAAK,EACL,IAAI,EACJ,SAAS,EACT,OAAO,EACP,OAAO,EAAE,WAAW,GACrB,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAyFhC"}