basalt-ui 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +26 -0
  3. package/MIGRATING.md +815 -23
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +160 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +118 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1465 -36
  17. package/configs/oxlint-plugin.test.ts +1190 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +39 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +93 -0
  75. package/dist/controls/filter-context.d.ts +60 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +20 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +46 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/number-filter.d.ts +26 -0
  99. package/dist/controls/number-filter.d.ts.map +1 -0
  100. package/dist/controls/number-filter.js +143 -0
  101. package/dist/controls/peer-boundary.test.d.ts +2 -0
  102. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  103. package/dist/controls/props-contract.test.d.ts +2 -0
  104. package/dist/controls/props-contract.test.d.ts.map +1 -0
  105. package/dist/controls/range-filter.d.ts +43 -0
  106. package/dist/controls/range-filter.d.ts.map +1 -0
  107. package/dist/controls/range-filter.js +135 -0
  108. package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
  109. package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -0
  110. package/dist/controls/search-filter.d.ts +10 -0
  111. package/dist/controls/search-filter.d.ts.map +1 -0
  112. package/dist/controls/search-filter.js +81 -0
  113. package/dist/controls/select-filter.d.ts +74 -0
  114. package/dist/controls/select-filter.d.ts.map +1 -0
  115. package/dist/controls/select-filter.js +10 -0
  116. package/dist/controls/sync-button.d.ts +38 -0
  117. package/dist/controls/sync-button.d.ts.map +1 -0
  118. package/dist/controls/sync-button.js +119 -0
  119. package/dist/controls/sync-button.module.css +63 -0
  120. package/dist/controls/sync-button.module.css.d.ts +11 -0
  121. package/dist/controls/sync-button.test.d.ts +2 -0
  122. package/dist/controls/sync-button.test.d.ts.map +1 -0
  123. package/dist/controls/toggle-filter.d.ts +9 -0
  124. package/dist/controls/toggle-filter.d.ts.map +1 -0
  125. package/dist/controls/toggle-filter.js +42 -0
  126. package/dist/controls/view-tabs.d.ts +23 -0
  127. package/dist/controls/view-tabs.d.ts.map +1 -0
  128. package/dist/controls/view-tabs.js +59 -0
  129. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  130. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  131. package/dist/controls-dates/date-range-picker.js +18 -0
  132. package/dist/controls-dates/index.d.ts +9 -0
  133. package/dist/controls-dates/index.d.ts.map +1 -0
  134. package/dist/controls-dates/index.js +4 -0
  135. package/dist/dashboard/delta-badge.d.ts +6 -12
  136. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  137. package/dist/dashboard/delta-badge.js +1 -50
  138. package/dist/dashboard/index.d.ts +1 -1
  139. package/dist/dashboard/index.d.ts.map +1 -1
  140. package/dist/dashboard/index.js +3 -1
  141. package/dist/dashboard/settings-section.d.ts +6 -4
  142. package/dist/dashboard/settings-section.d.ts.map +1 -1
  143. package/dist/dashboard/settings-section.js +23 -16
  144. package/dist/dashboard/settings-section.module.css +10 -0
  145. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  146. package/dist/dashboard/settings-section.test.d.ts +2 -0
  147. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  148. package/dist/dashboard/stat-card.d.ts +95 -9
  149. package/dist/dashboard/stat-card.d.ts.map +1 -1
  150. package/dist/dashboard/stat-card.js +108 -49
  151. package/dist/dashboard/stat-card.module.css +153 -0
  152. package/dist/dashboard/stat-card.module.css.d.ts +15 -0
  153. package/dist/data/data-table.d.ts +37 -15
  154. package/dist/data/data-table.d.ts.map +1 -1
  155. package/dist/data/data-table.js +96 -58
  156. package/dist/guard/grace.test.d.ts +20 -0
  157. package/dist/guard/grace.test.d.ts.map +1 -0
  158. package/dist/guard/index.d.ts +66 -3
  159. package/dist/guard/index.d.ts.map +1 -1
  160. package/dist/guard/index.js +78 -7
  161. package/dist/guard/types.d.ts +3 -3
  162. package/dist/guard/types.d.ts.map +1 -1
  163. package/dist/index.d.ts +13 -3
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js +13 -10
  166. package/dist/nav/types.d.ts +78 -2
  167. package/dist/nav/types.d.ts.map +1 -1
  168. package/dist/notifications/bell.d.ts +4 -1
  169. package/dist/notifications/bell.d.ts.map +1 -1
  170. package/dist/notifications/index.d.ts +1 -1
  171. package/dist/router-tanstack/field.d.ts +14 -0
  172. package/dist/router-tanstack/field.d.ts.map +1 -0
  173. package/dist/router-tanstack/field.js +4 -0
  174. package/dist/router-tanstack/index.d.ts +6 -2
  175. package/dist/router-tanstack/index.d.ts.map +1 -1
  176. package/dist/router-tanstack/index.js +4 -0
  177. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  178. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  179. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  180. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  181. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  182. package/dist/router-tanstack/search-param-store.js +16 -45
  183. package/dist/router-tanstack/search-store.d.ts +84 -0
  184. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  185. package/dist/router-tanstack/search-store.js +271 -0
  186. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  187. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  188. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  189. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  190. package/dist/router-tanstack/use-nav.d.ts +0 -2
  191. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  192. package/dist/router-tanstack/use-nav.js +34 -12
  193. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  194. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  195. package/dist/section/index.d.ts +2 -0
  196. package/dist/section/index.d.ts.map +1 -0
  197. package/dist/section/index.js +4 -0
  198. package/dist/section/section.d.ts +24 -0
  199. package/dist/section/section.d.ts.map +1 -0
  200. package/dist/section/section.js +126 -0
  201. package/dist/section/section.module.css +104 -0
  202. package/dist/section/section.module.css.d.ts +11 -0
  203. package/dist/section/section.test.d.ts +2 -0
  204. package/dist/section/section.test.d.ts.map +1 -0
  205. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  206. package/dist/shell/app-breadcrumbs.js +18 -15
  207. package/dist/shell/app-header.module.css +77 -37
  208. package/dist/shell/app-header.module.css.d.ts +2 -1
  209. package/dist/shell/app-mobile-nav.d.ts +9 -2
  210. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  211. package/dist/shell/app-mobile-nav.js +98 -11
  212. package/dist/shell/app-mobile-nav.module.css +8 -0
  213. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  214. package/dist/shell/app-sidebar.d.ts +55 -28
  215. package/dist/shell/app-sidebar.d.ts.map +1 -1
  216. package/dist/shell/app-sidebar.js +116 -67
  217. package/dist/shell/app-sidebar.module.css +303 -20
  218. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  219. package/dist/shell/index.d.ts +55 -21
  220. package/dist/shell/index.d.ts.map +1 -1
  221. package/dist/shell/index.js +40 -18
  222. package/dist/shell/mobile-nav-model.d.ts +13 -2
  223. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  224. package/dist/shell/mobile-nav-model.js +15 -1
  225. package/dist/shell/nav-count-badge.d.ts +1 -1
  226. package/dist/shell/page-bar.d.ts +51 -0
  227. package/dist/shell/page-bar.d.ts.map +1 -0
  228. package/dist/shell/page-bar.js +154 -0
  229. package/dist/shell/page-bar.module.css +178 -0
  230. package/dist/shell/page-bar.module.css.d.ts +17 -0
  231. package/dist/shell/page-bar.test.d.ts +2 -0
  232. package/dist/shell/page-bar.test.d.ts.map +1 -0
  233. package/dist/shell/sidebar-block-model.d.ts +48 -0
  234. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  235. package/dist/shell/sidebar-block-model.js +39 -0
  236. package/dist/shell/sidebar-blocks.d.ts +32 -0
  237. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  238. package/dist/shell/sidebar-blocks.js +169 -0
  239. package/dist/shell/sidebar-search.d.ts +13 -1
  240. package/dist/shell/sidebar-search.d.ts.map +1 -1
  241. package/dist/shell/sidebar-search.js +34 -1
  242. package/dist/shell/sidebar-search.module.css +26 -0
  243. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  244. package/dist/state/fields.d.ts +565 -0
  245. package/dist/state/fields.d.ts.map +1 -0
  246. package/dist/state/fields.js +405 -0
  247. package/dist/state/fields.test.d.ts +2 -0
  248. package/dist/state/fields.test.d.ts.map +1 -0
  249. package/dist/state/persisted.d.ts +60 -0
  250. package/dist/state/persisted.d.ts.map +1 -0
  251. package/dist/state/persisted.js +123 -0
  252. package/dist/state/persisted.test.d.ts +2 -0
  253. package/dist/state/persisted.test.d.ts.map +1 -0
  254. package/dist/state.d.ts +11 -56
  255. package/dist/state.d.ts.map +1 -1
  256. package/dist/state.js +14 -120
  257. package/dist/styles.css +13 -0
  258. package/dist/surfaces.d.ts +106 -24
  259. package/dist/surfaces.d.ts.map +1 -1
  260. package/dist/surfaces.js +156 -19
  261. package/dist/theme/controls.module.css +16 -1
  262. package/dist/theme/ctl-theme.d.ts +32 -0
  263. package/dist/theme/ctl-theme.d.ts.map +1 -0
  264. package/dist/theme/ctl-theme.js +54 -0
  265. package/dist/theme/ctl-theme.test.d.ts +2 -0
  266. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  267. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  268. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  269. package/dist/theme/divider-law.test.d.ts +2 -0
  270. package/dist/theme/divider-law.test.d.ts.map +1 -0
  271. package/dist/theme/icon-slot.d.ts +44 -0
  272. package/dist/theme/icon-slot.d.ts.map +1 -0
  273. package/dist/theme/icon-slot.js +21 -0
  274. package/dist/theme/icon-slot.module.css +47 -0
  275. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  276. package/dist/theme/icon-slot.test.d.ts +2 -0
  277. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  278. package/dist/theme/index.d.ts +1 -0
  279. package/dist/theme/index.d.ts.map +1 -1
  280. package/dist/theme/index.js +82 -5
  281. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  282. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  283. package/dist/theme/segmented-control.module.css +20 -0
  284. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  285. package/dist/tokens/index.d.ts +12 -0
  286. package/dist/tokens/index.d.ts.map +1 -1
  287. package/dist/tokens/index.js +38 -8
  288. package/dist/tokens/palette.d.ts +97 -61
  289. package/dist/tokens/palette.d.ts.map +1 -1
  290. package/dist/tokens/palette.js +76 -38
  291. package/dist/tokens.css +10 -2
  292. package/dist/widget-header/delta-badge.d.ts +13 -0
  293. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  294. package/dist/widget-header/delta-badge.js +29 -0
  295. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  296. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  297. package/dist/widget-header/index.d.ts +3 -0
  298. package/dist/widget-header/index.d.ts.map +1 -0
  299. package/dist/widget-header/index.js +6 -0
  300. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  301. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  302. package/dist/widget-header/widget-header.d.ts +67 -0
  303. package/dist/widget-header/widget-header.d.ts.map +1 -0
  304. package/dist/widget-header/widget-header.js +89 -0
  305. package/dist/widget-header/widget-header.module.css +350 -0
  306. package/dist/widget-header/widget-header.module.css.d.ts +25 -0
  307. package/dist/widget-header/widget-header.test.d.ts +2 -0
  308. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  309. package/llms.txt +30 -17
  310. package/package.json +14 -1
  311. package/src/charts/cursor/resolve.test.ts +29 -1
  312. package/src/charts/cursor/resolve.ts +38 -6
  313. package/src/charts/cursor/scope.tsx +6 -3
  314. package/src/charts/cursor/store.test.ts +19 -1
  315. package/src/charts/cursor/store.ts +10 -5
  316. package/src/charts/hooks/useChartCursor.ts +16 -8
  317. package/src/charts/index.ts +2 -2
  318. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  319. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  320. package/src/charts/primitives/ChartCard.tsx +86 -146
  321. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  322. package/src/cli/check-coverage.test.ts +297 -0
  323. package/src/cli/check-theme.test.ts +38 -14
  324. package/src/cli/index.ts +392 -16
  325. package/src/cli/init-sync.test.ts +97 -0
  326. package/src/connectivity/connectivity-indicator.tsx +16 -9
  327. package/src/content/index.ts +16 -3
  328. package/src/content/prose.module.css +7 -28
  329. package/src/content/prose.module.css.test.ts +8 -24
  330. package/src/controls/actions.module.css +62 -0
  331. package/src/controls/actions.module.css.d.ts +13 -0
  332. package/src/controls/actions.test.tsx +252 -0
  333. package/src/controls/actions.tsx +655 -0
  334. package/src/controls/actions.type-guard.test.ts +63 -0
  335. package/src/controls/compare-filter.tsx +64 -0
  336. package/src/controls/control-group.test.tsx +229 -0
  337. package/src/controls/control-group.tsx +63 -0
  338. package/src/controls/controls.module.css +542 -0
  339. package/src/controls/controls.module.css.d.ts +35 -0
  340. package/src/controls/controls.router.test.tsx +1053 -0
  341. package/src/controls/enum-filter.tsx +170 -0
  342. package/src/controls/filter-context.tsx +158 -0
  343. package/src/controls/filter-pill.tsx +120 -0
  344. package/src/controls/filter-set.test.tsx +472 -0
  345. package/src/controls/filter-set.tsx +225 -0
  346. package/src/controls/filter-sheet.tsx +285 -0
  347. package/src/controls/glyphs.tsx +81 -0
  348. package/src/controls/index.ts +49 -0
  349. package/src/controls/multi-select-filter.tsx +140 -0
  350. package/src/controls/number-filter.tsx +291 -0
  351. package/src/controls/peer-boundary.test.ts +85 -0
  352. package/src/controls/props-contract.test.tsx +137 -0
  353. package/src/controls/range-filter.tsx +237 -0
  354. package/src/controls/range-filter.type-guard.test.tsx +122 -0
  355. package/src/controls/search-filter.tsx +123 -0
  356. package/src/controls/select-filter.tsx +89 -0
  357. package/src/controls/sync-button.module.css +63 -0
  358. package/src/controls/sync-button.module.css.d.ts +11 -0
  359. package/src/controls/sync-button.test.tsx +183 -0
  360. package/src/controls/sync-button.tsx +197 -0
  361. package/src/controls/toggle-filter.tsx +60 -0
  362. package/src/controls/view-tabs.tsx +110 -0
  363. package/src/controls-dates/date-range-picker.tsx +42 -0
  364. package/src/controls-dates/index.ts +8 -0
  365. package/src/dashboard/delta-badge.tsx +4 -74
  366. package/src/dashboard/index.ts +6 -1
  367. package/src/dashboard/settings-section.module.css +10 -0
  368. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  369. package/src/dashboard/settings-section.test.tsx +56 -0
  370. package/src/dashboard/settings-section.tsx +33 -30
  371. package/src/dashboard/stat-card.module.css +153 -0
  372. package/src/dashboard/stat-card.module.css.d.ts +15 -0
  373. package/src/dashboard/stat-card.test.tsx +298 -1
  374. package/src/dashboard/stat-card.tsx +289 -67
  375. package/src/data/data-table.test.tsx +96 -11
  376. package/src/data/data-table.tsx +228 -84
  377. package/src/guard/check-source.test.ts +159 -13
  378. package/src/guard/grace.test.ts +158 -0
  379. package/src/guard/index.ts +185 -29
  380. package/src/guard/types.ts +4 -2
  381. package/src/index.ts +51 -5
  382. package/src/nav/types.ts +84 -2
  383. package/src/notifications/bell.tsx +6 -3
  384. package/src/notifications/index.ts +1 -1
  385. package/src/router-tanstack/field.ts +35 -0
  386. package/src/router-tanstack/index.ts +28 -2
  387. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  388. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  389. package/src/router-tanstack/search-param-store.ts +42 -159
  390. package/src/router-tanstack/search-store.router.test.tsx +882 -0
  391. package/src/router-tanstack/search-store.test.ts +578 -0
  392. package/src/router-tanstack/search-store.ts +546 -0
  393. package/src/router-tanstack/use-nav.test.tsx +187 -0
  394. package/src/router-tanstack/use-nav.ts +72 -14
  395. package/src/section/index.ts +1 -0
  396. package/src/section/section.module.css +104 -0
  397. package/src/section/section.module.css.d.ts +11 -0
  398. package/src/section/section.test.tsx +204 -0
  399. package/src/section/section.tsx +220 -0
  400. package/src/shell/app-breadcrumbs.tsx +44 -31
  401. package/src/shell/app-header.module.css +77 -37
  402. package/src/shell/app-header.module.css.d.ts +2 -1
  403. package/src/shell/app-mobile-nav.module.css +8 -0
  404. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  405. package/src/shell/app-mobile-nav.tsx +147 -8
  406. package/src/shell/app-sidebar-account.tsx +2 -2
  407. package/src/shell/app-sidebar.module.css +303 -20
  408. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  409. package/src/shell/app-sidebar.test.tsx +456 -41
  410. package/src/shell/app-sidebar.tsx +276 -140
  411. package/src/shell/index.test.tsx +291 -5
  412. package/src/shell/index.tsx +137 -38
  413. package/src/shell/mobile-nav-model.test.ts +228 -7
  414. package/src/shell/mobile-nav-model.ts +53 -3
  415. package/src/shell/nav-count-badge.tsx +1 -1
  416. package/src/shell/page-bar.module.css +178 -0
  417. package/src/shell/page-bar.module.css.d.ts +17 -0
  418. package/src/shell/page-bar.test.tsx +316 -0
  419. package/src/shell/page-bar.tsx +315 -0
  420. package/src/shell/sidebar-block-model.ts +83 -0
  421. package/src/shell/sidebar-blocks.tsx +301 -0
  422. package/src/shell/sidebar-search.module.css +26 -0
  423. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  424. package/src/shell/sidebar-search.tsx +71 -2
  425. package/src/state/fields.test.tsx +512 -0
  426. package/src/state/fields.ts +1150 -0
  427. package/src/state/persisted.test.ts +91 -0
  428. package/src/state/persisted.ts +261 -0
  429. package/src/state.ts +52 -240
  430. package/src/styles.css +13 -0
  431. package/src/surfaces.ts +204 -36
  432. package/src/theme/controls.module.css +16 -1
  433. package/src/theme/ctl-theme.test.tsx +275 -0
  434. package/src/theme/ctl-theme.tsx +138 -0
  435. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  436. package/src/theme/density-relations.test.ts +106 -51
  437. package/src/theme/divider-law.test.ts +259 -0
  438. package/src/theme/icon-slot.module.css +47 -0
  439. package/src/theme/icon-slot.module.css.d.ts +8 -0
  440. package/src/theme/icon-slot.test.tsx +275 -0
  441. package/src/theme/icon-slot.tsx +59 -0
  442. package/src/theme/index.ts +165 -3
  443. package/src/theme/layout-rhythm-css.test.ts +72 -0
  444. package/src/theme/radius.test.ts +11 -1
  445. package/src/theme/segmented-control.module.css +20 -0
  446. package/src/theme/shadow-surfaces.test.ts +20 -7
  447. package/src/theme/spacing.test.ts +75 -27
  448. package/src/theme/use-basalt-spacing.ts +2 -2
  449. package/src/tokens/density.test.ts +7 -25
  450. package/src/tokens/index.ts +48 -8
  451. package/src/tokens/palette.ts +107 -73
  452. package/src/widget-header/delta-badge.test.tsx +60 -0
  453. package/src/widget-header/delta-badge.tsx +57 -0
  454. package/src/widget-header/index.ts +2 -0
  455. package/src/widget-header/no-mantine.test.ts +33 -0
  456. package/src/widget-header/widget-header.module.css +350 -0
  457. package/src/widget-header/widget-header.module.css.d.ts +25 -0
  458. package/src/widget-header/widget-header.test.tsx +187 -0
  459. package/src/widget-header/widget-header.tsx +213 -0
  460. package/agent/rules/basalt-agent.md +0 -452
  461. package/agent/rules/basalt-app.md +0 -246
  462. package/agent/rules/basalt-commands.md +0 -320
  463. package/agent/rules/basalt-content.md +0 -409
  464. package/agent/rules/basalt-data.md +0 -329
  465. package/agent/rules/basalt-forms.md +0 -186
  466. package/agent/rules/basalt-notifications.md +0 -264
  467. package/agent/rules/basalt-query.md +0 -187
  468. package/agent/rules/basalt-router.md +0 -336
  469. package/dist/content/article-filter-bar.d.ts +0 -40
  470. package/dist/content/article-filter-bar.d.ts.map +0 -1
  471. package/dist/content/article-filter-bar.js +0 -56
  472. package/dist/shell/page-header.d.ts +0 -13
  473. package/dist/shell/page-header.d.ts.map +0 -1
  474. package/dist/shell/page-header.js +0 -23
  475. package/src/content/article-filter-bar.tsx +0 -88
  476. package/src/shell/page-header.tsx +0 -35
@@ -10,6 +10,7 @@ declare const classes: {
10
10
  readonly brand: string
11
11
  readonly brandLead: string
12
12
  readonly brandName: string
13
+ readonly brandButton: string
13
14
  readonly searchSlot: string
14
15
  readonly navScroll: string
15
16
  readonly navViewport: string
@@ -27,7 +28,24 @@ declare const classes: {
27
28
  readonly sectionHeader: string
28
29
  readonly subnavDropdown: string
29
30
  readonly childList: string
30
- readonly navExtra: string
31
31
  readonly ghostIcon: string
32
+ readonly block: string
33
+ readonly blockHeader: string
34
+ readonly blockHeaderButton: string
35
+ readonly blockIcon: string
36
+ readonly blockDot: string
37
+ readonly blockItems: string
38
+ readonly blockRow: string
39
+ readonly blockRowStatic: string
40
+ readonly blockRowLabel: string
41
+ readonly blockRowMeta: string
42
+ readonly blockToneDot: string
43
+ readonly blockMore: string
44
+ readonly blockProgress: string
45
+ readonly blockProgressHead: string
46
+ readonly blockRing: string
47
+ readonly footerLinks: string
48
+ readonly footerIconSlot: string
49
+ readonly footerVersion: string
32
50
  }
33
51
  export default classes
@@ -1,22 +1,23 @@
1
1
  import type { MouseEvent, ReactNode } from 'react';
2
- import type { BasaltAccountProps } from './account-types.js';
3
- import type { SidebarSearchConfig } from './sidebar-search.js';
4
- import type { MobileNavConfig, SidebarSection } from '../nav/types.js';
2
+ import type { GlobalAction } from '../controls/actions.js';
3
+ import type { AccountMenuItem, BasaltAccountProps } from './account-types.js';
4
+ import type { SidebarSearchActions, SidebarSearchConfig } from './sidebar-search.js';
5
+ import type { MobileNavConfig, SidebarBlock, SidebarSection } from '../nav/types.js';
5
6
  export { AppSidebar, type AppSidebarProps } from './app-sidebar.js';
6
7
  export { NavCountBadge } from './nav-count-badge.js';
7
- export { SidebarSearch, type SidebarSearchConfig } from './sidebar-search.js';
8
+ export { SidebarSearch, type SidebarSearchConfig, type SidebarSearchActions, } from './sidebar-search.js';
8
9
  export { SidebarAccount } from './app-sidebar-account.js';
9
10
  export type { AccountBadgeTone, BasaltIdentity, BasaltRole, BasaltPlan, AccountMenuItem, BasaltAccountState, BasaltAccountActions, BasaltAccountProps, } from './account-types.js';
10
11
  export { MobileNav, type MobileNavProps } from './app-mobile-nav.js';
11
12
  export { projectMobileNav, type ProjectMobileNavOptions, MOBILE_MAX_TABS_DEFAULT, MOBILE_MENU_MAX_DEFAULT, MOBILE_MORE_KEY, } from './mobile-nav-model.js';
12
13
  export { AppBreadcrumbs } from './app-breadcrumbs.js';
13
- export { PageHeaderProvider, PageActions, PageActionsOutlet } from './page-header.js';
14
+ export { PageBar, type PageBarProps } from './page-bar.js';
14
15
  /**
15
16
  * The shared nav vocabulary lives in `src/nav/types.ts` so the headless router bridge can `import
16
17
  * type` it without reaching into the Mantine layer. Re-exported here so `SidebarItem` /
17
18
  * `SidebarSection` keep resolving straight from `basalt-ui`, exactly as before.
18
19
  */
19
- export type { NavAnchor, NavAnchorProps, NavMobilePlacement, NavSectionMobile, SidebarItem, SidebarSection, MobileNavConfig, MobileNavGroup, MobileNavModel, MobileNavSlot, MobileNavSurface, } from '../nav/types.js';
20
+ export type { NavAnchor, NavAnchorProps, NavMobilePlacement, NavSectionMobile, SidebarItem, SidebarSection, SidebarBlock, SidebarBlockItem, SidebarBlockTone, SidebarListBlock, SidebarProgressBlock, SidebarCustomBlock, MobileNavConfig, MobileNavGroup, MobileNavModel, MobileNavSlot, MobileNavSurface, } from '../nav/types.js';
20
21
  /** Brand identity shown in the sidebar header (logo + name). */
21
22
  export type BrandConfig = {
22
23
  /** Display name (e.g. "Argo"). */
@@ -36,8 +37,13 @@ export type SettingsMenuItem = {
36
37
  onClick?: (e: MouseEvent) => void;
37
38
  };
38
39
  export type BasaltShellProps = {
39
- /** Brand identity for the sidebar header. */
40
- brand: BrandConfig;
40
+ /**
41
+ * Brand identity for the sidebar header. Supplying `menu` turns the brand row into a `Name ▾`
42
+ * workspace switcher — the rows are `AccountMenuItem`s, the shape the account menu already uses.
43
+ */
44
+ brand: BrandConfig & {
45
+ menu?: AccountMenuItem[];
46
+ };
41
47
  /** Grouped nav sections — the ONE definition, rendered as the sidebar and projected to the bar. */
42
48
  sections: SidebarSection[];
43
49
  /**
@@ -45,28 +51,56 @@ export type BasaltShellProps = {
45
51
  * configured still produces a working bar (see `projectMobileNav`'s zero-config fallback).
46
52
  */
47
53
  mobileNav?: MobileNavConfig;
48
- /** Persistent, shell-owned top-bar slot (timer, refresh, notifications). */
49
- globalActions?: ReactNode;
50
54
  /**
51
- * DESKTOP ONLY. Arbitrary content appended after `sections` inside the sidebar's nav scroll
52
- * region (a tree, a filter panel, a project list, …), for anything a set of `SidebarItem`s can't
53
- * express. Hidden on the collapsed desktop rail; the sidebar itself no longer exists below the
54
- * `sm` breakpoint, so this slot has no mobile representation — put mobile-reachable extras in
55
- * `mobileNav.moreExtra` instead.
55
+ * Persistent, shell-owned header actions (timer, notifications, a global `SyncButton`) — DECLARED
56
+ * DATA, not a `ReactNode` slot, so basalt owns the mobile projection: the first two ride the bar,
57
+ * the rest fold into the header's single kebab (`mobile: 'bar' | 'more' | 'hidden'`, see
58
+ * `GlobalAction`). That kebab is the SAME one `PageBar`'s ROW 1 opens — a route whose bar has
59
+ * row-1 actions (or `filtersEnd`) lends it, any other route gets it from the shell. An
60
+ * `ActionGroup` a consumer mounts in some other home never inherits these rows.
61
+ */
62
+ globalActions?: GlobalAction[];
63
+ /**
64
+ * Sidebar blocks — DECLARED DATA (law C13, `docs/CONTROLS-SPEC.md` §2.3), which replaced both
65
+ * `sidebarNavExtra` and `mobileNav.moreExtra`. `placement: 'nav'` (the default for `list` and
66
+ * `custom`) appends after the nav sections inside the scroll region; `'bottom'` pins above the
67
+ * settings footer.
68
+ *
69
+ * Because they are data, basalt owns the projections the two `ReactNode` slots could not express:
70
+ * a `list` with a `count` becomes a dot on its icon in the collapsed rail, a `progress` block
71
+ * becomes a ring on the settings row, and a block with `mobile: 'more'` becomes one More-sheet
72
+ * row opening a nested sheet of its items. A `kind: 'custom'` block is desktop-only, exactly as
73
+ * `sidebarNavExtra` was.
74
+ */
75
+ sidebarBlocks?: SidebarBlock[];
76
+ /**
77
+ * Entries appended to the sidebar settings footer — flat link rows at three or fewer, one gear
78
+ * menu at four or more (`AppSidebarProps.settingsMenuItems` documents the threshold).
56
79
  */
57
- sidebarNavExtra?: ReactNode;
58
- /** Entries appended to the sidebar settings menu. */
59
80
  settingsMenuItems?: SettingsMenuItem[];
60
81
  /**
61
- * Optional account row rendered above the settings menu in the sidebar footer (see
82
+ * Forces the footer form instead of taking the count rule above: `'flat'` always renders link
83
+ * rows, `'menu'` always renders the gear dropdown. `'auto'` (the default) is today's ≤3 → flat
84
+ * behaviour. Reach for it when the rows are CONTROLS rather than destinations — three of those
85
+ * (a theme radio group, a devtools switch) read as a widget pile in the footer even though the
86
+ * count says flat. Full rationale on `AppSidebarProps.settingsMenu`.
87
+ *
88
+ * @default 'auto'
89
+ */
90
+ settingsMenu?: 'auto' | 'flat' | 'menu';
91
+ /**
92
+ * Optional account row rendered below the settings menu in the sidebar footer (see
62
93
  * `SidebarAccount` / `BasaltAccountProps`). Omitting it reproduces today's footer unchanged.
63
94
  */
64
95
  account?: BasaltAccountProps;
65
96
  /**
66
97
  * Optional search field below the brand in the sidebar (fixed, above the nav scroll). Supply
67
- * `onOpen`, e.g. `() => openSpotlight()` from basalt-ui/commands.
98
+ * `onOpen`, e.g. `() => openSpotlight()` from basalt-ui/commands. `actions` adds one or two
99
+ * icon-only buttons to the right of that row (a tuple by type — see `SidebarSearchActions`).
68
100
  */
69
- search?: SidebarSearchConfig;
101
+ search?: SidebarSearchConfig & {
102
+ actions?: SidebarSearchActions;
103
+ };
70
104
  /**
71
105
  * localStorage key for the persisted sidebar-collapsed flag. Ignored when `collapsed` is set.
72
106
  *
@@ -92,5 +126,5 @@ export type BasaltShellProps = {
92
126
  /** Page content. */
93
127
  children?: ReactNode;
94
128
  };
95
- export declare function BasaltShell({ brand, sections, mobileNav, globalActions, sidebarNavExtra, settingsMenuItems, storageKey, collapsed: collapsedProp, onCollapsedChange, account, search, children, }: BasaltShellProps): import("react").JSX.Element;
129
+ export declare function BasaltShell({ brand, sections, mobileNav, globalActions, sidebarBlocks, settingsMenuItems, settingsMenu, storageKey, collapsed: collapsedProp, onCollapsedChange, account, search, children, }: BasaltShellProps): import("react").JSX.Element;
96
130
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"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"}
@@ -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-surface-hairline)`).
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
+ };