basalt-ui 1.24.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +25 -0
  3. package/MIGRATING.md +547 -21
  4. package/README.md +46 -33
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +155 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +113 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1275 -31
  17. package/configs/oxlint-plugin.test.ts +947 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +30 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +91 -0
  75. package/dist/controls/filter-context.d.ts +55 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +19 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +44 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/peer-boundary.test.d.ts +2 -0
  99. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  100. package/dist/controls/props-contract.test.d.ts +2 -0
  101. package/dist/controls/props-contract.test.d.ts.map +1 -0
  102. package/dist/controls/range-filter.d.ts +29 -0
  103. package/dist/controls/range-filter.d.ts.map +1 -0
  104. package/dist/controls/range-filter.js +120 -0
  105. package/dist/controls/search-filter.d.ts +10 -0
  106. package/dist/controls/search-filter.d.ts.map +1 -0
  107. package/dist/controls/search-filter.js +81 -0
  108. package/dist/controls/select-filter.d.ts +74 -0
  109. package/dist/controls/select-filter.d.ts.map +1 -0
  110. package/dist/controls/select-filter.js +10 -0
  111. package/dist/controls/sync-button.d.ts +38 -0
  112. package/dist/controls/sync-button.d.ts.map +1 -0
  113. package/dist/controls/sync-button.js +119 -0
  114. package/dist/controls/sync-button.module.css +63 -0
  115. package/dist/controls/sync-button.module.css.d.ts +11 -0
  116. package/dist/controls/sync-button.test.d.ts +2 -0
  117. package/dist/controls/sync-button.test.d.ts.map +1 -0
  118. package/dist/controls/toggle-filter.d.ts +9 -0
  119. package/dist/controls/toggle-filter.d.ts.map +1 -0
  120. package/dist/controls/toggle-filter.js +42 -0
  121. package/dist/controls/view-tabs.d.ts +23 -0
  122. package/dist/controls/view-tabs.d.ts.map +1 -0
  123. package/dist/controls/view-tabs.js +59 -0
  124. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  125. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  126. package/dist/controls-dates/date-range-picker.js +18 -0
  127. package/dist/controls-dates/index.d.ts +9 -0
  128. package/dist/controls-dates/index.d.ts.map +1 -0
  129. package/dist/controls-dates/index.js +4 -0
  130. package/dist/dashboard/delta-badge.d.ts +6 -12
  131. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  132. package/dist/dashboard/delta-badge.js +1 -50
  133. package/dist/dashboard/query-state.d.ts +1 -1
  134. package/dist/dashboard/settings-section.d.ts +6 -4
  135. package/dist/dashboard/settings-section.d.ts.map +1 -1
  136. package/dist/dashboard/settings-section.js +23 -16
  137. package/dist/dashboard/settings-section.module.css +10 -0
  138. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  139. package/dist/dashboard/settings-section.test.d.ts +2 -0
  140. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  141. package/dist/dashboard/stat-card.d.ts +44 -9
  142. package/dist/dashboard/stat-card.d.ts.map +1 -1
  143. package/dist/dashboard/stat-card.js +80 -48
  144. package/dist/dashboard/stat-card.module.css +82 -0
  145. package/dist/dashboard/stat-card.module.css.d.ts +11 -0
  146. package/dist/data/data-table.d.ts +102 -20
  147. package/dist/data/data-table.d.ts.map +1 -1
  148. package/dist/data/data-table.js +166 -65
  149. package/dist/data/table.d.ts +1 -1
  150. package/dist/data/table.d.ts.map +1 -1
  151. package/dist/guard/grace.test.d.ts +20 -0
  152. package/dist/guard/grace.test.d.ts.map +1 -0
  153. package/dist/guard/index.d.ts +66 -3
  154. package/dist/guard/index.d.ts.map +1 -1
  155. package/dist/guard/index.js +82 -7
  156. package/dist/guard/types.d.ts +3 -3
  157. package/dist/guard/types.d.ts.map +1 -1
  158. package/dist/index.d.ts +12 -2
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +13 -10
  161. package/dist/nav/types.d.ts +78 -2
  162. package/dist/nav/types.d.ts.map +1 -1
  163. package/dist/notifications/bell.d.ts +4 -1
  164. package/dist/notifications/bell.d.ts.map +1 -1
  165. package/dist/notifications/index.d.ts +1 -1
  166. package/dist/router-tanstack/field.d.ts +14 -0
  167. package/dist/router-tanstack/field.d.ts.map +1 -0
  168. package/dist/router-tanstack/field.js +4 -0
  169. package/dist/router-tanstack/index.d.ts +6 -2
  170. package/dist/router-tanstack/index.d.ts.map +1 -1
  171. package/dist/router-tanstack/index.js +4 -0
  172. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  173. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  174. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  175. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  176. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  177. package/dist/router-tanstack/search-param-store.js +16 -45
  178. package/dist/router-tanstack/search-store.d.ts +81 -0
  179. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  180. package/dist/router-tanstack/search-store.js +187 -0
  181. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  182. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  183. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  184. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  185. package/dist/router-tanstack/use-nav.d.ts +0 -2
  186. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  187. package/dist/router-tanstack/use-nav.js +34 -12
  188. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  189. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  190. package/dist/section/index.d.ts +2 -0
  191. package/dist/section/index.d.ts.map +1 -0
  192. package/dist/section/index.js +4 -0
  193. package/dist/section/section.d.ts +24 -0
  194. package/dist/section/section.d.ts.map +1 -0
  195. package/dist/section/section.js +126 -0
  196. package/dist/section/section.module.css +104 -0
  197. package/dist/section/section.module.css.d.ts +11 -0
  198. package/dist/section/section.test.d.ts +2 -0
  199. package/dist/section/section.test.d.ts.map +1 -0
  200. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  201. package/dist/shell/app-breadcrumbs.js +18 -15
  202. package/dist/shell/app-header.module.css +77 -37
  203. package/dist/shell/app-header.module.css.d.ts +2 -1
  204. package/dist/shell/app-mobile-nav.d.ts +9 -2
  205. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  206. package/dist/shell/app-mobile-nav.js +98 -11
  207. package/dist/shell/app-mobile-nav.module.css +8 -0
  208. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  209. package/dist/shell/app-sidebar.d.ts +55 -28
  210. package/dist/shell/app-sidebar.d.ts.map +1 -1
  211. package/dist/shell/app-sidebar.js +116 -67
  212. package/dist/shell/app-sidebar.module.css +303 -20
  213. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  214. package/dist/shell/index.d.ts +55 -21
  215. package/dist/shell/index.d.ts.map +1 -1
  216. package/dist/shell/index.js +40 -18
  217. package/dist/shell/mobile-nav-model.d.ts +13 -2
  218. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  219. package/dist/shell/mobile-nav-model.js +15 -1
  220. package/dist/shell/nav-count-badge.d.ts +1 -1
  221. package/dist/shell/page-bar.d.ts +51 -0
  222. package/dist/shell/page-bar.d.ts.map +1 -0
  223. package/dist/shell/page-bar.js +154 -0
  224. package/dist/shell/page-bar.module.css +178 -0
  225. package/dist/shell/page-bar.module.css.d.ts +17 -0
  226. package/dist/shell/page-bar.test.d.ts +2 -0
  227. package/dist/shell/page-bar.test.d.ts.map +1 -0
  228. package/dist/shell/sidebar-block-model.d.ts +48 -0
  229. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  230. package/dist/shell/sidebar-block-model.js +39 -0
  231. package/dist/shell/sidebar-blocks.d.ts +32 -0
  232. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  233. package/dist/shell/sidebar-blocks.js +169 -0
  234. package/dist/shell/sidebar-search.d.ts +13 -1
  235. package/dist/shell/sidebar-search.d.ts.map +1 -1
  236. package/dist/shell/sidebar-search.js +34 -1
  237. package/dist/shell/sidebar-search.module.css +26 -0
  238. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  239. package/dist/state/fields.d.ts +392 -0
  240. package/dist/state/fields.d.ts.map +1 -0
  241. package/dist/state/fields.js +369 -0
  242. package/dist/state/fields.test.d.ts +2 -0
  243. package/dist/state/fields.test.d.ts.map +1 -0
  244. package/dist/state/persisted.d.ts +48 -0
  245. package/dist/state/persisted.d.ts.map +1 -0
  246. package/dist/state/persisted.js +105 -0
  247. package/dist/state.d.ts +11 -56
  248. package/dist/state.d.ts.map +1 -1
  249. package/dist/state.js +14 -120
  250. package/dist/styles.css +13 -0
  251. package/dist/surfaces.d.ts +106 -24
  252. package/dist/surfaces.d.ts.map +1 -1
  253. package/dist/surfaces.js +155 -19
  254. package/dist/theme/controls.module.css +16 -1
  255. package/dist/theme/ctl-theme.d.ts +32 -0
  256. package/dist/theme/ctl-theme.d.ts.map +1 -0
  257. package/dist/theme/ctl-theme.js +54 -0
  258. package/dist/theme/ctl-theme.test.d.ts +2 -0
  259. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  260. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  261. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  262. package/dist/theme/divider-law.test.d.ts +2 -0
  263. package/dist/theme/divider-law.test.d.ts.map +1 -0
  264. package/dist/theme/icon-slot.d.ts +44 -0
  265. package/dist/theme/icon-slot.d.ts.map +1 -0
  266. package/dist/theme/icon-slot.js +21 -0
  267. package/dist/theme/icon-slot.module.css +47 -0
  268. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  269. package/dist/theme/icon-slot.test.d.ts +2 -0
  270. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  271. package/dist/theme/index.d.ts +1 -0
  272. package/dist/theme/index.d.ts.map +1 -1
  273. package/dist/theme/index.js +82 -5
  274. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  275. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  276. package/dist/theme/segmented-control.module.css +20 -0
  277. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  278. package/dist/tokens/index.d.ts +12 -0
  279. package/dist/tokens/index.d.ts.map +1 -1
  280. package/dist/tokens/index.js +38 -8
  281. package/dist/tokens/palette.d.ts +97 -61
  282. package/dist/tokens/palette.d.ts.map +1 -1
  283. package/dist/tokens/palette.js +76 -38
  284. package/dist/tokens.css +10 -2
  285. package/dist/widget-header/delta-badge.d.ts +13 -0
  286. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  287. package/dist/widget-header/delta-badge.js +29 -0
  288. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  289. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  290. package/dist/widget-header/index.d.ts +3 -0
  291. package/dist/widget-header/index.d.ts.map +1 -0
  292. package/dist/widget-header/index.js +6 -0
  293. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  294. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  295. package/dist/widget-header/widget-header.d.ts +37 -0
  296. package/dist/widget-header/widget-header.d.ts.map +1 -0
  297. package/dist/widget-header/widget-header.js +77 -0
  298. package/dist/widget-header/widget-header.module.css +335 -0
  299. package/dist/widget-header/widget-header.module.css.d.ts +24 -0
  300. package/dist/widget-header/widget-header.test.d.ts +2 -0
  301. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  302. package/llms.txt +30 -17
  303. package/package.json +14 -1
  304. package/src/charts/cursor/resolve.test.ts +29 -1
  305. package/src/charts/cursor/resolve.ts +38 -6
  306. package/src/charts/cursor/scope.tsx +6 -3
  307. package/src/charts/cursor/store.test.ts +19 -1
  308. package/src/charts/cursor/store.ts +10 -5
  309. package/src/charts/hooks/useChartCursor.ts +16 -8
  310. package/src/charts/index.ts +2 -2
  311. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  312. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  313. package/src/charts/primitives/ChartCard.tsx +86 -146
  314. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  315. package/src/cli/check-coverage.test.ts +297 -0
  316. package/src/cli/check-theme.test.ts +38 -14
  317. package/src/cli/index.ts +392 -16
  318. package/src/cli/init-sync.test.ts +97 -0
  319. package/src/connectivity/connectivity-indicator.tsx +16 -9
  320. package/src/content/index.ts +16 -3
  321. package/src/content/prose.module.css +7 -28
  322. package/src/content/prose.module.css.test.ts +8 -24
  323. package/src/controls/actions.module.css +62 -0
  324. package/src/controls/actions.module.css.d.ts +13 -0
  325. package/src/controls/actions.test.tsx +252 -0
  326. package/src/controls/actions.tsx +655 -0
  327. package/src/controls/actions.type-guard.test.ts +63 -0
  328. package/src/controls/compare-filter.tsx +64 -0
  329. package/src/controls/control-group.test.tsx +229 -0
  330. package/src/controls/control-group.tsx +63 -0
  331. package/src/controls/controls.module.css +542 -0
  332. package/src/controls/controls.module.css.d.ts +35 -0
  333. package/src/controls/controls.router.test.tsx +637 -0
  334. package/src/controls/enum-filter.tsx +159 -0
  335. package/src/controls/filter-context.tsx +153 -0
  336. package/src/controls/filter-pill.tsx +120 -0
  337. package/src/controls/filter-set.test.tsx +472 -0
  338. package/src/controls/filter-set.tsx +225 -0
  339. package/src/controls/filter-sheet.tsx +285 -0
  340. package/src/controls/glyphs.tsx +81 -0
  341. package/src/controls/index.ts +48 -0
  342. package/src/controls/multi-select-filter.tsx +140 -0
  343. package/src/controls/peer-boundary.test.ts +85 -0
  344. package/src/controls/props-contract.test.tsx +106 -0
  345. package/src/controls/range-filter.tsx +184 -0
  346. package/src/controls/search-filter.tsx +123 -0
  347. package/src/controls/select-filter.tsx +89 -0
  348. package/src/controls/sync-button.module.css +63 -0
  349. package/src/controls/sync-button.module.css.d.ts +11 -0
  350. package/src/controls/sync-button.test.tsx +183 -0
  351. package/src/controls/sync-button.tsx +197 -0
  352. package/src/controls/toggle-filter.tsx +60 -0
  353. package/src/controls/view-tabs.tsx +110 -0
  354. package/src/controls-dates/date-range-picker.tsx +42 -0
  355. package/src/controls-dates/index.ts +8 -0
  356. package/src/dashboard/delta-badge.tsx +4 -74
  357. package/src/dashboard/query-state.tsx +1 -1
  358. package/src/dashboard/settings-section.module.css +10 -0
  359. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  360. package/src/dashboard/settings-section.test.tsx +56 -0
  361. package/src/dashboard/settings-section.tsx +33 -30
  362. package/src/dashboard/stat-card.module.css +82 -0
  363. package/src/dashboard/stat-card.module.css.d.ts +11 -0
  364. package/src/dashboard/stat-card.test.tsx +67 -1
  365. package/src/dashboard/stat-card.tsx +209 -68
  366. package/src/data/data-table.test.tsx +282 -6
  367. package/src/data/data-table.tsx +429 -94
  368. package/src/data/table.ts +2 -0
  369. package/src/guard/check-source.test.ts +126 -13
  370. package/src/guard/grace.test.ts +149 -0
  371. package/src/guard/index.ts +172 -29
  372. package/src/guard/types.ts +4 -2
  373. package/src/index.ts +46 -5
  374. package/src/nav/types.ts +84 -2
  375. package/src/notifications/bell.tsx +6 -3
  376. package/src/notifications/index.ts +1 -1
  377. package/src/router-tanstack/field.ts +31 -0
  378. package/src/router-tanstack/index.ts +24 -2
  379. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  380. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  381. package/src/router-tanstack/search-param-store.ts +42 -159
  382. package/src/router-tanstack/search-store.router.test.tsx +661 -0
  383. package/src/router-tanstack/search-store.test.ts +418 -0
  384. package/src/router-tanstack/search-store.ts +380 -0
  385. package/src/router-tanstack/use-nav.test.tsx +187 -0
  386. package/src/router-tanstack/use-nav.ts +72 -14
  387. package/src/section/index.ts +1 -0
  388. package/src/section/section.module.css +104 -0
  389. package/src/section/section.module.css.d.ts +11 -0
  390. package/src/section/section.test.tsx +204 -0
  391. package/src/section/section.tsx +220 -0
  392. package/src/shell/app-breadcrumbs.tsx +44 -31
  393. package/src/shell/app-header.module.css +77 -37
  394. package/src/shell/app-header.module.css.d.ts +2 -1
  395. package/src/shell/app-mobile-nav.module.css +8 -0
  396. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  397. package/src/shell/app-mobile-nav.tsx +147 -8
  398. package/src/shell/app-sidebar-account.tsx +2 -2
  399. package/src/shell/app-sidebar.module.css +303 -20
  400. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  401. package/src/shell/app-sidebar.test.tsx +456 -41
  402. package/src/shell/app-sidebar.tsx +276 -140
  403. package/src/shell/index.test.tsx +291 -5
  404. package/src/shell/index.tsx +137 -38
  405. package/src/shell/mobile-nav-model.test.ts +228 -7
  406. package/src/shell/mobile-nav-model.ts +53 -3
  407. package/src/shell/nav-count-badge.tsx +1 -1
  408. package/src/shell/page-bar.module.css +178 -0
  409. package/src/shell/page-bar.module.css.d.ts +17 -0
  410. package/src/shell/page-bar.test.tsx +316 -0
  411. package/src/shell/page-bar.tsx +315 -0
  412. package/src/shell/sidebar-block-model.ts +83 -0
  413. package/src/shell/sidebar-blocks.tsx +301 -0
  414. package/src/shell/sidebar-search.module.css +26 -0
  415. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  416. package/src/shell/sidebar-search.tsx +71 -2
  417. package/src/state/fields.test.tsx +258 -0
  418. package/src/state/fields.ts +892 -0
  419. package/src/state/persisted.ts +211 -0
  420. package/src/state.ts +46 -240
  421. package/src/styles.css +13 -0
  422. package/src/surfaces.ts +203 -36
  423. package/src/theme/controls.module.css +16 -1
  424. package/src/theme/ctl-theme.test.tsx +275 -0
  425. package/src/theme/ctl-theme.tsx +138 -0
  426. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  427. package/src/theme/density-relations.test.ts +106 -51
  428. package/src/theme/divider-law.test.ts +259 -0
  429. package/src/theme/icon-slot.module.css +47 -0
  430. package/src/theme/icon-slot.module.css.d.ts +8 -0
  431. package/src/theme/icon-slot.test.tsx +275 -0
  432. package/src/theme/icon-slot.tsx +59 -0
  433. package/src/theme/index.ts +165 -3
  434. package/src/theme/layout-rhythm-css.test.ts +72 -0
  435. package/src/theme/radius.test.ts +11 -1
  436. package/src/theme/segmented-control.module.css +20 -0
  437. package/src/theme/shadow-surfaces.test.ts +20 -7
  438. package/src/theme/spacing.test.ts +75 -27
  439. package/src/theme/use-basalt-spacing.ts +2 -2
  440. package/src/tokens/density.test.ts +7 -25
  441. package/src/tokens/index.ts +48 -8
  442. package/src/tokens/palette.ts +107 -73
  443. package/src/widget-header/delta-badge.test.tsx +60 -0
  444. package/src/widget-header/delta-badge.tsx +57 -0
  445. package/src/widget-header/index.ts +2 -0
  446. package/src/widget-header/no-mantine.test.ts +33 -0
  447. package/src/widget-header/widget-header.module.css +335 -0
  448. package/src/widget-header/widget-header.module.css.d.ts +24 -0
  449. package/src/widget-header/widget-header.test.tsx +187 -0
  450. package/src/widget-header/widget-header.tsx +168 -0
  451. package/agent/rules/basalt-agent.md +0 -452
  452. package/agent/rules/basalt-app.md +0 -246
  453. package/agent/rules/basalt-commands.md +0 -320
  454. package/agent/rules/basalt-content.md +0 -409
  455. package/agent/rules/basalt-data.md +0 -269
  456. package/agent/rules/basalt-forms.md +0 -186
  457. package/agent/rules/basalt-notifications.md +0 -264
  458. package/agent/rules/basalt-query.md +0 -187
  459. package/agent/rules/basalt-router.md +0 -336
  460. package/dist/content/article-filter-bar.d.ts +0 -40
  461. package/dist/content/article-filter-bar.d.ts.map +0 -1
  462. package/dist/content/article-filter-bar.js +0 -56
  463. package/dist/shell/page-header.d.ts +0 -13
  464. package/dist/shell/page-header.d.ts.map +0 -1
  465. package/dist/shell/page-header.js +0 -23
  466. package/src/content/article-filter-bar.tsx +0 -88
  467. package/src/shell/page-header.tsx +0 -35
@@ -1,18 +1,21 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { AppShell } from "@mantine/core";
3
- import { useMemo } from "react";
2
+ import { AppShell, Box } from "@mantine/core";
3
+ import { Fragment, useMemo } from "react";
4
4
  import { AppSidebar } from "./app-sidebar.js";
5
5
  import { MobileNav, accountRowCount } from "./app-mobile-nav.js";
6
- import { projectMobileNav } from "./mobile-nav-model.js";
6
+ import { blockRowCount, projectMobileNav } from "./mobile-nav-model.js";
7
7
  import { AppBreadcrumbs } from "./app-breadcrumbs.js";
8
- import { PageActionsOutlet, PageHeaderProvider } from "./page-header.js";
9
- import { useBasaltSpacing } from "../theme/index.js";
8
+ import { PageBarOutlet, PageBarProvider, usePageKebabClaimed } from "./page-bar.js";
9
+ import { OverflowMenu, globalActionAsBarAction, globalActionMobile } from "../controls/actions.js";
10
+ import { CtlSlot, useBasaltSpacing } from "../theme/index.js";
10
11
  import { createPersistedState } from "../state.js";
11
12
  import headerClasses from "./app-header.module.css";
12
13
  import mobileNavClasses from "./app-mobile-nav.module.css";
13
14
  import { AppSidebar as AppSidebar2 } from "./app-sidebar.js";
14
15
  import { NavCountBadge } from "./nav-count-badge.js";
15
- import { SidebarSearch } from "./sidebar-search.js";
16
+ import {
17
+ SidebarSearch
18
+ } from "./sidebar-search.js";
16
19
  import { SidebarAccount } from "./app-sidebar-account.js";
17
20
  import { MobileNav as MobileNav2 } from "./app-mobile-nav.js";
18
21
  import {
@@ -22,7 +25,7 @@ import {
22
25
  MOBILE_MORE_KEY
23
26
  } from "./mobile-nav-model.js";
24
27
  import { AppBreadcrumbs as AppBreadcrumbs2 } from "./app-breadcrumbs.js";
25
- import { PageHeaderProvider as PageHeaderProvider2, PageActions, PageActionsOutlet as PageActionsOutlet2 } from "./page-header.js";
28
+ import { PageBar } from "./page-bar.js";
26
29
  function findActiveCrumb(sections) {
27
30
  for (const section of sections) {
28
31
  const found = findActiveWithParent(section.items);
@@ -74,8 +77,9 @@ function BasaltShell({
74
77
  sections,
75
78
  mobileNav,
76
79
  globalActions,
77
- sidebarNavExtra,
80
+ sidebarBlocks,
78
81
  settingsMenuItems,
82
+ settingsMenu,
79
83
  storageKey = "basalt-sidebar-collapsed",
80
84
  collapsed: collapsedProp,
81
85
  onCollapsedChange,
@@ -93,18 +97,19 @@ function BasaltShell({
93
97
  onCollapsedChange?.(next);
94
98
  };
95
99
  const activeCrumb = findActiveCrumb(sections);
96
- const extraMoreRows = accountRowCount(account) + (settingsMenuItems?.length ?? 0) + (mobileNav?.moreExtra ? 1 : 0);
100
+ const extraMoreRows = accountRowCount(account) + (settingsMenuItems?.length ?? 0) + blockRowCount(sidebarBlocks);
97
101
  const model = useMemo(
98
102
  () => projectMobileNav(sections, { config: mobileNav, extraMoreRows }),
99
103
  [sections, mobileNav, extraMoreRows]
100
104
  );
101
- return /* @__PURE__ */ jsx(PageHeaderProvider, { children: /* @__PURE__ */ jsxs(
105
+ return /* @__PURE__ */ jsx(PageBarProvider, { globalActions: globalActions ?? [], children: /* @__PURE__ */ jsxs(
102
106
  AppShell,
103
107
  {
104
108
  h: "100dvh",
105
109
  layout: "alt",
106
110
  header: {
107
- height: { base: step.appShellHeaderMobileHeight, sm: step.appShellHeaderHeight }
111
+ // ONE height at every width (law C14): nothing reserves a second mobile row any more.
112
+ height: step.appShellHeaderHeight
108
113
  },
109
114
  navbar: {
110
115
  width: {
@@ -120,8 +125,8 @@ function BasaltShell({
120
125
  children: [
121
126
  /* @__PURE__ */ jsx(AppShell.Header, { px: "md", withBorder: false, children: /* @__PURE__ */ jsxs("div", { className: headerClasses.bar, children: [
122
127
  /* @__PURE__ */ jsx("div", { className: headerClasses.lead, children: /* @__PURE__ */ jsx(AppBreadcrumbs, { ...activeCrumb }) }),
123
- /* @__PURE__ */ jsx(PageActionsOutlet, { className: headerClasses.pageActions }),
124
- globalActions && /* @__PURE__ */ jsx("div", { className: headerClasses.global, children: globalActions })
128
+ /* @__PURE__ */ jsx(PageBarOutlet, { className: headerClasses.pageBar }),
129
+ /* @__PURE__ */ jsx(HeaderGlobalActions, { actions: globalActions ?? [] })
125
130
  ] }) }),
126
131
  /* @__PURE__ */ jsx(AppShell.Navbar, { p: 0, withBorder: false, children: /* @__PURE__ */ jsx(
127
132
  AppSidebar,
@@ -130,8 +135,9 @@ function BasaltShell({
130
135
  sections,
131
136
  collapsed,
132
137
  onToggleCollapse: toggleCollapse,
133
- navExtra: sidebarNavExtra,
138
+ ...sidebarBlocks !== void 0 && { blocks: sidebarBlocks },
134
139
  ...settingsMenuItems !== void 0 && { settingsMenuItems },
140
+ ...settingsMenu !== void 0 && { settingsMenu },
135
141
  ...account !== void 0 && { account },
136
142
  ...search !== void 0 && { search }
137
143
  }
@@ -143,13 +149,31 @@ function BasaltShell({
143
149
  model,
144
150
  config: mobileNav,
145
151
  ...account !== void 0 && { account },
146
- ...settingsMenuItems !== void 0 && { settingsMenuItems }
152
+ ...settingsMenuItems !== void 0 && { settingsMenuItems },
153
+ ...sidebarBlocks !== void 0 && { blocks: sidebarBlocks }
147
154
  }
148
155
  ) })
149
156
  ]
150
157
  }
151
158
  ) });
152
159
  }
160
+ function HeaderGlobalActions({ actions }) {
161
+ const kebabClaimed = usePageKebabClaimed();
162
+ if (actions.length === 0) return null;
163
+ const more = actions.filter((action, index) => globalActionMobile(action, index) === "more").map(globalActionAsBarAction);
164
+ return (
165
+ // `CtlSlot` here for the same reason a home wraps its own slot (law C5): `globalActions` are
166
+ // consumer NODES, so basalt cannot pass them a size — it can only provide the tier they resolve
167
+ // against. Without it a `<NotificationBell/>`'s internal `ActionIcon` fell back to Mantine's
168
+ // `md` (28px) beside a 30px page group.
169
+ /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsxs("div", { className: headerClasses.global, children: [
170
+ actions.map(
171
+ (action, index) => globalActionMobile(action, index) === "bar" ? /* @__PURE__ */ jsx(Fragment, { children: action.node }, action.key) : /* @__PURE__ */ jsx(Box, { visibleFrom: "sm", children: action.node }, action.key)
172
+ ),
173
+ !kebabClaimed && more.length > 0 && /* @__PURE__ */ jsx(Box, { hiddenFrom: "sm", children: /* @__PURE__ */ jsx(OverflowMenu, { actions: more, trigger: "kebab", label: "More actions" }) })
174
+ ] }) })
175
+ );
176
+ }
153
177
  export {
154
178
  AppBreadcrumbs2 as AppBreadcrumbs,
155
179
  AppSidebar2 as AppSidebar,
@@ -159,9 +183,7 @@ export {
159
183
  MOBILE_MORE_KEY,
160
184
  MobileNav2 as MobileNav,
161
185
  NavCountBadge,
162
- PageActions,
163
- PageActionsOutlet2 as PageActionsOutlet,
164
- PageHeaderProvider2 as PageHeaderProvider,
186
+ PageBar,
165
187
  SidebarAccount,
166
188
  SidebarSearch,
167
189
  projectMobileNav2 as projectMobileNav
@@ -13,7 +13,7 @@
13
13
  * ever rendering below the fold (6 x 44px + 8px padding = 272px against 415px of headroom on the
14
14
  * smallest supported viewport), so past that the surface becomes a bottom sheet.
15
15
  */
16
- import type { MobileNavConfig, MobileNavModel, SidebarSection } from '../nav/types.js';
16
+ import type { MobileNavConfig, MobileNavModel, SidebarBlock, SidebarSection } from '../nav/types.js';
17
17
  /** Hard cap on bar slots, INCLUDING More. Five 72px slots is the widest a 360px viewport holds. */
18
18
  export declare const MOBILE_MAX_TABS_DEFAULT = 5;
19
19
  /**
@@ -27,12 +27,23 @@ export declare const MOBILE_MORE_KEY = "__more";
27
27
  export type ProjectMobileNavOptions = {
28
28
  config?: MobileNavConfig | undefined;
29
29
  /**
30
- * Extra rows the More surface will hold — account + settings + `moreExtra`. Drives both
30
+ * Extra rows the More surface will hold — account + settings + sidebar blocks. Drives both
31
31
  * `needsMore` and the menu-vs-sheet threshold. The model cannot see those rows (they are
32
32
  * Mantine-rendered chrome, not `SidebarItem`s), so the shell counts them and passes the total.
33
33
  */
34
34
  extraMoreRows?: number;
35
35
  };
36
+ /**
37
+ * How many More-surface rows `blocks` contribute — the `accountRowCount` sibling for sidebar
38
+ * blocks, and for the same reason: `BasaltShell` needs the number BEFORE the projection runs (it
39
+ * feeds `extraMoreRows`, which picks `menu` vs `sheet` against `menuMax`) while the renderer is
40
+ * what decides how many rows a block actually becomes.
41
+ *
42
+ * ONE row per block, never one per item: a list block projects to a single `Awaiting action · 3`
43
+ * row that opens a nested sheet of its items. Counting the items instead would push a 12-item
44
+ * block's More surface to a sheet on the strength of rows that surface never paints.
45
+ */
46
+ export declare function blockRowCount(blocks: readonly SidebarBlock[] | undefined): number;
36
47
  /**
37
48
  * Projects `sections` onto the bar. Implements §2.3's thirteen rules in order, and is the ONLY
38
49
  * place slot selection happens — `MobileNav` receives a finished model and paints it.
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-nav-model.d.ts","sourceRoot":"","sources":["../../src/shell/mobile-nav-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAId,cAAc,EACf,MAAM,cAAc,CAAA;AAErB,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,iGAAiG;AACjG,eAAO,MAAM,eAAe,WAAW,CAAA;AAEvC,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAoQD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EAAE,EAC1B,IAAI,CAAC,EAAE,uBAAuB,GAC7B,cAAc,CA2EhB"}
1
+ {"version":3,"file":"mobile-nav-model.d.ts","sourceRoot":"","sources":["../../src/shell/mobile-nav-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGd,YAAY,EAEZ,cAAc,EACf,MAAM,cAAc,CAAA;AAGrB,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,iGAAiG;AACjG,eAAO,MAAM,eAAe,WAAW,CAAA;AAEvC,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,GAAG,MAAM,CAQjF;AAgSD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EAAE,EAC1B,IAAI,CAAC,EAAE,uBAAuB,GAC7B,cAAc,CA2EhB"}
@@ -1,6 +1,14 @@
1
+ import { sidebarBlockMobile } from "./sidebar-block-model.js";
1
2
  const MOBILE_MAX_TABS_DEFAULT = 5;
2
3
  const MOBILE_MENU_MAX_DEFAULT = 6;
3
4
  const MOBILE_MORE_KEY = "__more";
5
+ function blockRowCount(blocks) {
6
+ if (blocks === void 0) return 0;
7
+ return blocks.filter((block) => {
8
+ if (sidebarBlockMobile(block) !== "more") return false;
9
+ return block.kind !== "list" || block.items.length > 0;
10
+ }).length;
11
+ }
4
12
  const DEV = process.env["NODE_ENV"] !== "production";
5
13
  function placementOf(item, sectionHidden) {
6
14
  if (sectionHidden) return "hidden";
@@ -17,6 +25,9 @@ function hasActiveDestination(item) {
17
25
  if (item.active) return true;
18
26
  return item.children?.some(hasActiveDestination) ?? false;
19
27
  }
28
+ function isActiveDestination(item) {
29
+ return item.active === true;
30
+ }
20
31
  function countRows(items) {
21
32
  return items.reduce((total, item) => total + 1 + countRows(item.children ?? []), 0);
22
33
  }
@@ -51,13 +62,15 @@ function itemCandidate(item) {
51
62
  return {
52
63
  key: item.key,
53
64
  covered: /* @__PURE__ */ new Set([item.key]),
65
+ // `isActiveDestination`, not the rollup: this slot COVERS one key, so it reads one key
66
+ // (`hasActiveDestination`'s doc has the whole accounting).
54
67
  toSlot: () => ({
55
68
  kind: "link",
56
69
  key: item.key,
57
70
  label: item.label,
58
71
  short: item.short ?? item.label,
59
72
  icon: item.icon,
60
- active: hasActiveDestination(item),
73
+ active: isActiveDestination(item),
61
74
  item
62
75
  })
63
76
  };
@@ -240,5 +253,6 @@ export {
240
253
  MOBILE_MAX_TABS_DEFAULT,
241
254
  MOBILE_MENU_MAX_DEFAULT,
242
255
  MOBILE_MORE_KEY,
256
+ blockRowCount,
243
257
  projectMobileNav
244
258
  };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Sidebar nav count badge (docs/DESIGN-SPEC.md §5): mono 10.5px, ink-8% bg, radius 5, height 16,
2
+ * Sidebar nav count badge (docs/DESIGN-SPEC.md §5): mono micro (11px), ink-8% bg, radius 5, height 16,
3
3
  * padding 0 5px, muted text; `marginLeft: auto` pins it to the row end on any render path.
4
4
  * `styles` (inline) rather than a token color prop, since none of Mantine's variant/color
5
5
  * combinations land on the ink-tint idiom. Returns `null` for a zero/empty count so the badge slot
@@ -0,0 +1,51 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ActionGroupProps, BarAction, GlobalAction } from '../controls/actions.js';
3
+ import type { SyncButtonProps } from '../controls/sync-button.js';
4
+ /** The custom property row 2's measured height is published on. */
5
+ export declare const PAGE_BAR_HEIGHT_VAR = "--basalt-page-bar-h";
6
+ /**
7
+ * Internal — mounted by `BasaltShell`. Owns the portal target, and the single-kebab claim that
8
+ * lets the shell fall back to its OWN kebab on a route that renders no `PageBar`.
9
+ */
10
+ export declare function PageBarProvider({ globalActions, children, }: {
11
+ globalActions: readonly GlobalAction[];
12
+ children: ReactNode;
13
+ }): ReactNode;
14
+ /** Internal — the app-shell header node the active page's row 1 portals into. */
15
+ export declare function PageBarOutlet({ className }: {
16
+ className?: string;
17
+ }): import("react").JSX.Element;
18
+ /** Internal — `true` while a page's `ActionGroup` owns the header's one mobile kebab. */
19
+ export declare function usePageKebabClaimed(): boolean;
20
+ export type PageBarProps = {
21
+ /**
22
+ * Read only when there is no `BasaltShell` outlet (a shell-less app such as linewatch). Inside a
23
+ * shell the breadcrumb names the page and this is ignored.
24
+ */
25
+ title?: string;
26
+ icon?: ReactNode;
27
+ actions?: ActionGroupProps;
28
+ /** `scope` is fixed to `'page'` — a global sync belongs in the shell's `globalActions` (law C12). */
29
+ sync?: Omit<SyncButtonProps, 'scope'>;
30
+ /** `FilterSet` descendants only (law C1). */
31
+ filters?: ReactNode;
32
+ /** Right-aligned row-2 actions ("Manage metrics"). */
33
+ filtersEnd?: BarAction[];
34
+ /** One `ViewTabs`. */
35
+ tabs?: ReactNode;
36
+ /**
37
+ * Added to the bar's ROOT — the shell-less `<div data-basalt-page-bar="standalone">`, or row 2's
38
+ * sticky wrapper (`data-basalt-page-bar="shell"`) inside a `BasaltShell`. The seam for the two
39
+ * things only the consumer's own layout knows: bleeding the sticky bar across its container's
40
+ * gutters (`margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter)`) and drawing
41
+ * a hairline under it (`border-bottom: 1px solid var(--vx-border)`).
42
+ *
43
+ * Scope that CSS through this class, not through a global `[data-basalt-page-bar]` selector: the
44
+ * data attributes are stable enough to READ (a shell-less consumer can style
45
+ * `[data-basalt-page-bar="standalone"]` in a pinch, and this doc is the promise that the value
46
+ * stays), but a global rule reaches every page in the app including the ones that want neither.
47
+ */
48
+ className?: string;
49
+ };
50
+ export declare function PageBar({ title, icon, actions, sync, filters, filtersEnd, tabs, className, }: PageBarProps): ReactNode;
51
+ //# sourceMappingURL=page-bar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-bar.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,OAAO,CAAA;AAKjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AA0BxD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,SAAS,YAAY,EAAE,CAAA;IACtC,QAAQ,EAAE,SAAS,CAAA;CACpB,GAAG,SAAS,CA2BZ;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGlE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,MAAM,MAAM,YAAY,GAAG;IACzB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,qGAAqG;IACrG,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IACrC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AA2DD,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,OAAO,EACP,UAAU,EACV,IAAI,EACJ,SAAS,GACV,EAAE,YAAY,GAAG,SAAS,CAmG1B"}
@@ -0,0 +1,154 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import {
3
+ createContext,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useLayoutEffect,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+ import { createPortal } from "react-dom";
12
+ import { CtlSlot } from "../theme/index.js";
13
+ import { BarActionRow, BarExtrasProvider, globalActionAsBarAction } from "../controls/actions.js";
14
+ import { SyncButton } from "../controls/sync-button.js";
15
+ import classes from "./page-bar.module.css";
16
+ const PAGE_BAR_HEIGHT_VAR = "--basalt-page-bar-h";
17
+ const NO_SLOTS = {
18
+ target: null,
19
+ setTarget: () => {
20
+ },
21
+ inShell: false,
22
+ mobileMoreActions: [],
23
+ kebabClaims: 0,
24
+ claimKebab: () => () => {
25
+ }
26
+ };
27
+ const PageBarContext = createContext(NO_SLOTS);
28
+ function PageBarProvider({
29
+ globalActions,
30
+ children
31
+ }) {
32
+ const [target, setTarget] = useState(null);
33
+ const [kebabClaims, setKebabClaims] = useState(0);
34
+ const claimKebab = useCallback(() => {
35
+ setKebabClaims((n) => n + 1);
36
+ return () => setKebabClaims((n) => n - 1);
37
+ }, []);
38
+ const mobileMoreActions = globalActions.filter((action, index) => (action.mobile ?? (index < 2 ? "bar" : "more")) === "more").map(globalActionAsBarAction);
39
+ const value = {
40
+ target,
41
+ setTarget,
42
+ inShell: true,
43
+ mobileMoreActions,
44
+ kebabClaims,
45
+ claimKebab
46
+ };
47
+ return /* @__PURE__ */ jsx(PageBarContext.Provider, { value, children: /* @__PURE__ */ jsx(BarExtrasProvider, { value: { mobileMoreActions, claimKebab }, children }) });
48
+ }
49
+ function PageBarOutlet({ className }) {
50
+ const { setTarget } = useContext(PageBarContext);
51
+ return /* @__PURE__ */ jsx("div", { ref: setTarget, className });
52
+ }
53
+ function usePageKebabClaimed() {
54
+ return useContext(PageBarContext).kebabClaims > 0;
55
+ }
56
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
57
+ function useMeasuredHeightVar(active) {
58
+ const ref = useRef(null);
59
+ useIsomorphicLayoutEffect(() => {
60
+ const node = ref.current;
61
+ if (!active || node === null) return;
62
+ const publish = () => {
63
+ const { height } = node.getBoundingClientRect();
64
+ if (height > 0) {
65
+ document.documentElement.style.setProperty(PAGE_BAR_HEIGHT_VAR, `${height}px`);
66
+ }
67
+ };
68
+ publish();
69
+ if (typeof ResizeObserver === "undefined") {
70
+ return () => document.documentElement.style.removeProperty(PAGE_BAR_HEIGHT_VAR);
71
+ }
72
+ const observer = new ResizeObserver(publish);
73
+ observer.observe(node);
74
+ return () => {
75
+ observer.disconnect();
76
+ document.documentElement.style.removeProperty(PAGE_BAR_HEIGHT_VAR);
77
+ };
78
+ }, [active]);
79
+ return ref;
80
+ }
81
+ function rootClass(own, extra) {
82
+ return [own, extra].filter(Boolean).join(" ");
83
+ }
84
+ function PageBar({
85
+ title,
86
+ icon,
87
+ actions,
88
+ sync,
89
+ filters,
90
+ filtersEnd,
91
+ tabs,
92
+ className
93
+ }) {
94
+ const { target, inShell } = useContext(PageBarContext);
95
+ const filtersEndActions = filtersEnd ?? [];
96
+ const hasRow2 = tabs !== void 0 || filters !== void 0 || filtersEndActions.length > 0;
97
+ const hasLead = !inShell && (title !== void 0 || icon !== void 0);
98
+ const hasRow1 = actions !== void 0 || sync !== void 0 || hasLead || filtersEndActions.length > 0;
99
+ const measureRef = useMeasuredHeightVar(hasRow2 || !inShell && hasRow1);
100
+ const row1 = hasRow1 ? /* @__PURE__ */ jsxs("div", { className: classes.row1, children: [
101
+ hasLead && /* @__PURE__ */ jsxs("div", { className: classes.lead, children: [
102
+ icon !== void 0 && /* @__PURE__ */ jsx("span", { className: classes.icon, "aria-hidden": true, children: icon }),
103
+ title !== void 0 && /* @__PURE__ */ jsx("h1", { className: classes.title, children: title })
104
+ ] }),
105
+ /* @__PURE__ */ jsx("div", { className: classes.row1End, children: (actions !== void 0 || filtersEndActions.length > 0 || sync !== void 0) && /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsx(
106
+ BarActionRow,
107
+ {
108
+ ...actions,
109
+ host: "page",
110
+ mobileOnly: filtersEndActions,
111
+ ...sync !== void 0 && { syncNode: /* @__PURE__ */ jsx(SyncButton, { ...sync, scope: "page" }) }
112
+ }
113
+ ) }) })
114
+ ] }) : null;
115
+ const row2 = hasRow2 ? /* @__PURE__ */ jsxs("div", { className: classes.row2, children: [
116
+ tabs !== void 0 && /* @__PURE__ */ jsx(CtlSlot, { children: tabs }),
117
+ filters !== void 0 && /* @__PURE__ */ jsx("div", { className: classes.filters, children: /* @__PURE__ */ jsx(CtlSlot, { children: filters }) }),
118
+ filtersEndActions.length > 0 && /* @__PURE__ */ jsx("div", { className: classes.filtersEnd, children: /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsx(BarActionRow, { secondary: filtersEndActions, host: "slot", viewport: "desktop" }) }) })
119
+ ] }) : null;
120
+ if (!inShell) {
121
+ if (row1 === null && row2 === null) return null;
122
+ return /* @__PURE__ */ jsxs(
123
+ "div",
124
+ {
125
+ ref: measureRef,
126
+ className: rootClass(classes.bar, className),
127
+ "data-basalt-page-bar": "standalone",
128
+ children: [
129
+ row1,
130
+ row2
131
+ ]
132
+ }
133
+ );
134
+ }
135
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
136
+ row1 !== null && target !== null && createPortal(row1, target),
137
+ row2 !== null && /* @__PURE__ */ jsx(
138
+ "div",
139
+ {
140
+ ref: measureRef,
141
+ className: rootClass(classes.row2Sticky, className),
142
+ "data-basalt-page-bar": "shell",
143
+ children: row2
144
+ }
145
+ )
146
+ ] });
147
+ }
148
+ export {
149
+ PAGE_BAR_HEIGHT_VAR,
150
+ PageBar,
151
+ PageBarOutlet,
152
+ PageBarProvider,
153
+ usePageKebabClaimed
154
+ };
@@ -0,0 +1,178 @@
1
+ /*
2
+ * `PageBar` layout (docs/CONTROLS-SPEC.md §2.1).
3
+ *
4
+ * Law C7 in CSS form: `flex-wrap: nowrap` on every row and NO `overflow-x` anywhere. A home that
5
+ * scrolls sideways hides its own controls; the fold is computed instead — `More` for actions
6
+ * (`ActionGroup`), a `Filters (n)` sheet for filters (`FilterSet`'s job, wave 4). The filter
7
+ * container below only supplies `min-width: 0` so that fold has a box to measure against.
8
+ */
9
+
10
+ /* The shell-less bar: both rows, sticky at the top of the document. One layer under the AppShell
11
+ * header (`--app-shell-header-z-index`, default 100) so it never wins a stacking fight with ordinary
12
+ * in-flow content — Mantine's Input section, Indicator, sticky table headers, and Slider thumbs all
13
+ * sit at `z-index: 1` or higher, which used to paint over a bare `z-index: 1` bar while scrolling. */
14
+ .bar {
15
+ position: sticky;
16
+ top: 0;
17
+ z-index: calc(var(--app-shell-header-z-index, 100) - 1);
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: var(--vx-space-stack-xs);
21
+ background-color: var(--vx-surface-bg);
22
+ }
23
+
24
+ /*
25
+ * Row 2 inside a shell: in the page flow, sticky under the 48px header. `--app-shell-header-height`
26
+ * is Mantine's own AppShell variable, so the offset tracks the header at any density with no React
27
+ * state and no measurement. `z-index` sits one layer below the header itself (see `.bar` above) —
28
+ * high enough to stay above ordinary page content, never above the AppShell header.
29
+ */
30
+ .row2Sticky {
31
+ position: sticky;
32
+ top: var(--app-shell-header-height, 0px);
33
+ z-index: calc(var(--app-shell-header-z-index, 100) - 1);
34
+ background-color: var(--vx-surface-bg);
35
+ }
36
+
37
+ .row1 {
38
+ display: flex;
39
+ flex-wrap: nowrap;
40
+ align-items: center;
41
+ gap: var(--vx-space-control-gap);
42
+ min-width: 0;
43
+ min-height: var(--vx-space-page-bar-row-height);
44
+ }
45
+
46
+ .lead {
47
+ display: flex;
48
+ flex-wrap: nowrap;
49
+ align-items: center;
50
+ gap: var(--vx-space-control-gap);
51
+ flex-shrink: 0;
52
+ min-width: 0;
53
+ }
54
+
55
+ .icon {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ color: var(--vx-muted);
59
+ }
60
+
61
+ /*
62
+ * The shell-less page title (law C8: the breadcrumb names the page inside a shell, this does it
63
+ * outside one). Head-font treatment, same 88%/550 as every `WidgetHeader` title.
64
+ */
65
+ .title {
66
+ margin: 0;
67
+ font-family: var(--basalt-font-head);
68
+ font-size: var(--vx-text-xl);
69
+ font-weight: 550;
70
+ font-stretch: 88%;
71
+ color: var(--vx-ink);
72
+ white-space: nowrap;
73
+ overflow: hidden;
74
+ text-overflow: ellipsis;
75
+ }
76
+
77
+ /*
78
+ * Icons inside a row-1 button are 16px and sit 6px from their label. Mantine spends
79
+ * `--mantine-spacing-xs` (11px) on a Button's section gap regardless of the size tier, which is the
80
+ * BODY tier's number — at 30px it detached the glyph from the word it belongs to. Scoped to the row
81
+ * rather than themed on `Button` globally: a `size="md"` button in a form still wants Mantine's gap.
82
+ */
83
+ .row1 :global(.mantine-Button-section[data-position='left']),
84
+ .row2 :global(.mantine-Button-section[data-position='left']) {
85
+ margin-inline-end: var(--vx-space-control-gap);
86
+ }
87
+
88
+ .row1 :global(.mantine-Button-section) > svg,
89
+ .row1 :global(.mantine-ActionIcon-root) > svg {
90
+ width: 1rem;
91
+ height: 1rem;
92
+ }
93
+
94
+ /* Actions and sync are right-aligned; the lead (or the breadcrumb, in a shell) holds the left. */
95
+ .row1End {
96
+ display: flex;
97
+ flex-wrap: nowrap;
98
+ align-items: center;
99
+ justify-content: flex-end;
100
+ gap: var(--vx-space-control-gap);
101
+ flex: 1 1 auto;
102
+ min-width: 0;
103
+ }
104
+
105
+ /*
106
+ * Row 2's rhythm is 8px · pills · 8px, NOT a 40px reserved row.
107
+ *
108
+ * `min-height: var(--vx-space-page-bar-row-height)` (40px) left a 30px pill floating inside a 40px
109
+ * box with the 5px above and below it belonging to nothing — and because the row sat in a `Stack`
110
+ * that already spaced it, the visible gap between the header and the pills read as ~19px while the
111
+ * gap from the pills to the first card read as ~19px too. Symmetric padding on the row itself is the
112
+ * same total height (46px vs 40px) with both halves owned by one declaration, so the sticky bar's
113
+ * published height covers exactly the band it paints.
114
+ */
115
+ .row2 {
116
+ display: flex;
117
+ flex-wrap: nowrap;
118
+ align-items: center;
119
+ gap: var(--vx-space-control-gap);
120
+ min-width: 0;
121
+ padding-block: var(--vx-space-stack-sm);
122
+ }
123
+
124
+ .filters {
125
+ display: flex;
126
+ flex-wrap: nowrap;
127
+ align-items: center;
128
+ gap: var(--vx-space-control-gap);
129
+ flex: 1 1 auto;
130
+ min-width: 0;
131
+ }
132
+
133
+ .filtersEnd {
134
+ display: flex;
135
+ flex-wrap: nowrap;
136
+ align-items: center;
137
+ gap: var(--vx-space-control-gap);
138
+ margin-inline-start: auto;
139
+ flex-shrink: 0;
140
+ }
141
+
142
+ /*
143
+ * Law C15 — every touch target inside a home is at least `touchControlHeight` (36px at level 0)
144
+ * below `sm`, while the VISIBLE box stays the 30px `ctl` tier. `::after` and not `::before`:
145
+ * Mantine's Button owns `::before` for its loading overlay (`@mantine/core/styles/Button.css`),
146
+ * so a hit area drawn there would fight it. `pointer-events` stays default so the overlay is the
147
+ * one receiving the tap.
148
+ *
149
+ * `overflow: visible` is LOAD-BEARING, not tidying. Mantine's Button and ActionIcon roots both set
150
+ * `overflow: hidden` (`Button.css`, `ActionIcon.css`), which clips an overlay that by definition
151
+ * extends past the 30px box — so without this the 36px target simply does not exist and a tap 3px
152
+ * above the control misses. What `overflow: hidden` buys those roots is clipping the loading
153
+ * overlay's blur, and that overlay is only painted under `[data-loading]`, which is excluded below.
154
+ */
155
+ @media (max-width: 47.99375em) {
156
+ .row1 :is(button, a),
157
+ .row2 :is(button, a) {
158
+ position: relative;
159
+ }
160
+
161
+ .row1 :is(button, a):not([data-loading]),
162
+ .row2 :is(button, a):not([data-loading]) {
163
+ overflow: visible;
164
+ }
165
+
166
+ .row1 :is(button, a)::after,
167
+ .row2 :is(button, a)::after {
168
+ content: '';
169
+ position: absolute;
170
+ top: 50%;
171
+ left: 50%;
172
+ translate: -50% -50%;
173
+ width: 100%;
174
+ height: 100%;
175
+ min-width: var(--vx-space-touch-control-height);
176
+ min-height: var(--vx-space-touch-control-height);
177
+ }
178
+ }
@@ -0,0 +1,17 @@
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 bar: string
7
+ readonly row2Sticky: string
8
+ readonly row1: string
9
+ readonly lead: string
10
+ readonly icon: string
11
+ readonly title: string
12
+ readonly row2: string
13
+ readonly row1End: string
14
+ readonly filters: string
15
+ readonly filtersEnd: string
16
+ }
17
+ export default classes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=page-bar.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-bar.test.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.test.tsx"],"names":[],"mappings":""}