basalt-ui 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +26 -0
  3. package/MIGRATING.md +815 -23
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +160 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +118 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1465 -36
  17. package/configs/oxlint-plugin.test.ts +1190 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +39 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +93 -0
  75. package/dist/controls/filter-context.d.ts +60 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +20 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +46 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/number-filter.d.ts +26 -0
  99. package/dist/controls/number-filter.d.ts.map +1 -0
  100. package/dist/controls/number-filter.js +143 -0
  101. package/dist/controls/peer-boundary.test.d.ts +2 -0
  102. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  103. package/dist/controls/props-contract.test.d.ts +2 -0
  104. package/dist/controls/props-contract.test.d.ts.map +1 -0
  105. package/dist/controls/range-filter.d.ts +43 -0
  106. package/dist/controls/range-filter.d.ts.map +1 -0
  107. package/dist/controls/range-filter.js +135 -0
  108. package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
  109. package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -0
  110. package/dist/controls/search-filter.d.ts +10 -0
  111. package/dist/controls/search-filter.d.ts.map +1 -0
  112. package/dist/controls/search-filter.js +81 -0
  113. package/dist/controls/select-filter.d.ts +74 -0
  114. package/dist/controls/select-filter.d.ts.map +1 -0
  115. package/dist/controls/select-filter.js +10 -0
  116. package/dist/controls/sync-button.d.ts +38 -0
  117. package/dist/controls/sync-button.d.ts.map +1 -0
  118. package/dist/controls/sync-button.js +119 -0
  119. package/dist/controls/sync-button.module.css +63 -0
  120. package/dist/controls/sync-button.module.css.d.ts +11 -0
  121. package/dist/controls/sync-button.test.d.ts +2 -0
  122. package/dist/controls/sync-button.test.d.ts.map +1 -0
  123. package/dist/controls/toggle-filter.d.ts +9 -0
  124. package/dist/controls/toggle-filter.d.ts.map +1 -0
  125. package/dist/controls/toggle-filter.js +42 -0
  126. package/dist/controls/view-tabs.d.ts +23 -0
  127. package/dist/controls/view-tabs.d.ts.map +1 -0
  128. package/dist/controls/view-tabs.js +59 -0
  129. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  130. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  131. package/dist/controls-dates/date-range-picker.js +18 -0
  132. package/dist/controls-dates/index.d.ts +9 -0
  133. package/dist/controls-dates/index.d.ts.map +1 -0
  134. package/dist/controls-dates/index.js +4 -0
  135. package/dist/dashboard/delta-badge.d.ts +6 -12
  136. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  137. package/dist/dashboard/delta-badge.js +1 -50
  138. package/dist/dashboard/index.d.ts +1 -1
  139. package/dist/dashboard/index.d.ts.map +1 -1
  140. package/dist/dashboard/index.js +3 -1
  141. package/dist/dashboard/settings-section.d.ts +6 -4
  142. package/dist/dashboard/settings-section.d.ts.map +1 -1
  143. package/dist/dashboard/settings-section.js +23 -16
  144. package/dist/dashboard/settings-section.module.css +10 -0
  145. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  146. package/dist/dashboard/settings-section.test.d.ts +2 -0
  147. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  148. package/dist/dashboard/stat-card.d.ts +95 -9
  149. package/dist/dashboard/stat-card.d.ts.map +1 -1
  150. package/dist/dashboard/stat-card.js +108 -49
  151. package/dist/dashboard/stat-card.module.css +153 -0
  152. package/dist/dashboard/stat-card.module.css.d.ts +15 -0
  153. package/dist/data/data-table.d.ts +37 -15
  154. package/dist/data/data-table.d.ts.map +1 -1
  155. package/dist/data/data-table.js +96 -58
  156. package/dist/guard/grace.test.d.ts +20 -0
  157. package/dist/guard/grace.test.d.ts.map +1 -0
  158. package/dist/guard/index.d.ts +66 -3
  159. package/dist/guard/index.d.ts.map +1 -1
  160. package/dist/guard/index.js +78 -7
  161. package/dist/guard/types.d.ts +3 -3
  162. package/dist/guard/types.d.ts.map +1 -1
  163. package/dist/index.d.ts +13 -3
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js +13 -10
  166. package/dist/nav/types.d.ts +78 -2
  167. package/dist/nav/types.d.ts.map +1 -1
  168. package/dist/notifications/bell.d.ts +4 -1
  169. package/dist/notifications/bell.d.ts.map +1 -1
  170. package/dist/notifications/index.d.ts +1 -1
  171. package/dist/router-tanstack/field.d.ts +14 -0
  172. package/dist/router-tanstack/field.d.ts.map +1 -0
  173. package/dist/router-tanstack/field.js +4 -0
  174. package/dist/router-tanstack/index.d.ts +6 -2
  175. package/dist/router-tanstack/index.d.ts.map +1 -1
  176. package/dist/router-tanstack/index.js +4 -0
  177. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  178. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  179. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  180. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  181. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  182. package/dist/router-tanstack/search-param-store.js +16 -45
  183. package/dist/router-tanstack/search-store.d.ts +84 -0
  184. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  185. package/dist/router-tanstack/search-store.js +271 -0
  186. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  187. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  188. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  189. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  190. package/dist/router-tanstack/use-nav.d.ts +0 -2
  191. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  192. package/dist/router-tanstack/use-nav.js +34 -12
  193. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  194. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  195. package/dist/section/index.d.ts +2 -0
  196. package/dist/section/index.d.ts.map +1 -0
  197. package/dist/section/index.js +4 -0
  198. package/dist/section/section.d.ts +24 -0
  199. package/dist/section/section.d.ts.map +1 -0
  200. package/dist/section/section.js +126 -0
  201. package/dist/section/section.module.css +104 -0
  202. package/dist/section/section.module.css.d.ts +11 -0
  203. package/dist/section/section.test.d.ts +2 -0
  204. package/dist/section/section.test.d.ts.map +1 -0
  205. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  206. package/dist/shell/app-breadcrumbs.js +18 -15
  207. package/dist/shell/app-header.module.css +77 -37
  208. package/dist/shell/app-header.module.css.d.ts +2 -1
  209. package/dist/shell/app-mobile-nav.d.ts +9 -2
  210. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  211. package/dist/shell/app-mobile-nav.js +98 -11
  212. package/dist/shell/app-mobile-nav.module.css +8 -0
  213. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  214. package/dist/shell/app-sidebar.d.ts +55 -28
  215. package/dist/shell/app-sidebar.d.ts.map +1 -1
  216. package/dist/shell/app-sidebar.js +116 -67
  217. package/dist/shell/app-sidebar.module.css +303 -20
  218. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  219. package/dist/shell/index.d.ts +55 -21
  220. package/dist/shell/index.d.ts.map +1 -1
  221. package/dist/shell/index.js +40 -18
  222. package/dist/shell/mobile-nav-model.d.ts +13 -2
  223. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  224. package/dist/shell/mobile-nav-model.js +15 -1
  225. package/dist/shell/nav-count-badge.d.ts +1 -1
  226. package/dist/shell/page-bar.d.ts +51 -0
  227. package/dist/shell/page-bar.d.ts.map +1 -0
  228. package/dist/shell/page-bar.js +154 -0
  229. package/dist/shell/page-bar.module.css +178 -0
  230. package/dist/shell/page-bar.module.css.d.ts +17 -0
  231. package/dist/shell/page-bar.test.d.ts +2 -0
  232. package/dist/shell/page-bar.test.d.ts.map +1 -0
  233. package/dist/shell/sidebar-block-model.d.ts +48 -0
  234. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  235. package/dist/shell/sidebar-block-model.js +39 -0
  236. package/dist/shell/sidebar-blocks.d.ts +32 -0
  237. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  238. package/dist/shell/sidebar-blocks.js +169 -0
  239. package/dist/shell/sidebar-search.d.ts +13 -1
  240. package/dist/shell/sidebar-search.d.ts.map +1 -1
  241. package/dist/shell/sidebar-search.js +34 -1
  242. package/dist/shell/sidebar-search.module.css +26 -0
  243. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  244. package/dist/state/fields.d.ts +565 -0
  245. package/dist/state/fields.d.ts.map +1 -0
  246. package/dist/state/fields.js +405 -0
  247. package/dist/state/fields.test.d.ts +2 -0
  248. package/dist/state/fields.test.d.ts.map +1 -0
  249. package/dist/state/persisted.d.ts +60 -0
  250. package/dist/state/persisted.d.ts.map +1 -0
  251. package/dist/state/persisted.js +123 -0
  252. package/dist/state/persisted.test.d.ts +2 -0
  253. package/dist/state/persisted.test.d.ts.map +1 -0
  254. package/dist/state.d.ts +11 -56
  255. package/dist/state.d.ts.map +1 -1
  256. package/dist/state.js +14 -120
  257. package/dist/styles.css +13 -0
  258. package/dist/surfaces.d.ts +106 -24
  259. package/dist/surfaces.d.ts.map +1 -1
  260. package/dist/surfaces.js +156 -19
  261. package/dist/theme/controls.module.css +16 -1
  262. package/dist/theme/ctl-theme.d.ts +32 -0
  263. package/dist/theme/ctl-theme.d.ts.map +1 -0
  264. package/dist/theme/ctl-theme.js +54 -0
  265. package/dist/theme/ctl-theme.test.d.ts +2 -0
  266. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  267. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  268. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  269. package/dist/theme/divider-law.test.d.ts +2 -0
  270. package/dist/theme/divider-law.test.d.ts.map +1 -0
  271. package/dist/theme/icon-slot.d.ts +44 -0
  272. package/dist/theme/icon-slot.d.ts.map +1 -0
  273. package/dist/theme/icon-slot.js +21 -0
  274. package/dist/theme/icon-slot.module.css +47 -0
  275. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  276. package/dist/theme/icon-slot.test.d.ts +2 -0
  277. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  278. package/dist/theme/index.d.ts +1 -0
  279. package/dist/theme/index.d.ts.map +1 -1
  280. package/dist/theme/index.js +82 -5
  281. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  282. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  283. package/dist/theme/segmented-control.module.css +20 -0
  284. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  285. package/dist/tokens/index.d.ts +12 -0
  286. package/dist/tokens/index.d.ts.map +1 -1
  287. package/dist/tokens/index.js +38 -8
  288. package/dist/tokens/palette.d.ts +97 -61
  289. package/dist/tokens/palette.d.ts.map +1 -1
  290. package/dist/tokens/palette.js +76 -38
  291. package/dist/tokens.css +10 -2
  292. package/dist/widget-header/delta-badge.d.ts +13 -0
  293. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  294. package/dist/widget-header/delta-badge.js +29 -0
  295. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  296. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  297. package/dist/widget-header/index.d.ts +3 -0
  298. package/dist/widget-header/index.d.ts.map +1 -0
  299. package/dist/widget-header/index.js +6 -0
  300. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  301. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  302. package/dist/widget-header/widget-header.d.ts +67 -0
  303. package/dist/widget-header/widget-header.d.ts.map +1 -0
  304. package/dist/widget-header/widget-header.js +89 -0
  305. package/dist/widget-header/widget-header.module.css +350 -0
  306. package/dist/widget-header/widget-header.module.css.d.ts +25 -0
  307. package/dist/widget-header/widget-header.test.d.ts +2 -0
  308. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  309. package/llms.txt +30 -17
  310. package/package.json +14 -1
  311. package/src/charts/cursor/resolve.test.ts +29 -1
  312. package/src/charts/cursor/resolve.ts +38 -6
  313. package/src/charts/cursor/scope.tsx +6 -3
  314. package/src/charts/cursor/store.test.ts +19 -1
  315. package/src/charts/cursor/store.ts +10 -5
  316. package/src/charts/hooks/useChartCursor.ts +16 -8
  317. package/src/charts/index.ts +2 -2
  318. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  319. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  320. package/src/charts/primitives/ChartCard.tsx +86 -146
  321. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  322. package/src/cli/check-coverage.test.ts +297 -0
  323. package/src/cli/check-theme.test.ts +38 -14
  324. package/src/cli/index.ts +392 -16
  325. package/src/cli/init-sync.test.ts +97 -0
  326. package/src/connectivity/connectivity-indicator.tsx +16 -9
  327. package/src/content/index.ts +16 -3
  328. package/src/content/prose.module.css +7 -28
  329. package/src/content/prose.module.css.test.ts +8 -24
  330. package/src/controls/actions.module.css +62 -0
  331. package/src/controls/actions.module.css.d.ts +13 -0
  332. package/src/controls/actions.test.tsx +252 -0
  333. package/src/controls/actions.tsx +655 -0
  334. package/src/controls/actions.type-guard.test.ts +63 -0
  335. package/src/controls/compare-filter.tsx +64 -0
  336. package/src/controls/control-group.test.tsx +229 -0
  337. package/src/controls/control-group.tsx +63 -0
  338. package/src/controls/controls.module.css +542 -0
  339. package/src/controls/controls.module.css.d.ts +35 -0
  340. package/src/controls/controls.router.test.tsx +1053 -0
  341. package/src/controls/enum-filter.tsx +170 -0
  342. package/src/controls/filter-context.tsx +158 -0
  343. package/src/controls/filter-pill.tsx +120 -0
  344. package/src/controls/filter-set.test.tsx +472 -0
  345. package/src/controls/filter-set.tsx +225 -0
  346. package/src/controls/filter-sheet.tsx +285 -0
  347. package/src/controls/glyphs.tsx +81 -0
  348. package/src/controls/index.ts +49 -0
  349. package/src/controls/multi-select-filter.tsx +140 -0
  350. package/src/controls/number-filter.tsx +291 -0
  351. package/src/controls/peer-boundary.test.ts +85 -0
  352. package/src/controls/props-contract.test.tsx +137 -0
  353. package/src/controls/range-filter.tsx +237 -0
  354. package/src/controls/range-filter.type-guard.test.tsx +122 -0
  355. package/src/controls/search-filter.tsx +123 -0
  356. package/src/controls/select-filter.tsx +89 -0
  357. package/src/controls/sync-button.module.css +63 -0
  358. package/src/controls/sync-button.module.css.d.ts +11 -0
  359. package/src/controls/sync-button.test.tsx +183 -0
  360. package/src/controls/sync-button.tsx +197 -0
  361. package/src/controls/toggle-filter.tsx +60 -0
  362. package/src/controls/view-tabs.tsx +110 -0
  363. package/src/controls-dates/date-range-picker.tsx +42 -0
  364. package/src/controls-dates/index.ts +8 -0
  365. package/src/dashboard/delta-badge.tsx +4 -74
  366. package/src/dashboard/index.ts +6 -1
  367. package/src/dashboard/settings-section.module.css +10 -0
  368. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  369. package/src/dashboard/settings-section.test.tsx +56 -0
  370. package/src/dashboard/settings-section.tsx +33 -30
  371. package/src/dashboard/stat-card.module.css +153 -0
  372. package/src/dashboard/stat-card.module.css.d.ts +15 -0
  373. package/src/dashboard/stat-card.test.tsx +298 -1
  374. package/src/dashboard/stat-card.tsx +289 -67
  375. package/src/data/data-table.test.tsx +96 -11
  376. package/src/data/data-table.tsx +228 -84
  377. package/src/guard/check-source.test.ts +159 -13
  378. package/src/guard/grace.test.ts +158 -0
  379. package/src/guard/index.ts +185 -29
  380. package/src/guard/types.ts +4 -2
  381. package/src/index.ts +51 -5
  382. package/src/nav/types.ts +84 -2
  383. package/src/notifications/bell.tsx +6 -3
  384. package/src/notifications/index.ts +1 -1
  385. package/src/router-tanstack/field.ts +35 -0
  386. package/src/router-tanstack/index.ts +28 -2
  387. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  388. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  389. package/src/router-tanstack/search-param-store.ts +42 -159
  390. package/src/router-tanstack/search-store.router.test.tsx +882 -0
  391. package/src/router-tanstack/search-store.test.ts +578 -0
  392. package/src/router-tanstack/search-store.ts +546 -0
  393. package/src/router-tanstack/use-nav.test.tsx +187 -0
  394. package/src/router-tanstack/use-nav.ts +72 -14
  395. package/src/section/index.ts +1 -0
  396. package/src/section/section.module.css +104 -0
  397. package/src/section/section.module.css.d.ts +11 -0
  398. package/src/section/section.test.tsx +204 -0
  399. package/src/section/section.tsx +220 -0
  400. package/src/shell/app-breadcrumbs.tsx +44 -31
  401. package/src/shell/app-header.module.css +77 -37
  402. package/src/shell/app-header.module.css.d.ts +2 -1
  403. package/src/shell/app-mobile-nav.module.css +8 -0
  404. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  405. package/src/shell/app-mobile-nav.tsx +147 -8
  406. package/src/shell/app-sidebar-account.tsx +2 -2
  407. package/src/shell/app-sidebar.module.css +303 -20
  408. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  409. package/src/shell/app-sidebar.test.tsx +456 -41
  410. package/src/shell/app-sidebar.tsx +276 -140
  411. package/src/shell/index.test.tsx +291 -5
  412. package/src/shell/index.tsx +137 -38
  413. package/src/shell/mobile-nav-model.test.ts +228 -7
  414. package/src/shell/mobile-nav-model.ts +53 -3
  415. package/src/shell/nav-count-badge.tsx +1 -1
  416. package/src/shell/page-bar.module.css +178 -0
  417. package/src/shell/page-bar.module.css.d.ts +17 -0
  418. package/src/shell/page-bar.test.tsx +316 -0
  419. package/src/shell/page-bar.tsx +315 -0
  420. package/src/shell/sidebar-block-model.ts +83 -0
  421. package/src/shell/sidebar-blocks.tsx +301 -0
  422. package/src/shell/sidebar-search.module.css +26 -0
  423. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  424. package/src/shell/sidebar-search.tsx +71 -2
  425. package/src/state/fields.test.tsx +512 -0
  426. package/src/state/fields.ts +1150 -0
  427. package/src/state/persisted.test.ts +91 -0
  428. package/src/state/persisted.ts +261 -0
  429. package/src/state.ts +52 -240
  430. package/src/styles.css +13 -0
  431. package/src/surfaces.ts +204 -36
  432. package/src/theme/controls.module.css +16 -1
  433. package/src/theme/ctl-theme.test.tsx +275 -0
  434. package/src/theme/ctl-theme.tsx +138 -0
  435. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  436. package/src/theme/density-relations.test.ts +106 -51
  437. package/src/theme/divider-law.test.ts +259 -0
  438. package/src/theme/icon-slot.module.css +47 -0
  439. package/src/theme/icon-slot.module.css.d.ts +8 -0
  440. package/src/theme/icon-slot.test.tsx +275 -0
  441. package/src/theme/icon-slot.tsx +59 -0
  442. package/src/theme/index.ts +165 -3
  443. package/src/theme/layout-rhythm-css.test.ts +72 -0
  444. package/src/theme/radius.test.ts +11 -1
  445. package/src/theme/segmented-control.module.css +20 -0
  446. package/src/theme/shadow-surfaces.test.ts +20 -7
  447. package/src/theme/spacing.test.ts +75 -27
  448. package/src/theme/use-basalt-spacing.ts +2 -2
  449. package/src/tokens/density.test.ts +7 -25
  450. package/src/tokens/index.ts +48 -8
  451. package/src/tokens/palette.ts +107 -73
  452. package/src/widget-header/delta-badge.test.tsx +60 -0
  453. package/src/widget-header/delta-badge.tsx +57 -0
  454. package/src/widget-header/index.ts +2 -0
  455. package/src/widget-header/no-mantine.test.ts +33 -0
  456. package/src/widget-header/widget-header.module.css +350 -0
  457. package/src/widget-header/widget-header.module.css.d.ts +25 -0
  458. package/src/widget-header/widget-header.test.tsx +187 -0
  459. package/src/widget-header/widget-header.tsx +213 -0
  460. package/agent/rules/basalt-agent.md +0 -452
  461. package/agent/rules/basalt-app.md +0 -246
  462. package/agent/rules/basalt-commands.md +0 -320
  463. package/agent/rules/basalt-content.md +0 -409
  464. package/agent/rules/basalt-data.md +0 -329
  465. package/agent/rules/basalt-forms.md +0 -186
  466. package/agent/rules/basalt-notifications.md +0 -264
  467. package/agent/rules/basalt-query.md +0 -187
  468. package/agent/rules/basalt-router.md +0 -336
  469. package/dist/content/article-filter-bar.d.ts +0 -40
  470. package/dist/content/article-filter-bar.d.ts.map +0 -1
  471. package/dist/content/article-filter-bar.js +0 -56
  472. package/dist/shell/page-header.d.ts +0 -13
  473. package/dist/shell/page-header.d.ts.map +0 -1
  474. package/dist/shell/page-header.js +0 -23
  475. package/src/content/article-filter-bar.tsx +0 -88
  476. package/src/shell/page-header.tsx +0 -35
@@ -0,0 +1,104 @@
1
+ /*
2
+ * Section — the tier-2 heading composer (docs/CONTROLS-SPEC.md §2.2). No border/background of its
3
+ * own — one shaded container level per page belongs to the cards inside a Section
4
+ * (`ChartCard`/`StatCard`), not to the Section itself.
5
+ */
6
+
7
+ /*
8
+ * 12px from the header to the body, and the same between two body children — one rhythm, read from
9
+ * the stack scale (`--vx-space-stack-md`) instead of the card-inset scale
10
+ * (`--mantine-spacing-sm`, 13px). A Section is a page-level GROUPING, so its gaps belong to the
11
+ * vertical rhythm, not to the inset ladder its cards use.
12
+ */
13
+ .root {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--vx-space-stack-md, 0.75rem);
17
+ }
18
+
19
+ .body {
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: var(--vx-space-stack-md, 0.75rem);
23
+ }
24
+
25
+ /*
26
+ * `summary` — the always-visible row under the header. No border, no background (the Section owns
27
+ * neither); it is a plain flow row, so a consumer's `Group` of stats or one line of `Text` lands
28
+ * where the body would have started.
29
+ */
30
+ .summary {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--mantine-spacing-xs, 0.625rem);
34
+ }
35
+
36
+ /*
37
+ * The collapse chevron — a plain control, not a Mantine ActionIcon, so it sits directly in the header
38
+ * row (its own CtlSlot fragment would nest a theme provider around one glyph). It is sized to the
39
+ * `ctl` tier BY HAND for exactly that reason: a 12px glyph in a 20px box was a different height from
40
+ * every other control in the same header, which is what made the section header read as ragged. 30px
41
+ * square, glyph centred, no padding — the box IS the tier.
42
+ */
43
+ .chevron {
44
+ /* `relative` is the anchor for the C15 hit-area overlay in the `< sm` block at the bottom. */
45
+ position: relative;
46
+ display: inline-flex;
47
+ flex: none;
48
+ align-items: center;
49
+ justify-content: center;
50
+ /* `--vx-space-control-height-ctl` is a bare rem — the theme's `ctlSizeVars` (`theme/index.ts`)
51
+ * reconstructs every OTHER ctl-tier box as `calc(<rem> * var(--mantine-scale))` so it tracks
52
+ * Mantine's `theme.scale`; read raw here, this box used to stay fixed while a scaled Button/Input
53
+ * beside it grew or shrank, reintroducing the exact raggedness this control was sized to fix. */
54
+ width: calc(var(--vx-space-control-height-ctl) * var(--mantine-scale, 1));
55
+ height: calc(var(--vx-space-control-height-ctl) * var(--mantine-scale, 1));
56
+ appearance: none;
57
+ margin: 0;
58
+ padding: 0;
59
+ border: none;
60
+ background: none;
61
+ border-radius: var(--vx-radius-ctrl);
62
+ color: var(--vx-muted);
63
+ cursor: pointer;
64
+ }
65
+
66
+ .chevron:hover,
67
+ .chevron:focus-visible {
68
+ color: var(--vx-ink);
69
+ background: var(--vx-surface-subtle);
70
+ }
71
+
72
+ .chevron svg {
73
+ transition: transform 150ms ease;
74
+ }
75
+
76
+ .chevron[data-open='false'] svg {
77
+ transform: rotate(-90deg);
78
+ }
79
+
80
+ /*
81
+ * Law C15 — the chevron is a 12px glyph in a 20px box, which is a fine mouse target and an
82
+ * unusable thumb one. Below `sm` an `::after` overlay lifts the HIT area to
83
+ * `--vx-space-touch-control-height` (36px at level 0) while the visible glyph stays put — the same
84
+ * overlay `shell/page-bar.module.css` draws for the bar's own controls. `overflow: visible` is part
85
+ * of the fix, not tidying: an overlay that extends past the box is clipped by any `overflow:
86
+ * hidden` on the host (which is what made the identical overlay a no-op on Mantine's Button roots).
87
+ */
88
+ @media (max-width: 47.99375em) {
89
+ .chevron {
90
+ overflow: visible;
91
+ }
92
+
93
+ .chevron::after {
94
+ content: '';
95
+ position: absolute;
96
+ top: 50%;
97
+ left: 50%;
98
+ translate: -50% -50%;
99
+ width: 100%;
100
+ height: 100%;
101
+ min-width: var(--vx-space-touch-control-height, 2.25rem);
102
+ min-height: var(--vx-space-touch-control-height, 2.25rem);
103
+ }
104
+ }
@@ -0,0 +1,11 @@
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 root: string
7
+ readonly body: string
8
+ readonly summary: string
9
+ readonly chevron: string
10
+ }
11
+ export default classes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=section.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.test.d.ts","sourceRoot":"","sources":["../../src/section/section.test.tsx"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"app-breadcrumbs.d.ts","sourceRoot":"","sources":["../../src/shell/app-breadcrumbs.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAkB7C,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,MAAM,EACN,YAAY,EACZ,UAAU,EACV,IAAI,GACL,EAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IACpC,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,sCAyCA"}
1
+ {"version":3,"file":"app-breadcrumbs.d.ts","sourceRoot":"","sources":["../../src/shell/app-breadcrumbs.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAmB7C,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,MAAM,EACN,YAAY,EACZ,UAAU,EACV,IAAI,GACL,EAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IACpC,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,sCAqDA"}
@@ -1,6 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Anchor, Group, Text } from "@mantine/core";
3
3
  import { VX } from "../tokens/index.js";
4
+ import classes from "./app-header.module.css";
4
5
  const crumbStyle = { fontSize: VX.text.md, color: "var(--vx-faint)" };
5
6
  const separatorStyle = { fontSize: VX.text.md, color: "var(--vx-surface-border)" };
6
7
  const currentStyle = {
@@ -19,21 +20,23 @@ function AppBreadcrumbs({
19
20
  }) {
20
21
  if (!page) return null;
21
22
  return /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", style: { minWidth: 0 }, children: [
22
- section && /* @__PURE__ */ jsxs(Fragment, { children: [
23
- /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: section }),
24
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
25
- ] }),
26
- parent && parentAnchor && /* @__PURE__ */ jsxs(Fragment, { children: [
27
- /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", component: parentAnchor, truncate: true, children: parent }),
28
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
29
- ] }),
30
- parent && !parentAnchor && parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
31
- /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", href: parentHref, truncate: true, children: parent }),
32
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
33
- ] }),
34
- parent && !parentAnchor && !parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
35
- /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: parent }),
36
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
23
+ (section !== void 0 || parent !== void 0) && /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", className: classes.crumbAncestors, style: { minWidth: 0 }, children: [
24
+ section && /* @__PURE__ */ jsxs(Fragment, { children: [
25
+ /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: section }),
26
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
27
+ ] }),
28
+ parent && parentAnchor && /* @__PURE__ */ jsxs(Fragment, { children: [
29
+ /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", component: parentAnchor, truncate: true, children: parent }),
30
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
31
+ ] }),
32
+ parent && !parentAnchor && parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
33
+ /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", href: parentHref, truncate: true, children: parent }),
34
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
35
+ ] }),
36
+ parent && !parentAnchor && !parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: parent }),
38
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
39
+ ] })
37
40
  ] }),
38
41
  /* @__PURE__ */ jsx(Text, { style: currentStyle, truncate: true, children: page })
39
42
  ] });
@@ -1,7 +1,10 @@
1
- /* App-shell top bar layout. Desktop: a single 56px row —
2
- * [burger? · breadcrumb] ········ [page actions] | [global actions].
3
- * Mobile (< sm): wraps to two rows — lead + global on row 1, the page action row
4
- * (horizontally scrollable) on row 2. Header height is responsive to match. */
1
+ /* App-shell top bar layout. ONE 48px row on every viewport —
2
+ * [breadcrumb] ········ [page bar row 1] | [global actions].
3
+ *
4
+ * The pre-1.26.0 mobile layout wrapped to a second, always-reserved row for the page actions and
5
+ * scrolled it sideways. Both are gone (law C7 and law C14): the header height is now the
6
+ * `appShellHeaderHeight` token at every width, an empty page bar reserves nothing, and overflow
7
+ * folds into `ActionGroup`'s kebab instead of scrolling out of view. */
5
8
 
6
9
  /*
7
10
  * Transparent header (docs/DESIGN-SPEC.md §5): NO bottom rule at all. The header and the page body
@@ -16,58 +19,95 @@
16
19
  height: 100%;
17
20
  }
18
21
 
22
+ /*
23
+ * **Exactly ONE side of the header row is elastic, and it is the side whose content can ellipsize.**
24
+ * That is the whole rule, and both collisions this header has produced were the same violation of
25
+ * it from opposite directions.
26
+ *
27
+ * First the breadcrumb was `flex-shrink: 0`, so it won the whole width and the primary action
28
+ * rendered UNDERNEATH it. Then BOTH sides were made shrinkable (`.lead` at `0 1 auto`, `.pageBar` at
29
+ * `1 1 auto` with `min-width: 0` pushed onto its children) — and at 390px the controls, which cannot
30
+ * clip, shrank below their content and spilled: a flex container with `justify-content: flex-end`
31
+ * overflows towards its START, so the sync button painted 30px INTO the page title. Measured in
32
+ * Chrome: the title spanned 119.7–197.1 and the sync button 158.3–188.3.
33
+ *
34
+ * So the lead is now the elastic side (`flex: 1 1 auto`, `min-width: 0`, each crumb truncating via
35
+ * its own `truncate` prop) and the control side is rigid. A row of buttons that genuinely does not
36
+ * fit is not a shrinking problem — it is the OVERFLOW FOLD's problem, and basalt already owns that
37
+ * (law C7: below `sm` row 1 is the primary plus ONE kebab).
38
+ */
19
39
  .lead {
20
40
  display: flex;
21
41
  align-items: center;
22
42
  gap: var(--mantine-spacing-sm);
23
- flex-shrink: 0;
43
+ flex: 1 1 auto;
24
44
  min-width: 0;
25
45
  }
26
46
 
27
- .pageActions {
28
- flex: 1 1 auto;
47
+ /* No `overflow: hidden` here or on `.lead` above: Mantine's focus ring is `outline: 2px solid;
48
+ * outline-offset: 2px` — OUTSIDE the anchor box — and a wrapper clipping this tightly to the
49
+ * breadcrumb anchors clipped the ring on every edge, leaving the primary in-shell nav link with no
50
+ * visible focus indicator. Truncation is already owned by each crumb's own `truncate` prop
51
+ * (`app-breadcrumbs.tsx`, Mantine's `[data-truncate]` rule sets its own `overflow: hidden;
52
+ * text-overflow: ellipsis; white-space: nowrap` on the element that actually holds the text) — this
53
+ * wrapper only needs to let its children shrink. */
54
+ .lead * {
29
55
  min-width: 0;
30
- display: flex;
31
- align-items: center;
32
- justify-content: flex-end;
33
- gap: var(--mantine-spacing-xs);
34
- overflow-x: auto;
35
- scrollbar-width: thin;
36
56
  }
37
57
 
38
- /* Keep the whole control row on one horizontally-scrollable line. Mantine Group
39
- * defaults to flex-wrap:wrap, which otherwise stacks the nested control groups and
40
- * clips them against the slim bar's fixed height. */
41
- .pageActions :global(.mantine-Group-root) {
58
+ /*
59
+ * The `PageBar` row-1 portal target — the RIGID side of the row (see `.lead` above for the rule and
60
+ * for the two collisions that established it). Never a scroll container — see the header note above.
61
+ *
62
+ * `flex: 0 0 auto` and no `min-width: 0` anywhere below it, deliberately: a button cannot ellipsize,
63
+ * so letting this side shrink does not make it narrower, it makes it OVERFLOW — leftwards, into the
64
+ * breadcrumb, because of the `justify-content: flex-end` two lines down. The previous version pushed
65
+ * `min-width: 0` onto every child for exactly the reason that comment gave (a `wrap="nowrap"` group's
66
+ * automatic minimum size is its min-content width and flex will not go below it) — the reasoning was
67
+ * right and the conclusion was backwards: that floor is the row's protection, not its bug.
68
+ */
69
+ .pageBar {
70
+ flex: 0 0 auto;
71
+ display: flex;
42
72
  flex-wrap: nowrap;
73
+ align-items: center;
74
+ justify-content: flex-end;
75
+ gap: var(--vx-space-control-gap);
43
76
  }
44
77
 
78
+ /*
79
+ * The shell globals sit at the SAME 30px tier as everything else in the row, and they get there the
80
+ * same way a home's slot does — `HeaderGlobalActions` wraps this region in a `CtlSlot`, so a
81
+ * consumer's bare `<ActionIcon>` in `globalActions` defaults to `size="ctl"` with no prop at the call
82
+ * site (law C5). They rendered at Mantine's `md` 28px before, which is 2px under the page group
83
+ * beside them: close enough to look like a mistake rather than a tier.
84
+ *
85
+ * The gap to the page group is `.bar`'s own `sm`, so the globals read as a separate cluster; between
86
+ * themselves they use the control gap, like every other adjacent-control pair.
87
+ */
45
88
  .global {
46
89
  display: flex;
90
+ flex-wrap: nowrap;
47
91
  align-items: center;
48
- gap: var(--mantine-spacing-xs);
92
+ gap: var(--vx-space-control-gap);
49
93
  flex-shrink: 0;
50
94
  }
51
95
 
52
- @media (max-width: 47.99375em) {
53
- .bar {
54
- flex-wrap: wrap;
55
- align-content: center;
56
- }
57
-
58
- .lead {
59
- order: 1;
60
- flex: 1 1 auto;
61
- }
62
-
63
- .global {
64
- order: 2;
65
- }
96
+ .global :global(.mantine-ActionIcon-root) > svg {
97
+ width: 1rem;
98
+ height: 1rem;
99
+ }
66
100
 
67
- .pageActions {
68
- order: 3;
69
- flex-basis: 100%;
70
- height: var(--vx-space-app-header-mobile-actions-height);
71
- justify-content: flex-start;
101
+ /*
102
+ * The breadcrumb's ANCESTOR crumbs — hidden below `sm`. See `app-breadcrumbs.tsx` for why: at 390px
103
+ * the row's elastic side truncated `Overview / Dashboard` to `O… / D…`, which is two ellipses and a
104
+ * separator in place of the one word that says where the reader is.
105
+ *
106
+ * `47.99375em` is the `sm` breakpoint in the exact literal form every other basalt CSS module uses —
107
+ * a media query cannot resolve a custom property.
108
+ */
109
+ @media (max-width: 47.99375em) {
110
+ .crumbAncestors {
111
+ display: none;
72
112
  }
73
113
  }
@@ -5,7 +5,8 @@
5
5
  declare const classes: {
6
6
  readonly bar: string
7
7
  readonly lead: string
8
- readonly pageActions: string
8
+ readonly pageBar: string
9
9
  readonly global: string
10
+ readonly crumbAncestors: string
10
11
  }
11
12
  export default classes
@@ -1,5 +1,5 @@
1
1
  import type { ReactElement } from 'react';
2
- import type { MobileNavConfig, MobileNavModel } from '../nav/types.js';
2
+ import type { MobileNavConfig, MobileNavModel, SidebarBlock } from '../nav/types.js';
3
3
  import type { BasaltAccountProps } from './account-types.js';
4
4
  import type { SettingsMenuItem } from './index.js';
5
5
  export type MobileNavProps = {
@@ -11,6 +11,13 @@ export type MobileNavProps = {
11
11
  * deletable: everything it used to hold is reachable from More. */
12
12
  account?: BasaltAccountProps | undefined;
13
13
  settingsMenuItems?: SettingsMenuItem[] | undefined;
14
+ /**
15
+ * The sidebar's blocks, projected. A block with `mobile: 'more'` becomes ONE row in the More
16
+ * surface (`Awaiting action · 3`) that opens a nested 44px-row sheet of its items — the sidebar
17
+ * itself does not exist below `sm`, so this is the only way its blocks are reachable at all.
18
+ * Counted for the menu-vs-sheet threshold by `blockRowCount`, never re-counted here.
19
+ */
20
+ blocks?: SidebarBlock[] | undefined;
14
21
  };
15
22
  /**
16
23
  * How many rows `account` contributes to the More surface — DELEGATED to `accountRows` rather than
@@ -21,5 +28,5 @@ export type MobileNavProps = {
21
28
  * rows — still conjured a More slot that opened empty.
22
29
  */
23
30
  export declare function accountRowCount(account: BasaltAccountProps | undefined): number;
24
- export declare function MobileNav({ model, config, account, settingsMenuItems, }: MobileNavProps): ReactElement;
31
+ export declare function MobileNav({ model, config, account, settingsMenuItems, blocks, }: MobileNavProps): ReactElement;
25
32
  //# sourceMappingURL=app-mobile-nav.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,YAAY,EAAa,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGf,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAiB/C,MAAM,MAAM,cAAc,GAAG;IAC3B,kFAAkF;IAClF,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;wEAEoE;IACpE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACxC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;CACnD,CAAA;AAkFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAE/E;AAsFD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,OAAO,EACP,iBAAiB,GAClB,EAAE,cAAc,GAAG,YAAY,CAiP/B"}
1
+ {"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,YAAY,EAAa,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAEd,YAAY,EAKb,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAmB/C,MAAM,MAAM,cAAc,GAAG;IAC3B,kFAAkF;IAClF,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;wEAEoE;IACpE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACxC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;IAClD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAA;CACpC,CAAA;AAkFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAE/E;AAsFD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,OAAO,EACP,iBAAiB,EACjB,MAAM,GACP,EAAE,cAAc,GAAG,YAAY,CAuW/B"}
@@ -3,6 +3,8 @@ import { Drawer, Menu, NavLink, ScrollArea, Stack, Text, UnstyledButton } from "
3
3
  import { useReducedMotion } from "@mantine/hooks";
4
4
  import { Fragment as Fragment2, useState } from "react";
5
5
  import { NavCountBadge } from "./nav-count-badge.js";
6
+ import { SidebarBlockToneDot } from "./sidebar-blocks.js";
7
+ import { sidebarBlockMobile } from "./sidebar-block-model.js";
6
8
  import { useBasaltSpacing } from "../theme/index.js";
7
9
  import classes from "./app-mobile-nav.module.css";
8
10
  const MENU_MIDDLEWARES = { flip: false, shift: { padding: 8 } };
@@ -134,9 +136,11 @@ function MobileNav({
134
136
  model,
135
137
  config,
136
138
  account,
137
- settingsMenuItems
139
+ settingsMenuItems,
140
+ blocks
138
141
  }) {
139
142
  const [openKey, setOpenKey] = useState(null);
143
+ const [openBlockKey, setOpenBlockKey] = useState(null);
140
144
  const reduceMotion = useReducedMotion();
141
145
  const { step } = useBasaltSpacing();
142
146
  const close = () => setOpenKey(null);
@@ -145,6 +149,61 @@ function MobileNav({
145
149
  target.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" });
146
150
  };
147
151
  const extraRows = [...accountRows(account), ...settingsRows(settingsMenuItems)];
152
+ const moreBlocks = (blocks ?? []).filter(
153
+ (block) => block.kind !== "custom" && sidebarBlockMobile(block) === "more" && (block.kind !== "list" || block.items.length > 0)
154
+ );
155
+ const listBlocks = moreBlocks.filter((block) => block.kind === "list");
156
+ const activateBlock = (block) => {
157
+ close();
158
+ if (block.kind === "list") {
159
+ setOpenBlockKey(block.key);
160
+ return;
161
+ }
162
+ block.onClick?.();
163
+ };
164
+ const menuBlockRow = (block) => /* @__PURE__ */ jsx(Menu.Item, { className: classes.menuItem, onClick: () => activateBlock(block), children: blockRowLabel(block) }, block.key);
165
+ const sheetBlockRow = (block) => /* @__PURE__ */ jsx(
166
+ NavLink,
167
+ {
168
+ classNames: { root: classes.row },
169
+ label: blockRowLabel(block),
170
+ onClick: () => activateBlock(block)
171
+ },
172
+ block.key
173
+ );
174
+ const blockItemRow = (item) => {
175
+ const lead = item.icon ?? (item.tone !== void 0 ? /* @__PURE__ */ jsx(SidebarBlockToneDot, { tone: item.tone }) : void 0);
176
+ const meta = item.meta !== void 0 ? /* @__PURE__ */ jsx(Text, { component: "span", className: classes.rowMeta, children: item.meta }) : void 0;
177
+ const interactive = item.Anchor !== void 0 || item.href !== void 0 || item.onClick !== void 0;
178
+ if (!interactive) {
179
+ return /* @__PURE__ */ jsxs("div", { className: classes.row, children: [
180
+ lead,
181
+ /* @__PURE__ */ jsx(Text, { component: "span", children: item.label }),
182
+ meta
183
+ ] }, item.key);
184
+ }
185
+ const shared = {
186
+ classNames: { root: classes.row },
187
+ label: item.label,
188
+ ...lead !== void 0 && { leftSection: lead },
189
+ ...meta !== void 0 && { rightSection: meta },
190
+ onClick: () => {
191
+ item.onClick?.();
192
+ setOpenBlockKey(null);
193
+ }
194
+ };
195
+ const Anchor = item.Anchor;
196
+ if (Anchor) return /* @__PURE__ */ jsx(NavLink, { component: Anchor, ...shared }, item.key);
197
+ return /* @__PURE__ */ jsx(
198
+ NavLink,
199
+ {
200
+ component: "a",
201
+ ...item.href !== void 0 && { href: item.href },
202
+ ...shared
203
+ },
204
+ item.key
205
+ );
206
+ };
148
207
  const hasCount = (slot) => slot.kind === "link" ? Boolean(slot.item.count) : slot.groups.some((g) => groupHasCount(g.items));
149
208
  const tabInner = (slot) => /* @__PURE__ */ jsxs(Fragment, { children: [
150
209
  /* @__PURE__ */ jsxs("span", { className: classes.tabIcon, children: [
@@ -271,9 +330,9 @@ function MobileNav({
271
330
  ...group.label ? [/* @__PURE__ */ jsx(Menu.Label, { children: group.label }, `${group.key}-label`)] : [],
272
331
  ...rows(group.items, 0, menuRow)
273
332
  ]),
274
- slot.isMore && extraRows.length > 0 && slotHasRows(slot) ? /* @__PURE__ */ jsx(Menu.Divider, {}) : null,
275
- slot.isMore ? extraRows.map(menuActionRow) : null,
276
- slot.isMore ? config?.moreExtra : null
333
+ slot.isMore && moreBlocks.length + extraRows.length > 0 && slotHasRows(slot) ? /* @__PURE__ */ jsx(Menu.Divider, {}) : null,
334
+ slot.isMore ? moreBlocks.map(menuBlockRow) : null,
335
+ slot.isMore ? extraRows.map(menuActionRow) : null
277
336
  ] })
278
337
  ]
279
338
  },
@@ -304,17 +363,45 @@ function MobileNav({
304
363
  sheetGroupLabel(group),
305
364
  rows(group.items, 0, sheetRow)
306
365
  ] }, group.key)),
307
- slot.isMore && extraRows.length > 0 ? /* @__PURE__ */ jsx(Stack, { gap: 2, children: extraRows.map(sheetActionRow) }) : null,
308
- slot.isMore ? config?.moreExtra : null
366
+ slot.isMore && moreBlocks.length > 0 ? /* @__PURE__ */ jsx(Stack, { gap: 2, children: moreBlocks.map(sheetBlockRow) }) : null,
367
+ slot.isMore && extraRows.length > 0 ? /* @__PURE__ */ jsx(Stack, { gap: 2, children: extraRows.map(sheetActionRow) }) : null
309
368
  ] }) })
310
369
  }
311
370
  )
312
371
  ] }, slot.key);
313
- return /* @__PURE__ */ jsx("nav", { className: classes.bar, "aria-label": "Primary", children: model.slots.map((slot) => {
314
- if (slot.kind === "link") return linkTab(slot);
315
- if (slot.kind === "menu") return menuSlot(slot);
316
- return sheetSlot(slot);
317
- }) });
372
+ const blockSheet = (block) => /* @__PURE__ */ jsx(
373
+ Drawer,
374
+ {
375
+ opened: openBlockKey === block.key,
376
+ onClose: () => setOpenBlockKey(null),
377
+ position: "bottom",
378
+ padding: "md",
379
+ title: block.label,
380
+ classNames: {
381
+ content: classes.sheet,
382
+ title: classes.sheetTitle,
383
+ header: classes.sheetHeader
384
+ },
385
+ transitionProps: {
386
+ transition: "slide-up",
387
+ duration: reduceMotion ? 0 : 220,
388
+ timingFunction: "cubic-bezier(0.32, 0.72, 0, 1)"
389
+ },
390
+ children: /* @__PURE__ */ jsx(ScrollArea.Autosize, { mah: "62dvh", type: "scroll", children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: block.items.map(blockItemRow) }) })
391
+ },
392
+ `block-${block.key}`
393
+ );
394
+ return /* @__PURE__ */ jsxs("nav", { className: classes.bar, "aria-label": "Primary", children: [
395
+ model.slots.map((slot) => {
396
+ if (slot.kind === "link") return linkTab(slot);
397
+ if (slot.kind === "menu") return menuSlot(slot);
398
+ return sheetSlot(slot);
399
+ }),
400
+ listBlocks.map(blockSheet)
401
+ ] });
402
+ }
403
+ function blockRowLabel(block) {
404
+ return block.kind === "list" ? `${block.label} \xB7 ${block.count ?? block.items.length}` : `${block.label} \xB7 ${block.value} of ${block.total}`;
318
405
  }
319
406
  function slotHasRows(slot) {
320
407
  return slot.groups.some((group) => group.items.length > 0);
@@ -252,6 +252,14 @@
252
252
  background-color: color-mix(in srgb, var(--vx-ink) 10%, transparent);
253
253
  }
254
254
 
255
+ /* Trailing meta on a block-item row (a timestamp, an owner) — the mobile twin of the sidebar's
256
+ * `.blockRowMeta`, so the same `SidebarBlockItem.meta` reads the same on both surfaces. */
257
+ .rowMeta {
258
+ flex: none;
259
+ font-size: var(--vx-text-micro);
260
+ color: var(--vx-faint);
261
+ }
262
+
255
263
  /*
256
264
  * Active = neutral surface fill + plain text color. Never the identity blue (DESIGN.md). --nl-color
257
265
  * forces NavLink's active text/icon color (otherwise the accent) to the plain text color. The
@@ -19,5 +19,6 @@ declare const classes: {
19
19
  readonly sheetHeader: string
20
20
  readonly row: string
21
21
  readonly rowNested: string
22
+ readonly rowMeta: string
22
23
  }
23
24
  export default classes
@@ -1,49 +1,76 @@
1
- import type { ReactNode } from 'react';
2
1
  import type { BrandConfig, SettingsMenuItem } from './index.js';
3
- import type { SidebarSection } from '../nav/types.js';
4
- import type { BasaltAccountProps } from './account-types.js';
5
- import type { SidebarSearchConfig } from './sidebar-search.js';
2
+ import type { SidebarBlock, SidebarSection } from '../nav/types.js';
3
+ import type { AccountMenuItem, BasaltAccountProps } from './account-types.js';
4
+ import type { SidebarSearchActions, SidebarSearchConfig } from './sidebar-search.js';
6
5
  export type AppSidebarProps = {
7
- brand: BrandConfig;
6
+ /**
7
+ * Brand identity. Supplying `menu` turns the brand row into a `Name ▾` workspace switcher —
8
+ * the entries are the existing `AccountMenuItem` shape, so a consumer already mapping account
9
+ * actions needs no second vocabulary.
10
+ */
11
+ brand: BrandConfig & {
12
+ menu?: AccountMenuItem[];
13
+ };
8
14
  sections: SidebarSection[];
9
15
  collapsed: boolean;
10
16
  onToggleCollapse: () => void;
11
17
  /**
12
18
  * Footer settings-menu entries (theme switcher, devtools, …) — supplied by the consumer.
13
- * The pinned footer "Settings" row renders ONLY when this is a non-empty list — apps that put
14
- * Settings in a nav section (the common case) omit this and get no duplicate footer row. The
15
- * `brand.version` label renders inside this menu, so it also only shows when the row does.
19
+ * The pinned footer row renders ONLY when this is a non-empty list — apps that put Settings in a
20
+ * nav section (the common case) omit this and get no duplicate footer row.
21
+ *
22
+ * **Three entries or fewer render FLAT**, as one link row each (Settings · Integrations · Invite
23
+ * teammates), and four or more collapse into the single gear "Settings" menu. A menu that opens
24
+ * to show two rows costs a click for nothing; a footer of eight rows costs the nav its height.
25
+ * That threshold is basalt's — `docs/CONTROLS-SPEC.md` §2.3 — and `settingsMenu` is the override
26
+ * for the cases the count cannot see. `brand.version` renders as a faint label under the flat
27
+ * rows, and inside the dropdown in the menu form.
16
28
  */
17
29
  settingsMenuItems?: SettingsMenuItem[];
30
+ /**
31
+ * Which footer form `settingsMenuItems` takes. `'auto'` (the default) is the count rule above;
32
+ * `'flat'` and `'menu'` force one regardless of how many entries there are.
33
+ *
34
+ * The override exists because the COUNT is not the whole question. Three rows that are each a
35
+ * theme radio group, a devtools switch and a density slider are three CONTROLS, not three
36
+ * destinations — flat they fill the footer with widgets and push the account row off the fold,
37
+ * and the count rule cannot tell them from three links. Equally, four short destinations may be
38
+ * worth their height on a tall sidebar. `'auto'` stays the default so nothing moves for a
39
+ * consumer that never had an opinion.
40
+ *
41
+ * DESKTOP FOOTER ONLY. Below `sm` there is no sidebar at all and the same entries are flat rows
42
+ * in the bottom bar's More surface either way — a dropdown inside that sheet would be a menu
43
+ * inside a menu.
44
+ *
45
+ * @default 'auto'
46
+ */
47
+ settingsMenu?: 'auto' | 'flat' | 'menu';
18
48
  /**
19
49
  * Optional account row rendered below the settings menu in the sidebar footer (see
20
- * `SidebarAccount` / `BasaltAccountProps`), separated by its own top hairline. Omitting it
21
- * reproduces today's footer unchanged.
50
+ * `SidebarAccount` / `BasaltAccountProps`) — no separating hairline, the row's own top padding
51
+ * supplies the separation. Omitting it reproduces today's footer unchanged.
22
52
  */
23
53
  account?: BasaltAccountProps;
24
54
  /**
25
55
  * Optional search field rendered directly below the brand and ABOVE the nav scroll region — a
26
- * fixed, non-scrolling row. Pair with basalt-ui/commands' openSpotlight.
56
+ * fixed, non-scrolling row. Pair with basalt-ui/commands' openSpotlight. `actions` adds up to two
57
+ * icon-only buttons to the right of that row.
27
58
  */
28
- search?: SidebarSearchConfig;
59
+ search?: SidebarSearchConfig & {
60
+ actions?: SidebarSearchActions;
61
+ };
29
62
  /**
30
- * Arbitrary content appended after `sections` inside the nav `ScrollArea` — a tree, a filter
31
- * panel, a project list, anything a set of `SidebarItem`s can't express. Renders as the last
32
- * child of the scrolling nav column, so a long list scrolls with the rest of the nav instead of
33
- * fighting it for height. Pass `sections={[]}` to use this slot exclusively; the section-spacing
34
- * rule only fires between adjacent children, so an empty `sections` produces no orphan divider or
35
- * dead padding above it.
63
+ * Declared blocks (`docs/CONTROLS-SPEC.md` §2.3, law C13) — an "Awaiting action" list, a
64
+ * "Recents" list, a "Getting started" progress row, or a `kind: 'custom'` node for a tree or
65
+ * filter panel no set of rows can express (`kind: 'custom'` is what replaced `navExtra`).
36
66
  *
37
- * Hidden on the collapsed desktop rail; still present in the mobile drawer, which opens at full
38
- * width. The rail is ~56px of icon buttons with no sensible representation for arbitrary
39
- * consumer content, so the CSS media query that drives the rail hides this slot the same way it
40
- * hides `.childList` — never a JS check on `collapsed`, since that one value is shared by the
41
- * rail AND the drawer (only the media query tells them apart). `SidebarSearch` gets to adapt
42
- * itself to the rail (it takes `collapsed` and renders its own icon-only form) because the shell
43
- * owns that control; it cannot adapt content it knows nothing about, so this slot hides instead
44
- * of squashing it.
67
+ * `placement: 'nav'` appends after `sections` INSIDE the nav scroll region, so a long list
68
+ * scrolls with the nav instead of fighting it for height; `'bottom'` pins above the settings
69
+ * footer. Because these are data and not a `ReactNode` slot, basalt owns both projections a slot
70
+ * could never express: the collapsed rail (a count dot on the icon, a ring on the settings row)
71
+ * and the mobile More sheet — see `sidebarBlockRail` / `sidebarBlockMobile`.
45
72
  */
46
- navExtra?: ReactNode;
73
+ blocks?: SidebarBlock[];
47
74
  };
48
- export declare function AppSidebar({ brand, sections, collapsed, onToggleCollapse, settingsMenuItems, account, search, navExtra, }: AppSidebarProps): import("react").JSX.Element;
75
+ export declare function AppSidebar({ brand, sections, collapsed, onToggleCollapse, settingsMenuItems, settingsMenu, account, search, blocks, }: AppSidebarProps): import("react").JSX.Element;
49
76
  //# sourceMappingURL=app-sidebar.d.ts.map