basalt-ui 1.25.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (462) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +18 -0
  3. package/MIGRATING.md +546 -20
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +155 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +113 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1275 -31
  17. package/configs/oxlint-plugin.test.ts +947 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +30 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +91 -0
  75. package/dist/controls/filter-context.d.ts +55 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +19 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +44 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/peer-boundary.test.d.ts +2 -0
  99. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  100. package/dist/controls/props-contract.test.d.ts +2 -0
  101. package/dist/controls/props-contract.test.d.ts.map +1 -0
  102. package/dist/controls/range-filter.d.ts +29 -0
  103. package/dist/controls/range-filter.d.ts.map +1 -0
  104. package/dist/controls/range-filter.js +120 -0
  105. package/dist/controls/search-filter.d.ts +10 -0
  106. package/dist/controls/search-filter.d.ts.map +1 -0
  107. package/dist/controls/search-filter.js +81 -0
  108. package/dist/controls/select-filter.d.ts +74 -0
  109. package/dist/controls/select-filter.d.ts.map +1 -0
  110. package/dist/controls/select-filter.js +10 -0
  111. package/dist/controls/sync-button.d.ts +38 -0
  112. package/dist/controls/sync-button.d.ts.map +1 -0
  113. package/dist/controls/sync-button.js +119 -0
  114. package/dist/controls/sync-button.module.css +63 -0
  115. package/dist/controls/sync-button.module.css.d.ts +11 -0
  116. package/dist/controls/sync-button.test.d.ts +2 -0
  117. package/dist/controls/sync-button.test.d.ts.map +1 -0
  118. package/dist/controls/toggle-filter.d.ts +9 -0
  119. package/dist/controls/toggle-filter.d.ts.map +1 -0
  120. package/dist/controls/toggle-filter.js +42 -0
  121. package/dist/controls/view-tabs.d.ts +23 -0
  122. package/dist/controls/view-tabs.d.ts.map +1 -0
  123. package/dist/controls/view-tabs.js +59 -0
  124. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  125. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  126. package/dist/controls-dates/date-range-picker.js +18 -0
  127. package/dist/controls-dates/index.d.ts +9 -0
  128. package/dist/controls-dates/index.d.ts.map +1 -0
  129. package/dist/controls-dates/index.js +4 -0
  130. package/dist/dashboard/delta-badge.d.ts +6 -12
  131. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  132. package/dist/dashboard/delta-badge.js +1 -50
  133. package/dist/dashboard/settings-section.d.ts +6 -4
  134. package/dist/dashboard/settings-section.d.ts.map +1 -1
  135. package/dist/dashboard/settings-section.js +23 -16
  136. package/dist/dashboard/settings-section.module.css +10 -0
  137. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  138. package/dist/dashboard/settings-section.test.d.ts +2 -0
  139. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  140. package/dist/dashboard/stat-card.d.ts +44 -9
  141. package/dist/dashboard/stat-card.d.ts.map +1 -1
  142. package/dist/dashboard/stat-card.js +80 -48
  143. package/dist/dashboard/stat-card.module.css +82 -0
  144. package/dist/dashboard/stat-card.module.css.d.ts +11 -0
  145. package/dist/data/data-table.d.ts +37 -15
  146. package/dist/data/data-table.d.ts.map +1 -1
  147. package/dist/data/data-table.js +88 -58
  148. package/dist/guard/grace.test.d.ts +20 -0
  149. package/dist/guard/grace.test.d.ts.map +1 -0
  150. package/dist/guard/index.d.ts +66 -3
  151. package/dist/guard/index.d.ts.map +1 -1
  152. package/dist/guard/index.js +82 -7
  153. package/dist/guard/types.d.ts +3 -3
  154. package/dist/guard/types.d.ts.map +1 -1
  155. package/dist/index.d.ts +12 -2
  156. package/dist/index.d.ts.map +1 -1
  157. package/dist/index.js +13 -10
  158. package/dist/nav/types.d.ts +78 -2
  159. package/dist/nav/types.d.ts.map +1 -1
  160. package/dist/notifications/bell.d.ts +4 -1
  161. package/dist/notifications/bell.d.ts.map +1 -1
  162. package/dist/notifications/index.d.ts +1 -1
  163. package/dist/router-tanstack/field.d.ts +14 -0
  164. package/dist/router-tanstack/field.d.ts.map +1 -0
  165. package/dist/router-tanstack/field.js +4 -0
  166. package/dist/router-tanstack/index.d.ts +6 -2
  167. package/dist/router-tanstack/index.d.ts.map +1 -1
  168. package/dist/router-tanstack/index.js +4 -0
  169. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  170. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  171. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  172. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  173. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  174. package/dist/router-tanstack/search-param-store.js +16 -45
  175. package/dist/router-tanstack/search-store.d.ts +81 -0
  176. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  177. package/dist/router-tanstack/search-store.js +187 -0
  178. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  179. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  180. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  181. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  182. package/dist/router-tanstack/use-nav.d.ts +0 -2
  183. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  184. package/dist/router-tanstack/use-nav.js +34 -12
  185. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  186. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  187. package/dist/section/index.d.ts +2 -0
  188. package/dist/section/index.d.ts.map +1 -0
  189. package/dist/section/index.js +4 -0
  190. package/dist/section/section.d.ts +24 -0
  191. package/dist/section/section.d.ts.map +1 -0
  192. package/dist/section/section.js +126 -0
  193. package/dist/section/section.module.css +104 -0
  194. package/dist/section/section.module.css.d.ts +11 -0
  195. package/dist/section/section.test.d.ts +2 -0
  196. package/dist/section/section.test.d.ts.map +1 -0
  197. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  198. package/dist/shell/app-breadcrumbs.js +18 -15
  199. package/dist/shell/app-header.module.css +77 -37
  200. package/dist/shell/app-header.module.css.d.ts +2 -1
  201. package/dist/shell/app-mobile-nav.d.ts +9 -2
  202. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  203. package/dist/shell/app-mobile-nav.js +98 -11
  204. package/dist/shell/app-mobile-nav.module.css +8 -0
  205. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  206. package/dist/shell/app-sidebar.d.ts +55 -28
  207. package/dist/shell/app-sidebar.d.ts.map +1 -1
  208. package/dist/shell/app-sidebar.js +116 -67
  209. package/dist/shell/app-sidebar.module.css +303 -20
  210. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  211. package/dist/shell/index.d.ts +55 -21
  212. package/dist/shell/index.d.ts.map +1 -1
  213. package/dist/shell/index.js +40 -18
  214. package/dist/shell/mobile-nav-model.d.ts +13 -2
  215. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  216. package/dist/shell/mobile-nav-model.js +15 -1
  217. package/dist/shell/nav-count-badge.d.ts +1 -1
  218. package/dist/shell/page-bar.d.ts +51 -0
  219. package/dist/shell/page-bar.d.ts.map +1 -0
  220. package/dist/shell/page-bar.js +154 -0
  221. package/dist/shell/page-bar.module.css +178 -0
  222. package/dist/shell/page-bar.module.css.d.ts +17 -0
  223. package/dist/shell/page-bar.test.d.ts +2 -0
  224. package/dist/shell/page-bar.test.d.ts.map +1 -0
  225. package/dist/shell/sidebar-block-model.d.ts +48 -0
  226. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  227. package/dist/shell/sidebar-block-model.js +39 -0
  228. package/dist/shell/sidebar-blocks.d.ts +32 -0
  229. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  230. package/dist/shell/sidebar-blocks.js +169 -0
  231. package/dist/shell/sidebar-search.d.ts +13 -1
  232. package/dist/shell/sidebar-search.d.ts.map +1 -1
  233. package/dist/shell/sidebar-search.js +34 -1
  234. package/dist/shell/sidebar-search.module.css +26 -0
  235. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  236. package/dist/state/fields.d.ts +392 -0
  237. package/dist/state/fields.d.ts.map +1 -0
  238. package/dist/state/fields.js +369 -0
  239. package/dist/state/fields.test.d.ts +2 -0
  240. package/dist/state/fields.test.d.ts.map +1 -0
  241. package/dist/state/persisted.d.ts +48 -0
  242. package/dist/state/persisted.d.ts.map +1 -0
  243. package/dist/state/persisted.js +105 -0
  244. package/dist/state.d.ts +11 -56
  245. package/dist/state.d.ts.map +1 -1
  246. package/dist/state.js +14 -120
  247. package/dist/styles.css +13 -0
  248. package/dist/surfaces.d.ts +106 -24
  249. package/dist/surfaces.d.ts.map +1 -1
  250. package/dist/surfaces.js +155 -19
  251. package/dist/theme/controls.module.css +16 -1
  252. package/dist/theme/ctl-theme.d.ts +32 -0
  253. package/dist/theme/ctl-theme.d.ts.map +1 -0
  254. package/dist/theme/ctl-theme.js +54 -0
  255. package/dist/theme/ctl-theme.test.d.ts +2 -0
  256. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  257. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  258. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  259. package/dist/theme/divider-law.test.d.ts +2 -0
  260. package/dist/theme/divider-law.test.d.ts.map +1 -0
  261. package/dist/theme/icon-slot.d.ts +44 -0
  262. package/dist/theme/icon-slot.d.ts.map +1 -0
  263. package/dist/theme/icon-slot.js +21 -0
  264. package/dist/theme/icon-slot.module.css +47 -0
  265. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  266. package/dist/theme/icon-slot.test.d.ts +2 -0
  267. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  268. package/dist/theme/index.d.ts +1 -0
  269. package/dist/theme/index.d.ts.map +1 -1
  270. package/dist/theme/index.js +82 -5
  271. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  272. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  273. package/dist/theme/segmented-control.module.css +20 -0
  274. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  275. package/dist/tokens/index.d.ts +12 -0
  276. package/dist/tokens/index.d.ts.map +1 -1
  277. package/dist/tokens/index.js +38 -8
  278. package/dist/tokens/palette.d.ts +97 -61
  279. package/dist/tokens/palette.d.ts.map +1 -1
  280. package/dist/tokens/palette.js +76 -38
  281. package/dist/tokens.css +10 -2
  282. package/dist/widget-header/delta-badge.d.ts +13 -0
  283. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  284. package/dist/widget-header/delta-badge.js +29 -0
  285. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  286. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  287. package/dist/widget-header/index.d.ts +3 -0
  288. package/dist/widget-header/index.d.ts.map +1 -0
  289. package/dist/widget-header/index.js +6 -0
  290. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  291. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  292. package/dist/widget-header/widget-header.d.ts +37 -0
  293. package/dist/widget-header/widget-header.d.ts.map +1 -0
  294. package/dist/widget-header/widget-header.js +77 -0
  295. package/dist/widget-header/widget-header.module.css +335 -0
  296. package/dist/widget-header/widget-header.module.css.d.ts +24 -0
  297. package/dist/widget-header/widget-header.test.d.ts +2 -0
  298. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  299. package/llms.txt +30 -17
  300. package/package.json +14 -1
  301. package/src/charts/cursor/resolve.test.ts +29 -1
  302. package/src/charts/cursor/resolve.ts +38 -6
  303. package/src/charts/cursor/scope.tsx +6 -3
  304. package/src/charts/cursor/store.test.ts +19 -1
  305. package/src/charts/cursor/store.ts +10 -5
  306. package/src/charts/hooks/useChartCursor.ts +16 -8
  307. package/src/charts/index.ts +2 -2
  308. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  309. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  310. package/src/charts/primitives/ChartCard.tsx +86 -146
  311. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  312. package/src/cli/check-coverage.test.ts +297 -0
  313. package/src/cli/check-theme.test.ts +38 -14
  314. package/src/cli/index.ts +392 -16
  315. package/src/cli/init-sync.test.ts +97 -0
  316. package/src/connectivity/connectivity-indicator.tsx +16 -9
  317. package/src/content/index.ts +16 -3
  318. package/src/content/prose.module.css +7 -28
  319. package/src/content/prose.module.css.test.ts +8 -24
  320. package/src/controls/actions.module.css +62 -0
  321. package/src/controls/actions.module.css.d.ts +13 -0
  322. package/src/controls/actions.test.tsx +252 -0
  323. package/src/controls/actions.tsx +655 -0
  324. package/src/controls/actions.type-guard.test.ts +63 -0
  325. package/src/controls/compare-filter.tsx +64 -0
  326. package/src/controls/control-group.test.tsx +229 -0
  327. package/src/controls/control-group.tsx +63 -0
  328. package/src/controls/controls.module.css +542 -0
  329. package/src/controls/controls.module.css.d.ts +35 -0
  330. package/src/controls/controls.router.test.tsx +637 -0
  331. package/src/controls/enum-filter.tsx +159 -0
  332. package/src/controls/filter-context.tsx +153 -0
  333. package/src/controls/filter-pill.tsx +120 -0
  334. package/src/controls/filter-set.test.tsx +472 -0
  335. package/src/controls/filter-set.tsx +225 -0
  336. package/src/controls/filter-sheet.tsx +285 -0
  337. package/src/controls/glyphs.tsx +81 -0
  338. package/src/controls/index.ts +48 -0
  339. package/src/controls/multi-select-filter.tsx +140 -0
  340. package/src/controls/peer-boundary.test.ts +85 -0
  341. package/src/controls/props-contract.test.tsx +106 -0
  342. package/src/controls/range-filter.tsx +184 -0
  343. package/src/controls/search-filter.tsx +123 -0
  344. package/src/controls/select-filter.tsx +89 -0
  345. package/src/controls/sync-button.module.css +63 -0
  346. package/src/controls/sync-button.module.css.d.ts +11 -0
  347. package/src/controls/sync-button.test.tsx +183 -0
  348. package/src/controls/sync-button.tsx +197 -0
  349. package/src/controls/toggle-filter.tsx +60 -0
  350. package/src/controls/view-tabs.tsx +110 -0
  351. package/src/controls-dates/date-range-picker.tsx +42 -0
  352. package/src/controls-dates/index.ts +8 -0
  353. package/src/dashboard/delta-badge.tsx +4 -74
  354. package/src/dashboard/settings-section.module.css +10 -0
  355. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  356. package/src/dashboard/settings-section.test.tsx +56 -0
  357. package/src/dashboard/settings-section.tsx +33 -30
  358. package/src/dashboard/stat-card.module.css +82 -0
  359. package/src/dashboard/stat-card.module.css.d.ts +11 -0
  360. package/src/dashboard/stat-card.test.tsx +67 -1
  361. package/src/dashboard/stat-card.tsx +209 -68
  362. package/src/data/data-table.test.tsx +96 -11
  363. package/src/data/data-table.tsx +220 -84
  364. package/src/guard/check-source.test.ts +126 -13
  365. package/src/guard/grace.test.ts +149 -0
  366. package/src/guard/index.ts +172 -29
  367. package/src/guard/types.ts +4 -2
  368. package/src/index.ts +46 -5
  369. package/src/nav/types.ts +84 -2
  370. package/src/notifications/bell.tsx +6 -3
  371. package/src/notifications/index.ts +1 -1
  372. package/src/router-tanstack/field.ts +31 -0
  373. package/src/router-tanstack/index.ts +24 -2
  374. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  375. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  376. package/src/router-tanstack/search-param-store.ts +42 -159
  377. package/src/router-tanstack/search-store.router.test.tsx +661 -0
  378. package/src/router-tanstack/search-store.test.ts +418 -0
  379. package/src/router-tanstack/search-store.ts +380 -0
  380. package/src/router-tanstack/use-nav.test.tsx +187 -0
  381. package/src/router-tanstack/use-nav.ts +72 -14
  382. package/src/section/index.ts +1 -0
  383. package/src/section/section.module.css +104 -0
  384. package/src/section/section.module.css.d.ts +11 -0
  385. package/src/section/section.test.tsx +204 -0
  386. package/src/section/section.tsx +220 -0
  387. package/src/shell/app-breadcrumbs.tsx +44 -31
  388. package/src/shell/app-header.module.css +77 -37
  389. package/src/shell/app-header.module.css.d.ts +2 -1
  390. package/src/shell/app-mobile-nav.module.css +8 -0
  391. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  392. package/src/shell/app-mobile-nav.tsx +147 -8
  393. package/src/shell/app-sidebar-account.tsx +2 -2
  394. package/src/shell/app-sidebar.module.css +303 -20
  395. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  396. package/src/shell/app-sidebar.test.tsx +456 -41
  397. package/src/shell/app-sidebar.tsx +276 -140
  398. package/src/shell/index.test.tsx +291 -5
  399. package/src/shell/index.tsx +137 -38
  400. package/src/shell/mobile-nav-model.test.ts +228 -7
  401. package/src/shell/mobile-nav-model.ts +53 -3
  402. package/src/shell/nav-count-badge.tsx +1 -1
  403. package/src/shell/page-bar.module.css +178 -0
  404. package/src/shell/page-bar.module.css.d.ts +17 -0
  405. package/src/shell/page-bar.test.tsx +316 -0
  406. package/src/shell/page-bar.tsx +315 -0
  407. package/src/shell/sidebar-block-model.ts +83 -0
  408. package/src/shell/sidebar-blocks.tsx +301 -0
  409. package/src/shell/sidebar-search.module.css +26 -0
  410. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  411. package/src/shell/sidebar-search.tsx +71 -2
  412. package/src/state/fields.test.tsx +258 -0
  413. package/src/state/fields.ts +892 -0
  414. package/src/state/persisted.ts +211 -0
  415. package/src/state.ts +46 -240
  416. package/src/styles.css +13 -0
  417. package/src/surfaces.ts +203 -36
  418. package/src/theme/controls.module.css +16 -1
  419. package/src/theme/ctl-theme.test.tsx +275 -0
  420. package/src/theme/ctl-theme.tsx +138 -0
  421. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  422. package/src/theme/density-relations.test.ts +106 -51
  423. package/src/theme/divider-law.test.ts +259 -0
  424. package/src/theme/icon-slot.module.css +47 -0
  425. package/src/theme/icon-slot.module.css.d.ts +8 -0
  426. package/src/theme/icon-slot.test.tsx +275 -0
  427. package/src/theme/icon-slot.tsx +59 -0
  428. package/src/theme/index.ts +165 -3
  429. package/src/theme/layout-rhythm-css.test.ts +72 -0
  430. package/src/theme/radius.test.ts +11 -1
  431. package/src/theme/segmented-control.module.css +20 -0
  432. package/src/theme/shadow-surfaces.test.ts +20 -7
  433. package/src/theme/spacing.test.ts +75 -27
  434. package/src/theme/use-basalt-spacing.ts +2 -2
  435. package/src/tokens/density.test.ts +7 -25
  436. package/src/tokens/index.ts +48 -8
  437. package/src/tokens/palette.ts +107 -73
  438. package/src/widget-header/delta-badge.test.tsx +60 -0
  439. package/src/widget-header/delta-badge.tsx +57 -0
  440. package/src/widget-header/index.ts +2 -0
  441. package/src/widget-header/no-mantine.test.ts +33 -0
  442. package/src/widget-header/widget-header.module.css +335 -0
  443. package/src/widget-header/widget-header.module.css.d.ts +24 -0
  444. package/src/widget-header/widget-header.test.tsx +187 -0
  445. package/src/widget-header/widget-header.tsx +168 -0
  446. package/agent/rules/basalt-agent.md +0 -452
  447. package/agent/rules/basalt-app.md +0 -246
  448. package/agent/rules/basalt-commands.md +0 -320
  449. package/agent/rules/basalt-content.md +0 -409
  450. package/agent/rules/basalt-data.md +0 -329
  451. package/agent/rules/basalt-forms.md +0 -186
  452. package/agent/rules/basalt-notifications.md +0 -264
  453. package/agent/rules/basalt-query.md +0 -187
  454. package/agent/rules/basalt-router.md +0 -336
  455. package/dist/content/article-filter-bar.d.ts +0 -40
  456. package/dist/content/article-filter-bar.d.ts.map +0 -1
  457. package/dist/content/article-filter-bar.js +0 -56
  458. package/dist/shell/page-header.d.ts +0 -13
  459. package/dist/shell/page-header.d.ts.map +0 -1
  460. package/dist/shell/page-header.js +0 -23
  461. package/src/content/article-filter-bar.tsx +0 -88
  462. package/src/shell/page-header.tsx +0 -35
@@ -0,0 +1,169 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { NavAnchor } from '../nav/types.js';
3
+ import type { AnyNavLink } from '../router-tanstack/nav.js';
4
+ /**
5
+ * A button-shaped action. `Anchor` is the router seam basalt HOSTS (the same seam `SidebarItem`
6
+ * uses — pass the one `useNav` builds, or your router's `Link`); `link` is the no-router fallback
7
+ * and only its `to` is honoured, since resolving `search`/`params` needs a router. Supply `Anchor`
8
+ * whenever the destination carries either.
9
+ */
10
+ export type BarActionItem = {
11
+ key: string;
12
+ label: string;
13
+ kind?: undefined;
14
+ icon?: ReactNode;
15
+ onClick?: () => void;
16
+ link?: AnyNavLink;
17
+ Anchor?: NavAnchor;
18
+ disabled?: boolean;
19
+ loading?: boolean;
20
+ danger?: boolean;
21
+ /**
22
+ * Joins this action to its adjacent `group: true` siblings into ONE `ControlGroup` — shared
23
+ * borders, radius on the outer ends only, no gap. Declare it on every member of the run: a set of
24
+ * three where the middle one omits it is two runs of one, which is the same row it was before.
25
+ *
26
+ * It is an opt-in because "these act on the same thing" is a fact only the caller has. A `‹ Today ›`
27
+ * stepper is the shape; three independent actions that happen to sit beside each other are not, and
28
+ * joining those would claim a relationship the reader would then have to un-learn.
29
+ */
30
+ group?: true;
31
+ /** Mobile placement. @default 'bar' for `primary`, `'more'` for everything else */
32
+ mobile?: 'bar' | 'more' | 'hidden';
33
+ };
34
+ /** A nested group. Always folds into the `More` menu on both viewports — it never takes bar width. */
35
+ export type BarActionMenu = {
36
+ key: string;
37
+ kind: 'menu';
38
+ label: string;
39
+ icon?: ReactNode;
40
+ items: BarAction[];
41
+ };
42
+ /** An escape hatch for a control basalt does not model (a live chip, a consumer's own widget). */
43
+ export type BarActionCustom = {
44
+ key: string;
45
+ kind: 'custom';
46
+ node: ReactNode;
47
+ mobile?: 'bar' | 'more' | 'hidden';
48
+ };
49
+ export type BarAction = BarActionItem | BarActionMenu | BarActionCustom;
50
+ /**
51
+ * A home's `actions` slot. `primary` is SINGULAR by type — that is the whole enforcement of law
52
+ * C6's "exactly one primary"; there is no runtime check because a second one cannot be written.
53
+ */
54
+ export type ActionGroupProps = {
55
+ primary?: BarAction;
56
+ secondary?: BarAction[];
57
+ };
58
+ /**
59
+ * A shell-owned, persistent header action (timer, notifications, a global `SyncButton`).
60
+ * `node` is rendered as-is — basalt owns only the placement.
61
+ *
62
+ * Below `sm` a `'more'` action renders inside the header's single kebab, which means its `node` is
63
+ * mounted a second time there (lazily — a Mantine `Menu.Dropdown` mounts its children on open).
64
+ * Keep `'more'` for self-contained nodes and give anything holding live state `mobile: 'bar'`,
65
+ * which renders exactly once.
66
+ */
67
+ export type GlobalAction = {
68
+ key: string;
69
+ node: ReactNode;
70
+ /** @default 'bar' for the first two, `'more'` for the rest */
71
+ mobile?: 'bar' | 'more' | 'hidden';
72
+ };
73
+ /** Secondaries rendered inline on desktop; the rest fold into `More` (`docs/CONTROLS-SPEC.md` §2.1). */
74
+ export declare const DESKTOP_SECONDARY_MAX = 3;
75
+ /** `GlobalAction`s without an explicit `mobile` that still land on the mobile bar. */
76
+ export declare const MOBILE_GLOBAL_BAR_MAX = 2;
77
+ /**
78
+ * What the shell hands the page's `ActionGroup` so the header ends up with ONE kebab instead of
79
+ * two: the shell's `mobile: 'more'` global actions (already mapped to `kind: 'custom'` rows), and
80
+ * a claim the mobile kebab takes on mount. While a page's kebab holds the claim, the shell renders
81
+ * no kebab of its own.
82
+ *
83
+ * Lives here, in the lowest layer, so `src/shell/**` imports downward into `src/controls/**` and
84
+ * never the reverse — a context declared in the shell would make `./controls` pull the shell in.
85
+ */
86
+ export type BarExtras = {
87
+ mobileMoreActions: readonly BarAction[];
88
+ /** Claims the single mobile kebab. Returns the release, so it works straight out of an effect. */
89
+ claimKebab: () => () => void;
90
+ };
91
+ /** Internal — mounted by `BasaltShell` (via `PageBarProvider`), never by a consumer. */
92
+ export declare function BarExtrasProvider({ value, children, }: {
93
+ value: BarExtras;
94
+ children: ReactNode;
95
+ }): ReactNode;
96
+ /** Resolved mobile placement of one action. A `kind: 'menu'` group is always a `More` row. */
97
+ export declare function barActionMobile(action: BarAction, isPrimary: boolean): 'bar' | 'more' | 'hidden';
98
+ /** `GlobalAction[]` → the same placement law, with "the first two ride the bar" as the default. */
99
+ export declare function globalActionMobile(action: GlobalAction, index: number): 'bar' | 'more' | 'hidden';
100
+ /** A `GlobalAction` reaching a `BarAction` slot — the kebab renders one row shape, not two. */
101
+ export declare function globalActionAsBarAction(action: GlobalAction): BarActionCustom;
102
+ export type OverflowMenuProps = {
103
+ actions: readonly BarAction[];
104
+ /**
105
+ * `more` — the labelled desktop `More` button. `kebab` — the mobile ⋯ icon button.
106
+ * @default 'more'
107
+ */
108
+ trigger?: 'more' | 'kebab';
109
+ /** Trigger label (and the kebab's accessible name). @default 'More' */
110
+ label?: string;
111
+ };
112
+ /**
113
+ * The overflow fold — the only place an action that doesn't fit goes (law C7). Rows are 44px below
114
+ * `sm` (`--vx-space-sheet-row-height`), so the mobile kebab is already a touch surface without a
115
+ * second, sheet-shaped component.
116
+ */
117
+ export declare function OverflowMenu({ actions, trigger, label, }: OverflowMenuProps): ReactNode;
118
+ /**
119
+ * Internal — the shared projection behind `ActionGroup` (and `PageBar`'s two rows).
120
+ *
121
+ * `host` is the whole reason this is not just `ActionGroup`. The shell's `mobile: 'more'` global
122
+ * actions must land in EXACTLY ONE kebab, and the only instance entitled to them is `PageBar`'s
123
+ * row 1. When every `ActionGroup` read `BarExtrasContext`, a page with `filtersEnd` (or any
124
+ * consumer composing an `ActionGroup` into a `Section`/`ChartCard` `actions` slot) grew a SECOND
125
+ * kebab that re-mounted the shell's global nodes and stole the claim from the shell's own fallback.
126
+ * `host: 'slot'` — the public `ActionGroup`'s value — gets `NO_EXTRAS`: no global rows, no claim.
127
+ */
128
+ type BarActionRowProps = ActionGroupProps & {
129
+ /** `page` = `PageBar` row 1, the one instance that reads the shell extras and takes the claim. */
130
+ host: 'page' | 'slot';
131
+ /**
132
+ * `desktop` renders the desktop variant only — `PageBar` row 2, whose items live in the row-1
133
+ * kebab below `sm` (spec §2.1: mobile row 2 is tabs + the first pill + `Filters (n)`, nothing
134
+ * else). @default 'both'
135
+ */
136
+ viewport?: 'both' | 'desktop';
137
+ /**
138
+ * Actions projected onto the MOBILE variant only, never the desktop row — `PageBar.filtersEnd`,
139
+ * whose desktop home is row 2. Each still obeys its own `mobile` placement, so a `'bar'` one
140
+ * becomes an inline icon button and a `'hidden'` one stays desktop-only.
141
+ */
142
+ mobileOnly?: readonly BarAction[];
143
+ /**
144
+ * The home's `SyncButton`, mounted ONCE between the secondaries and the primary — see the ordering
145
+ * note in the body. Only `PageBar` row 1 passes it; a home with no sync passes nothing and the
146
+ * desktop row stays a single group.
147
+ */
148
+ syncNode?: ReactNode;
149
+ };
150
+ export declare function BarActionRow({ primary, secondary, host, viewport, mobileOnly, syncNode, }: BarActionRowProps): ReactNode;
151
+ /**
152
+ * A home's `actions` slot, projected for both viewports.
153
+ *
154
+ * Desktop: `primary` as a `filled` button · up to `DESKTOP_SECONDARY_MAX` secondaries as `default`
155
+ * buttons · everything past that, plus every `kind: 'menu'`, in one `More` menu.
156
+ * Mobile: `primary` as an icon button when it ships an icon and a compact labelled button when it
157
+ * does not · one kebab holding every `mobile: 'more'` action · `mobile: 'hidden'` drops the action
158
+ * entirely.
159
+ *
160
+ * The shell's `mobile: 'more'` global actions are folded in by `PageBar`'s ROW-1 group only, not by
161
+ * this component — see `BarActionRowProps.host` for why every other home has to stay out of them.
162
+ *
163
+ * @example
164
+ * <PageBar actions={{ primary: { key: 'new', label: 'New run', onClick: start },
165
+ * secondary: [{ key: 'export', label: 'Export', onClick: exportCsv }] }} />
166
+ */
167
+ export declare function ActionGroup(props: ActionGroupProps): ReactNode;
168
+ export {};
169
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/controls/actions.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,287 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { ActionIcon, Button, Group, Menu } from "@mantine/core";
3
+ import { createContext, useContext, useEffect } from "react";
4
+ import { IconSlot } from "../theme/icon-slot.js";
5
+ import { ControlGroup } from "./control-group.js";
6
+ import classes from "./actions.module.css";
7
+ const DESKTOP_SECONDARY_MAX = 3;
8
+ const MOBILE_GLOBAL_BAR_MAX = 2;
9
+ const NO_EXTRAS = {
10
+ mobileMoreActions: [],
11
+ claimKebab: () => () => {
12
+ }
13
+ };
14
+ const BarExtrasContext = createContext(NO_EXTRAS);
15
+ function BarExtrasProvider({
16
+ value,
17
+ children
18
+ }) {
19
+ return /* @__PURE__ */ jsx(BarExtrasContext.Provider, { value, children });
20
+ }
21
+ function barActionMobile(action, isPrimary) {
22
+ if (action.kind === "menu") return "more";
23
+ return action.mobile ?? (isPrimary ? "bar" : "more");
24
+ }
25
+ function globalActionMobile(action, index) {
26
+ return action.mobile ?? (index < MOBILE_GLOBAL_BAR_MAX ? "bar" : "more");
27
+ }
28
+ function globalActionAsBarAction(action) {
29
+ return { key: action.key, kind: "custom", node: action.node, mobile: "more" };
30
+ }
31
+ function DotsGlyph() {
32
+ return /* @__PURE__ */ jsxs("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: [
33
+ /* @__PURE__ */ jsx("circle", { cx: 12, cy: 5, r: 1.75 }),
34
+ /* @__PURE__ */ jsx("circle", { cx: 12, cy: 12, r: 1.75 }),
35
+ /* @__PURE__ */ jsx("circle", { cx: 12, cy: 19, r: 1.75 })
36
+ ] });
37
+ }
38
+ function ChevronGlyph() {
39
+ return /* @__PURE__ */ jsx("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx(
40
+ "path",
41
+ {
42
+ d: "M6 9l6 6 6-6",
43
+ stroke: "currentColor",
44
+ strokeWidth: 2,
45
+ strokeLinecap: "round",
46
+ strokeLinejoin: "round"
47
+ }
48
+ ) });
49
+ }
50
+ const MOBILE_PRIMARY_PADDING_X = 10;
51
+ const BAR_GAP = 6;
52
+ function BarButton({
53
+ action,
54
+ variant,
55
+ px
56
+ }) {
57
+ const shared = {
58
+ variant,
59
+ ...px !== void 0 && { px },
60
+ disabled: action.disabled === true,
61
+ loading: action.loading === true,
62
+ // EVERY icon in this file goes through `IconSlot` — the box is the framework's, never the
63
+ // caller's glyph (see `theme/icon-slot.tsx`). Mantine's `leftSection` is a bare flex box with no
64
+ // size of its own, so a consumer's `width="24"` SVG used to set this row's height.
65
+ ...action.icon !== void 0 && { leftSection: /* @__PURE__ */ jsx(IconSlot, { children: action.icon }) },
66
+ ...action.danger === true && { color: "red" },
67
+ ...action.onClick !== void 0 && { onClick: action.onClick }
68
+ };
69
+ if (action.Anchor !== void 0) {
70
+ return /* @__PURE__ */ jsx(Button, { component: action.Anchor, ...shared, children: action.label });
71
+ }
72
+ if (action.link !== void 0) {
73
+ return /* @__PURE__ */ jsx(Button, { component: "a", href: action.link.to, ...shared, children: action.label });
74
+ }
75
+ return /* @__PURE__ */ jsx(Button, { ...shared, children: action.label });
76
+ }
77
+ function BarIconButton({
78
+ action,
79
+ variant
80
+ }) {
81
+ const shared = {
82
+ variant,
83
+ "aria-label": action.label,
84
+ disabled: action.disabled === true,
85
+ loading: action.loading === true,
86
+ ...action.danger === true && { color: "red" },
87
+ ...action.onClick !== void 0 && { onClick: action.onClick }
88
+ };
89
+ const body = /* @__PURE__ */ jsx(IconSlot, { children: action.icon });
90
+ if (action.Anchor !== void 0) {
91
+ return /* @__PURE__ */ jsx(ActionIcon, { component: action.Anchor, ...shared, children: body });
92
+ }
93
+ if (action.link !== void 0) {
94
+ return /* @__PURE__ */ jsx(ActionIcon, { component: "a", href: action.link.to, ...shared, children: body });
95
+ }
96
+ return /* @__PURE__ */ jsx(ActionIcon, { ...shared, children: body });
97
+ }
98
+ function MenuRow({ action }) {
99
+ const shared = {
100
+ className: action.danger === true ? `${classes.menuItem} ${classes.menuItemDanger}` : classes.menuItem,
101
+ disabled: action.disabled === true,
102
+ ...action.icon !== void 0 && { leftSection: /* @__PURE__ */ jsx(IconSlot, { children: action.icon }) },
103
+ ...action.onClick !== void 0 && { onClick: action.onClick }
104
+ };
105
+ if (action.Anchor !== void 0 && action.disabled !== true) {
106
+ return /* @__PURE__ */ jsx(Menu.Item, { component: action.Anchor, ...shared, children: action.label });
107
+ }
108
+ if (action.link !== void 0 && action.disabled !== true) {
109
+ return /* @__PURE__ */ jsx(Menu.Item, { component: "a", href: action.link.to, ...shared, children: action.label });
110
+ }
111
+ return /* @__PURE__ */ jsx(Menu.Item, { ...shared, children: action.label });
112
+ }
113
+ function menuRows(actions) {
114
+ return actions.flatMap((action) => {
115
+ if (action.kind === "menu") {
116
+ return [
117
+ /* @__PURE__ */ jsx(Menu.Label, { children: action.label }, `${action.key}-label`),
118
+ ...menuRows(action.items)
119
+ ];
120
+ }
121
+ if (action.kind === "custom") {
122
+ return [
123
+ /* @__PURE__ */ jsx("div", { className: classes.customRow, children: action.node }, action.key)
124
+ ];
125
+ }
126
+ return [/* @__PURE__ */ jsx(MenuRow, { action }, action.key)];
127
+ });
128
+ }
129
+ function OverflowMenu({
130
+ actions,
131
+ trigger = "more",
132
+ label = "More"
133
+ }) {
134
+ if (actions.length === 0) return null;
135
+ return /* @__PURE__ */ jsxs(
136
+ Menu,
137
+ {
138
+ position: "bottom-end",
139
+ offset: 6,
140
+ withinPortal: true,
141
+ zIndex: 400,
142
+ closeOnItemClick: true,
143
+ closeOnClickOutside: true,
144
+ closeOnEscape: true,
145
+ trapFocus: true,
146
+ returnFocus: true,
147
+ menuItemTabIndex: 0,
148
+ children: [
149
+ /* @__PURE__ */ jsx(Menu.Target, { children: trigger === "kebab" ? (
150
+ // `size="ctl"` explicitly, not inherited: the kebab is rendered by `HeaderGlobalActions`
151
+ // too, which is shell chrome rather than a home's slot, so there is no `CtlSlot` above it
152
+ // there. 28px beside 30px buttons is the mismatch the header read as.
153
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "ctl", "aria-label": label, children: /* @__PURE__ */ jsx(IconSlot, { className: classes.kebabIcon, children: /* @__PURE__ */ jsx(DotsGlyph, {}) }) })
154
+ ) : /* @__PURE__ */ jsx(
155
+ Button,
156
+ {
157
+ variant: "default",
158
+ rightSection: /* @__PURE__ */ jsx(IconSlot, { className: classes.chevronIcon, children: /* @__PURE__ */ jsx(ChevronGlyph, {}) }),
159
+ children: label
160
+ }
161
+ ) }),
162
+ /* @__PURE__ */ jsx(Menu.Dropdown, { children: menuRows(actions) })
163
+ ]
164
+ }
165
+ );
166
+ }
167
+ function joinRuns(actions, opts) {
168
+ const joinable = (action) => action.kind === void 0 && action.key !== opts.excludeKey && (action.group === true || opts.viewport === "mobile" && action.icon !== void 0);
169
+ const runs = [];
170
+ for (const action of actions) {
171
+ const tail = runs.at(-1);
172
+ if (joinable(action) && tail !== void 0 && tail.every(joinable)) tail.push(action);
173
+ else runs.push([action]);
174
+ }
175
+ return runs;
176
+ }
177
+ function BarActionRow({
178
+ primary,
179
+ secondary,
180
+ host,
181
+ viewport = "both",
182
+ mobileOnly = [],
183
+ syncNode
184
+ }) {
185
+ const context = useContext(BarExtrasContext);
186
+ const extras = host === "page" ? context : NO_EXTRAS;
187
+ const list = secondary ?? [];
188
+ const flat = list.filter((a) => a.kind !== "menu");
189
+ const menus = list.filter((a) => a.kind === "menu");
190
+ const inline = flat.slice(0, DESKTOP_SECONDARY_MAX);
191
+ const desktopOverflow = [...flat.slice(DESKTOP_SECONDARY_MAX), ...menus];
192
+ const wantsMobile = viewport === "both";
193
+ const mobileList = [...list, ...mobileOnly];
194
+ const mobileBar = wantsMobile ? [
195
+ ...primary !== void 0 && barActionMobile(primary, true) === "bar" ? [primary] : [],
196
+ ...mobileList.filter((a) => barActionMobile(a, false) === "bar")
197
+ ] : [];
198
+ const mobileMore = wantsMobile ? [
199
+ ...primary !== void 0 && barActionMobile(primary, true) === "more" ? [primary] : [],
200
+ ...mobileList.filter((a) => barActionMobile(a, false) === "more"),
201
+ ...extras.mobileMoreActions
202
+ ] : [];
203
+ const claimsKebab = mobileMore.length > 0;
204
+ const claimKebab = extras.claimKebab;
205
+ useEffect(() => {
206
+ if (!claimsKebab) return;
207
+ return claimKebab();
208
+ }, [claimsKebab, claimKebab]);
209
+ const desktopPrimary = primary !== void 0 ? /* @__PURE__ */ jsx(BarEntry, { action: primary, emphasis: "primary" }) : null;
210
+ const hasDesktop = primary !== void 0 || list.length > 0;
211
+ const hasMobile = mobileBar.length > 0 || claimsKebab;
212
+ if (!hasDesktop && !hasMobile && syncNode === void 0) return null;
213
+ const desktopLead = inline.length > 0 || desktopOverflow.length > 0 || syncNode === void 0;
214
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
215
+ hasDesktop && desktopLead && /* @__PURE__ */ jsxs(Group, { gap: BAR_GAP, wrap: "nowrap", visibleFrom: "sm", children: [
216
+ joinRuns(inline, { viewport: "desktop" }).map(
217
+ (run) => run.length === 1 ? /* @__PURE__ */ jsx(BarEntry, { action: run[0], emphasis: "secondary" }, run[0].key) : /* @__PURE__ */ jsx(ControlGroup, { children: run.map((action) => /* @__PURE__ */ jsx(BarEntry, { action, emphasis: "secondary", iconOnly: true }, action.key)) }, `join-${run[0].key}`)
218
+ ),
219
+ /* @__PURE__ */ jsx(OverflowMenu, { actions: desktopOverflow }),
220
+ syncNode === void 0 && desktopPrimary
221
+ ] }),
222
+ syncNode,
223
+ syncNode !== void 0 && desktopPrimary !== null && /* @__PURE__ */ jsx(Group, { gap: BAR_GAP, wrap: "nowrap", visibleFrom: "sm", children: desktopPrimary }),
224
+ hasMobile && /* @__PURE__ */ jsxs(Group, { gap: BAR_GAP, wrap: "nowrap", hiddenFrom: "sm", children: [
225
+ joinRuns(mobileBar, { viewport: "mobile", excludeKey: primary?.key }).map(
226
+ (run) => run.length === 1 ? /* @__PURE__ */ jsx(
227
+ BarEntry,
228
+ {
229
+ action: run[0],
230
+ emphasis: run[0].key === primary?.key ? "mobile-primary" : "mobile-secondary"
231
+ },
232
+ run[0].key
233
+ ) : /* @__PURE__ */ jsx(ControlGroup, { children: run.map((action) => /* @__PURE__ */ jsx(
234
+ BarEntry,
235
+ {
236
+ action,
237
+ emphasis: action.key === primary?.key ? "mobile-primary" : "mobile-secondary"
238
+ },
239
+ action.key
240
+ )) }, `join-${run[0].key}`)
241
+ ),
242
+ claimsKebab && /* @__PURE__ */ jsx(OverflowMenu, { actions: mobileMore, trigger: "kebab", label: "More actions" })
243
+ ] })
244
+ ] });
245
+ }
246
+ function ActionGroup(props) {
247
+ return /* @__PURE__ */ jsx(BarActionRow, { ...props, host: "slot" });
248
+ }
249
+ function BarEntry({
250
+ action,
251
+ emphasis,
252
+ iconOnly
253
+ }) {
254
+ if (action.kind === "custom") return /* @__PURE__ */ jsx("span", { className: classes.customSlot, children: action.node });
255
+ if (action.kind === "menu") return /* @__PURE__ */ jsx(OverflowMenu, { actions: [action], label: action.label });
256
+ if (emphasis === "mobile-primary") return /* @__PURE__ */ jsx(MobileBarEntry, { action, variant: "filled" });
257
+ if (emphasis === "mobile-secondary") return /* @__PURE__ */ jsx(MobileBarEntry, { action, variant: "subtle" });
258
+ if (iconOnly === true && action.icon !== void 0) {
259
+ return /* @__PURE__ */ jsx(BarIconButton, { action, variant: "default" });
260
+ }
261
+ return /* @__PURE__ */ jsx(BarButton, { action, variant: emphasis === "primary" ? "filled" : "default" });
262
+ }
263
+ function MobileBarEntry({
264
+ action,
265
+ variant
266
+ }) {
267
+ if (action.icon !== void 0) return /* @__PURE__ */ jsx(BarIconButton, { action, variant });
268
+ return /* @__PURE__ */ jsx(
269
+ BarButton,
270
+ {
271
+ action,
272
+ variant: variant === "filled" ? "filled" : "default",
273
+ px: MOBILE_PRIMARY_PADDING_X
274
+ }
275
+ );
276
+ }
277
+ export {
278
+ ActionGroup,
279
+ BarActionRow,
280
+ BarExtrasProvider,
281
+ DESKTOP_SECONDARY_MAX,
282
+ MOBILE_GLOBAL_BAR_MAX,
283
+ OverflowMenu,
284
+ barActionMobile,
285
+ globalActionAsBarAction,
286
+ globalActionMobile
287
+ };
@@ -0,0 +1,62 @@
1
+ /*
2
+ * `OverflowMenu` row chrome. Rows are `ctl`-tier (30px) on desktop and 44px below `sm`
3
+ * (`--vx-space-sheet-row-height`, law C15) — the mobile kebab IS the sheet, so there is no second
4
+ * component for the touch case.
5
+ */
6
+
7
+ .menuItem {
8
+ min-height: calc(var(--vx-space-control-height-ctl) * var(--mantine-scale, 1));
9
+ }
10
+
11
+ /*
12
+ * Danger rows read the status token, not a Mantine shade — `--vx-bad-solid` resolves per color
13
+ * scheme, a pinned `red.6` does not (see the `mantine-shade-index` guard).
14
+ */
15
+ .menuItemDanger {
16
+ color: var(--vx-bad-solid);
17
+ }
18
+
19
+ /* A `kind: 'custom'` row hosts the consumer's own node, so it only supplies the row box. */
20
+ .customRow {
21
+ display: flex;
22
+ align-items: center;
23
+ min-height: calc(var(--vx-space-control-height-ctl) * var(--mantine-scale, 1));
24
+ padding: 0 var(--vx-space-row-inset-x);
25
+ }
26
+
27
+ @media (max-width: 47.99375em) {
28
+ .menuItem,
29
+ .customRow {
30
+ min-height: var(--vx-space-sheet-row-height);
31
+ }
32
+ }
33
+
34
+ /*
35
+ * A `kind: 'custom'` node's slot — see `BarEntry`'s doc. The whole point is the HEIGHT: basalt does
36
+ * not draw the node, so the only thing it can guarantee is the box it sits in. `inline-flex` +
37
+ * `align-items: center` centres a 20px Badge inside the 30px tier instead of letting it sit on the
38
+ * row's baseline, and `flex: none` keeps it out of the row's own shrink arithmetic.
39
+ */
40
+ .customSlot {
41
+ display: inline-flex;
42
+ flex: none;
43
+ align-items: center;
44
+ height: calc(var(--vx-space-control-height-ctl) * var(--mantine-scale, 1));
45
+ }
46
+
47
+ /*
48
+ * The fold's own two glyphs, sized by overriding the icon slot's box var — never by the `width`
49
+ * attribute on the SVG, which is a presentation attribute the slot's CSS outranks anyway. The kebab
50
+ * reads at 18px because it is the ENTIRE content of an icon button (there is no label beside it to
51
+ * balance against), while `More`'s trailing chevron is the same 14px every pill's trailing
52
+ * affordance takes (`controls.module.css`'s `.glyph`).
53
+ */
54
+ .kebabIcon {
55
+ --vx-space-icon-size: 1.125rem;
56
+ }
57
+
58
+ .chevronIcon {
59
+ --vx-space-icon-size: 0.875rem;
60
+
61
+ color: var(--vx-faint);
62
+ }
@@ -0,0 +1,13 @@
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 menuItem: string
7
+ readonly menuItemDanger: string
8
+ readonly customRow: string
9
+ readonly customSlot: string
10
+ readonly kebabIcon: string
11
+ readonly chevronIcon: string
12
+ }
13
+ export default classes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=actions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.test.d.ts","sourceRoot":"","sources":["../../src/controls/actions.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=actions.type-guard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.type-guard.test.d.ts","sourceRoot":"","sources":["../../src/controls/actions.type-guard.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `CompareFilter` — the period-comparison enum every dashboard grows
3
+ * (`docs/CONTROLS-SPEC.md` §3). A `SelectFilter` narrowed to the three values basalt names, so the
4
+ * label, the option order and the fallback are the same on every page instead of three per app.
5
+ *
6
+ * @example
7
+ * // compare: field.enum(['none', 'previous', 'year'], 'none')
8
+ * <CompareFilter field={analytics.field.compare} />
9
+ */
10
+ import type { ReactNode } from 'react';
11
+ import type { EnumField, FieldHandle } from '../state.js';
12
+ /** The three comparison bases — declare the field as `field.enum(COMPARE_VALUES, 'none')`. */
13
+ export type CompareValue = 'none' | 'previous' | 'year';
14
+ /** The declared value order, so a consumer's `field.enum` and this control cannot disagree. */
15
+ export declare const COMPARE_VALUES: readonly CompareValue[];
16
+ /**
17
+ * The three option labels basalt names — the other half of "the same on every page instead of three
18
+ * per app", which this control promised and did not deliver.
19
+ *
20
+ * `field.enum` labels an option with the raw VALUE until a consumer calls `store.labels()`, so a
21
+ * `CompareFilter` over an unlabelled field printed `none` / `previous` / `year` in its popover and,
22
+ * now that the pill reads the value (see `EnumFilter`'s doc), in the bar too. Every app then wrote
23
+ * the same three strings into its own `labels()` call, which is three chances to disagree about what
24
+ * `year` means.
25
+ *
26
+ * These are DEFAULTS, not overrides: a field whose label differs from its value has been labelled
27
+ * deliberately, and that label wins. Detecting it as `label !== value` is exactly the signal
28
+ * `optionsFor` leaves behind (`state/fields.ts` — `label: map?.[value] ?? value`), so nothing new
29
+ * has to be threaded through the store to ask the question.
30
+ */
31
+ export declare const COMPARE_LABELS: Readonly<Record<CompareValue, string>>;
32
+ export type CompareFilterProps = {
33
+ readonly field: FieldHandle<EnumField<CompareValue>>;
34
+ /** @default 'Compare' */
35
+ readonly label?: string;
36
+ /** Leading pill icon. Defaults to an arrows-swap glyph — like `RangeFilter`'s calendar, the mark
37
+ * is part of what makes the pill legible as a period comparison. */
38
+ readonly icon?: ReactNode;
39
+ };
40
+ export declare function CompareFilter({ field, label, icon }: CompareFilterProps): ReactNode;
41
+ //# sourceMappingURL=compare-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-filter.d.ts","sourceRoot":"","sources":["../../src/controls/compare-filter.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAItD,8FAA8F;AAC9F,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAA;AAEvD,+FAA+F;AAC/F,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAAiC,CAAA;AAEnF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAIjE,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAA;IACpD,yBAAyB;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;yEACqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAC1B,CAAA;AAED,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,KAAiB,EAAE,IAAI,EAAE,EAAE,kBAAkB,GAAG,SAAS,CAS/F"}
@@ -0,0 +1,23 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { EnumFilter } from "./enum-filter.js";
3
+ import { SwapGlyph } from "./glyphs.js";
4
+ const COMPARE_VALUES = ["none", "previous", "year"];
5
+ const COMPARE_LABELS = {
6
+ none: "No comparison",
7
+ previous: "Previous period",
8
+ year: "Same period last year"
9
+ };
10
+ function CompareFilter({ field, label = "Compare", icon }) {
11
+ const options = field.options.map(
12
+ (option) => option.label === option.value && isCompareValue(option.value) ? { value: option.value, label: COMPARE_LABELS[option.value] } : option
13
+ );
14
+ return /* @__PURE__ */ jsx(EnumFilter, { field, label, icon: icon ?? /* @__PURE__ */ jsx(SwapGlyph, {}), options });
15
+ }
16
+ function isCompareValue(value) {
17
+ return COMPARE_VALUES.includes(value);
18
+ }
19
+ export {
20
+ COMPARE_LABELS,
21
+ COMPARE_VALUES,
22
+ CompareFilter
23
+ };
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+ export type ControlGroupProps = {
3
+ readonly children: ReactNode;
4
+ /**
5
+ * `none` (the default) JOINS the children: no gap, one shared hairline between each pair, and the
6
+ * corner radius only on the outer ends, so the set reads as one box.
7
+ *
8
+ * `tight` keeps the tier's own control gap and only guarantees the alignment and the no-wrap. It
9
+ * is for a set that belongs together but must stay separately pressable — three destructive
10
+ * actions, or a pair where one child is `filled` and joining it would put a fill edge against a
11
+ * border edge.
12
+ *
13
+ * @default 'none'
14
+ */
15
+ readonly gap?: 'none' | 'tight';
16
+ readonly className?: string;
17
+ };
18
+ export declare function ControlGroup({ children, gap, className }: ControlGroupProps): ReactNode;
19
+ //# sourceMappingURL=control-group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-group.d.ts","sourceRoot":"","sources":["../../src/controls/control-group.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,GAAY,EAAE,SAAS,EAAE,EAAE,iBAAiB,GAAG,SAAS,CAWhG"}