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,178 @@
1
+ /*
2
+ * `PageBar` layout (docs/CONTROLS-SPEC.md §2.1).
3
+ *
4
+ * Law C7 in CSS form: `flex-wrap: nowrap` on every row and NO `overflow-x` anywhere. A home that
5
+ * scrolls sideways hides its own controls; the fold is computed instead — `More` for actions
6
+ * (`ActionGroup`), a `Filters (n)` sheet for filters (`FilterSet`'s job, wave 4). The filter
7
+ * container below only supplies `min-width: 0` so that fold has a box to measure against.
8
+ */
9
+
10
+ /* The shell-less bar: both rows, sticky at the top of the document. One layer under the AppShell
11
+ * header (`--app-shell-header-z-index`, default 100) so it never wins a stacking fight with ordinary
12
+ * in-flow content — Mantine's Input section, Indicator, sticky table headers, and Slider thumbs all
13
+ * sit at `z-index: 1` or higher, which used to paint over a bare `z-index: 1` bar while scrolling. */
14
+ .bar {
15
+ position: sticky;
16
+ top: 0;
17
+ z-index: calc(var(--app-shell-header-z-index, 100) - 1);
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: var(--vx-space-stack-xs);
21
+ background-color: var(--vx-surface-bg);
22
+ }
23
+
24
+ /*
25
+ * Row 2 inside a shell: in the page flow, sticky under the 48px header. `--app-shell-header-height`
26
+ * is Mantine's own AppShell variable, so the offset tracks the header at any density with no React
27
+ * state and no measurement. `z-index` sits one layer below the header itself (see `.bar` above) —
28
+ * high enough to stay above ordinary page content, never above the AppShell header.
29
+ */
30
+ .row2Sticky {
31
+ position: sticky;
32
+ top: var(--app-shell-header-height, 0px);
33
+ z-index: calc(var(--app-shell-header-z-index, 100) - 1);
34
+ background-color: var(--vx-surface-bg);
35
+ }
36
+
37
+ .row1 {
38
+ display: flex;
39
+ flex-wrap: nowrap;
40
+ align-items: center;
41
+ gap: var(--vx-space-control-gap);
42
+ min-width: 0;
43
+ min-height: var(--vx-space-page-bar-row-height);
44
+ }
45
+
46
+ .lead {
47
+ display: flex;
48
+ flex-wrap: nowrap;
49
+ align-items: center;
50
+ gap: var(--vx-space-control-gap);
51
+ flex-shrink: 0;
52
+ min-width: 0;
53
+ }
54
+
55
+ .icon {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ color: var(--vx-muted);
59
+ }
60
+
61
+ /*
62
+ * The shell-less page title (law C8: the breadcrumb names the page inside a shell, this does it
63
+ * outside one). Head-font treatment, same 88%/550 as every `WidgetHeader` title.
64
+ */
65
+ .title {
66
+ margin: 0;
67
+ font-family: var(--basalt-font-head);
68
+ font-size: var(--vx-text-xl);
69
+ font-weight: 550;
70
+ font-stretch: 88%;
71
+ color: var(--vx-ink);
72
+ white-space: nowrap;
73
+ overflow: hidden;
74
+ text-overflow: ellipsis;
75
+ }
76
+
77
+ /*
78
+ * Icons inside a row-1 button are 16px and sit 6px from their label. Mantine spends
79
+ * `--mantine-spacing-xs` (11px) on a Button's section gap regardless of the size tier, which is the
80
+ * BODY tier's number — at 30px it detached the glyph from the word it belongs to. Scoped to the row
81
+ * rather than themed on `Button` globally: a `size="md"` button in a form still wants Mantine's gap.
82
+ */
83
+ .row1 :global(.mantine-Button-section[data-position='left']),
84
+ .row2 :global(.mantine-Button-section[data-position='left']) {
85
+ margin-inline-end: var(--vx-space-control-gap);
86
+ }
87
+
88
+ .row1 :global(.mantine-Button-section) > svg,
89
+ .row1 :global(.mantine-ActionIcon-root) > svg {
90
+ width: 1rem;
91
+ height: 1rem;
92
+ }
93
+
94
+ /* Actions and sync are right-aligned; the lead (or the breadcrumb, in a shell) holds the left. */
95
+ .row1End {
96
+ display: flex;
97
+ flex-wrap: nowrap;
98
+ align-items: center;
99
+ justify-content: flex-end;
100
+ gap: var(--vx-space-control-gap);
101
+ flex: 1 1 auto;
102
+ min-width: 0;
103
+ }
104
+
105
+ /*
106
+ * Row 2's rhythm is 8px · pills · 8px, NOT a 40px reserved row.
107
+ *
108
+ * `min-height: var(--vx-space-page-bar-row-height)` (40px) left a 30px pill floating inside a 40px
109
+ * box with the 5px above and below it belonging to nothing — and because the row sat in a `Stack`
110
+ * that already spaced it, the visible gap between the header and the pills read as ~19px while the
111
+ * gap from the pills to the first card read as ~19px too. Symmetric padding on the row itself is the
112
+ * same total height (46px vs 40px) with both halves owned by one declaration, so the sticky bar's
113
+ * published height covers exactly the band it paints.
114
+ */
115
+ .row2 {
116
+ display: flex;
117
+ flex-wrap: nowrap;
118
+ align-items: center;
119
+ gap: var(--vx-space-control-gap);
120
+ min-width: 0;
121
+ padding-block: var(--vx-space-stack-sm);
122
+ }
123
+
124
+ .filters {
125
+ display: flex;
126
+ flex-wrap: nowrap;
127
+ align-items: center;
128
+ gap: var(--vx-space-control-gap);
129
+ flex: 1 1 auto;
130
+ min-width: 0;
131
+ }
132
+
133
+ .filtersEnd {
134
+ display: flex;
135
+ flex-wrap: nowrap;
136
+ align-items: center;
137
+ gap: var(--vx-space-control-gap);
138
+ margin-inline-start: auto;
139
+ flex-shrink: 0;
140
+ }
141
+
142
+ /*
143
+ * Law C15 — every touch target inside a home is at least `touchControlHeight` (36px at level 0)
144
+ * below `sm`, while the VISIBLE box stays the 30px `ctl` tier. `::after` and not `::before`:
145
+ * Mantine's Button owns `::before` for its loading overlay (`@mantine/core/styles/Button.css`),
146
+ * so a hit area drawn there would fight it. `pointer-events` stays default so the overlay is the
147
+ * one receiving the tap.
148
+ *
149
+ * `overflow: visible` is LOAD-BEARING, not tidying. Mantine's Button and ActionIcon roots both set
150
+ * `overflow: hidden` (`Button.css`, `ActionIcon.css`), which clips an overlay that by definition
151
+ * extends past the 30px box — so without this the 36px target simply does not exist and a tap 3px
152
+ * above the control misses. What `overflow: hidden` buys those roots is clipping the loading
153
+ * overlay's blur, and that overlay is only painted under `[data-loading]`, which is excluded below.
154
+ */
155
+ @media (max-width: 47.99375em) {
156
+ .row1 :is(button, a),
157
+ .row2 :is(button, a) {
158
+ position: relative;
159
+ }
160
+
161
+ .row1 :is(button, a):not([data-loading]),
162
+ .row2 :is(button, a):not([data-loading]) {
163
+ overflow: visible;
164
+ }
165
+
166
+ .row1 :is(button, a)::after,
167
+ .row2 :is(button, a)::after {
168
+ content: '';
169
+ position: absolute;
170
+ top: 50%;
171
+ left: 50%;
172
+ translate: -50% -50%;
173
+ width: 100%;
174
+ height: 100%;
175
+ min-width: var(--vx-space-touch-control-height);
176
+ min-height: var(--vx-space-touch-control-height);
177
+ }
178
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Typed CSS-module declaration (the S4 CSS-modules path). Named string props — not an index
3
+ * signature — so dot access stays verbatim under the package's strict flags.
4
+ */
5
+ declare const classes: {
6
+ readonly bar: string
7
+ readonly row2Sticky: string
8
+ readonly row1: string
9
+ readonly lead: string
10
+ readonly icon: string
11
+ readonly title: string
12
+ readonly row2: string
13
+ readonly row1End: string
14
+ readonly filters: string
15
+ readonly filtersEnd: string
16
+ }
17
+ export default classes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=page-bar.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-bar.test.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The sidebar-block law — every default a `SidebarBlock` leaves unstated, plus the two persisted
3
+ * fold-key grammars.
4
+ *
5
+ * PURE BY CONTRACT, for the same reason `mobile-nav-model.ts` is: three consumers read these
6
+ * defaults and none of them may disagree. `app-sidebar.tsx` decides where a block renders and
7
+ * whether the rail keeps it, `sidebar-blocks.tsx` paints it, and `mobile-nav-model.ts` counts the
8
+ * More-sheet rows it will produce — a count that picks `menu` vs `sheet` before any of it renders.
9
+ * A default re-derived per call site is how the count and the renderer drift apart (see
10
+ * `accountRowCount`'s doc for the same failure with account rows).
11
+ */
12
+ import type { SidebarBlock } from '../nav/types.js';
13
+ /** Envelope version for a persisted fold flag. Bump only if the value stops being a boolean. */
14
+ export declare const FOLD_VERSION = 1;
15
+ /**
16
+ * `'nav'` renders after the nav sections inside the scroll region; `'bottom'` pins above the
17
+ * settings footer. A progress block is `'bottom'` — it is a pinned status row, not a nav tail.
18
+ */
19
+ export declare function sidebarBlockPlacement(block: SidebarBlock): 'nav' | 'bottom';
20
+ /**
21
+ * The collapsed-rail projection. A list block earns a dot only when it carries a `count` — a rail
22
+ * icon with no number behind it is a dot that means nothing — and a progress block a ring on the
23
+ * settings row. A `custom` block has no rail representation at all (arbitrary content in ~48px of
24
+ * icon column), which is exactly what `sidebarNavExtra` did before it.
25
+ */
26
+ export declare function sidebarBlockRail(block: SidebarBlock): 'dot' | 'ring' | 'hidden';
27
+ /**
28
+ * The mobile projection. A list is reachable from More by default — below `sm` the sidebar does
29
+ * not render at all, so a hidden list is unreachable, not merely unshown. A progress row defaults
30
+ * to `'hidden'`: it is ambient status, and every More row costs headroom the menu-vs-sheet
31
+ * threshold is measured against.
32
+ */
33
+ export declare function sidebarBlockMobile(block: SidebarBlock): 'more' | 'hidden';
34
+ /** Rows a list block paints, honouring `max` — the "Show more" toggle reveals the remainder. */
35
+ export declare function sidebarBlockVisibleCount(itemCount: number, max: number | undefined): number;
36
+ /**
37
+ * A section label as a storage-key segment. Lowercase, every run of non-alphanumerics collapsed to
38
+ * one `-`, no leading/trailing separator — `'Awaiting action'` → `'awaiting-action'`.
39
+ */
40
+ export declare function slugifyLabel(label: string): string;
41
+ /**
42
+ * `createPersistedState` namespaces every key as `basalt:<key>`, so these return the UNNAMESPACED
43
+ * halves — the real storage keys are `basalt:sidebar-block:<key>` and
44
+ * `basalt:sidebar-section:<label-slug>` (`docs/CONTROLS-SPEC.md` §2.3).
45
+ */
46
+ export declare function sidebarBlockFoldKey(blockKey: string): string;
47
+ export declare function sidebarSectionFoldKey(label: string): string;
48
+ //# sourceMappingURL=sidebar-block-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-block-model.d.ts","sourceRoot":"","sources":["../../src/shell/sidebar-block-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,gGAAgG;AAChG,eAAO,MAAM,YAAY,IAAI,CAAA;AAE7B;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,GAAG,QAAQ,CAG3E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAS/E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,QAAQ,CAIzE;AAED,gGAAgG;AAChG,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG3F;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D"}
@@ -0,0 +1,39 @@
1
+ const FOLD_VERSION = 1;
2
+ function sidebarBlockPlacement(block) {
3
+ if (block.kind === "progress") return block.placement ?? "bottom";
4
+ return block.placement ?? "nav";
5
+ }
6
+ function sidebarBlockRail(block) {
7
+ if (block.kind === "custom") return "hidden";
8
+ if (block.kind === "progress") return block.rail ?? "ring";
9
+ if (block.icon === void 0 || block.count === void 0) return "hidden";
10
+ return block.rail ?? "dot";
11
+ }
12
+ function sidebarBlockMobile(block) {
13
+ if (block.kind === "custom") return "hidden";
14
+ if (block.kind === "progress") return block.mobile ?? "hidden";
15
+ return block.mobile ?? "more";
16
+ }
17
+ function sidebarBlockVisibleCount(itemCount, max) {
18
+ if (max === void 0 || max >= itemCount) return itemCount;
19
+ return Math.max(0, max);
20
+ }
21
+ function slugifyLabel(label) {
22
+ return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
23
+ }
24
+ function sidebarBlockFoldKey(blockKey) {
25
+ return `sidebar-block:${blockKey}`;
26
+ }
27
+ function sidebarSectionFoldKey(label) {
28
+ return `sidebar-section:${slugifyLabel(label)}`;
29
+ }
30
+ export {
31
+ FOLD_VERSION,
32
+ sidebarBlockFoldKey,
33
+ sidebarBlockMobile,
34
+ sidebarBlockPlacement,
35
+ sidebarBlockRail,
36
+ sidebarBlockVisibleCount,
37
+ sidebarSectionFoldKey,
38
+ slugifyLabel
39
+ };
@@ -0,0 +1,32 @@
1
+ import type { SidebarBlockTone, SidebarCustomBlock, SidebarListBlock, SidebarProgressBlock } from '../nav/types.js';
2
+ /**
3
+ * Fold/expand chevron. Lives here rather than in `app-sidebar.tsx` purely for direction: that
4
+ * module imports this one, so a glyph shared by a collapsible SECTION header and a collapsible
5
+ * BLOCK header has to sit on this side of the edge or the two files cycle.
6
+ */
7
+ export declare function IconChevron({ open }: {
8
+ open: boolean;
9
+ }): import("react").JSX.Element;
10
+ /**
11
+ * A persisted fold flag (`true` = folded). Exactly one hook call regardless of `key`, so swapping
12
+ * keys mid-life never moves the hook count.
13
+ *
14
+ * Every fold in the sidebar goes through this — block folds at `basalt:sidebar-block:<key>` and
15
+ * nav-section folds at `basalt:sidebar-section:<label-slug>`. Through 1.25.0 the section folds were
16
+ * one `useState` keyed by label, so every reload re-opened a section the user had closed.
17
+ */
18
+ export declare function usePersistedFold(key: string, initial: boolean): readonly [boolean, (next: boolean) => void];
19
+ /** Status dot — the icon fallback for a toned row. Reads `--vx-status-*` through `data-tone`. */
20
+ export declare function SidebarBlockToneDot({ tone }: {
21
+ tone: SidebarBlockTone;
22
+ }): import("react").JSX.Element;
23
+ /** `value` of `total` as a ring — the collapsed rail's stand-in for a progress block. */
24
+ export declare function SidebarProgressRing({ value, total }: {
25
+ value: number;
26
+ total: number;
27
+ }): import("react").JSX.Element;
28
+ /** One block, whichever kind it is. `AppSidebar` places it; this decides nothing but the paint. */
29
+ export declare function SidebarBlockView({ block, }: {
30
+ block: SidebarListBlock | SidebarProgressBlock | SidebarCustomBlock;
31
+ }): import("react").JSX.Element;
32
+ //# sourceMappingURL=sidebar-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-blocks.d.ts","sourceRoot":"","sources":["../../src/shell/sidebar-blocks.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,cAAc,CAAA;AAYrB;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,+BAgBtD;AAsBD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,GACf,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,CAE7C;AAcD,iGAAiG;AACjG,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,+BAEvE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,+BAwBrF;AA4JD,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,GACN,EAAE;IACD,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,kBAAkB,CAAA;CACpE,+BAIA"}
@@ -0,0 +1,169 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Collapse, NavLink, Progress, Text, UnstyledButton } from "@mantine/core";
3
+ import { useState } from "react";
4
+ import { NavCountBadge } from "./nav-count-badge.js";
5
+ import { createPersistedState } from "../state.js";
6
+ import {
7
+ FOLD_VERSION,
8
+ sidebarBlockFoldKey,
9
+ sidebarBlockRail,
10
+ sidebarBlockVisibleCount
11
+ } from "./sidebar-block-model.js";
12
+ import { IconSlot } from "../theme/icon-slot.js";
13
+ import classes from "./app-sidebar.module.css";
14
+ function IconChevron({ open }) {
15
+ return /* @__PURE__ */ jsx(
16
+ "svg",
17
+ {
18
+ width: 12,
19
+ height: 12,
20
+ viewBox: "0 0 24 24",
21
+ fill: "none",
22
+ stroke: "currentColor",
23
+ strokeWidth: 2,
24
+ strokeLinecap: "round",
25
+ strokeLinejoin: "round",
26
+ "aria-hidden": true,
27
+ children: open ? /* @__PURE__ */ jsx("path", { d: "M6 9l6 6l6 -6" }) : /* @__PURE__ */ jsx("path", { d: "M9 6l6 6l-6 6" })
28
+ }
29
+ );
30
+ }
31
+ const foldStores = /* @__PURE__ */ new Map();
32
+ function foldStore(key, initial) {
33
+ const cached = foldStores.get(key);
34
+ if (cached) return cached;
35
+ const store = createPersistedState({ key, version: FOLD_VERSION, initial });
36
+ foldStores.set(key, store);
37
+ return store;
38
+ }
39
+ function usePersistedFold(key, initial) {
40
+ return foldStore(key, initial)();
41
+ }
42
+ function BlockLabel({ children }) {
43
+ return /* @__PURE__ */ jsx(Text, { component: "div", className: classes.sectionLabel, children });
44
+ }
45
+ function SidebarBlockToneDot({ tone }) {
46
+ return /* @__PURE__ */ jsx("span", { className: classes.blockToneDot, "data-tone": tone, "aria-hidden": true });
47
+ }
48
+ function SidebarProgressRing({ value, total }) {
49
+ const CIRCUMFERENCE = 2 * Math.PI * 8;
50
+ const ratio = total > 0 ? Math.min(1, Math.max(0, value / total)) : 0;
51
+ return /* @__PURE__ */ jsx("span", { className: classes.blockRing, "data-basalt-rail-ring": true, children: /* @__PURE__ */ jsxs("svg", { width: 20, height: 20, viewBox: "0 0 20 20", "aria-hidden": true, children: [
52
+ /* @__PURE__ */ jsx("circle", { cx: 10, cy: 10, r: 8, fill: "none", stroke: "var(--vx-divider)", strokeWidth: 2 }),
53
+ /* @__PURE__ */ jsx(
54
+ "circle",
55
+ {
56
+ cx: 10,
57
+ cy: 10,
58
+ r: 8,
59
+ fill: "none",
60
+ stroke: "var(--vx-accent)",
61
+ strokeWidth: 2,
62
+ strokeLinecap: "round",
63
+ strokeDasharray: CIRCUMFERENCE,
64
+ strokeDashoffset: CIRCUMFERENCE * (1 - ratio),
65
+ transform: "rotate(-90 10 10)"
66
+ }
67
+ )
68
+ ] }) });
69
+ }
70
+ function BlockRow({ item }) {
71
+ const rawLead = item.icon ?? (item.tone !== void 0 ? /* @__PURE__ */ jsx(SidebarBlockToneDot, { tone: item.tone }) : void 0);
72
+ const lead = rawLead === void 0 ? void 0 : /* @__PURE__ */ jsx(IconSlot, { children: rawLead });
73
+ const meta = item.meta !== void 0 ? /* @__PURE__ */ jsx(Text, { component: "span", className: classes.blockRowMeta, children: item.meta }) : void 0;
74
+ const interactive = item.Anchor !== void 0 || item.href !== void 0 || item.onClick !== void 0;
75
+ if (!interactive) {
76
+ return /* @__PURE__ */ jsxs("div", { className: `${classes.blockRow} ${classes.blockRowStatic}`, children: [
77
+ lead,
78
+ /* @__PURE__ */ jsx(Text, { component: "span", className: classes.blockRowLabel, children: item.label }),
79
+ meta
80
+ ] });
81
+ }
82
+ const shared = {
83
+ classNames: { root: classes.blockRow },
84
+ label: item.label,
85
+ ...lead !== void 0 && { leftSection: lead },
86
+ ...meta !== void 0 && { rightSection: meta },
87
+ ...item.onClick !== void 0 && { onClick: item.onClick }
88
+ };
89
+ const Anchor = item.Anchor;
90
+ if (Anchor) return /* @__PURE__ */ jsx(NavLink, { component: Anchor, ...shared });
91
+ return /* @__PURE__ */ jsx(NavLink, { component: "a", ...item.href !== void 0 && { href: item.href }, ...shared });
92
+ }
93
+ function ListBlock({ block }) {
94
+ const [folded, setFolded] = usePersistedFold(sidebarBlockFoldKey(block.key), false);
95
+ const [showAll, setShowAll] = useState(false);
96
+ const rail = sidebarBlockRail(block);
97
+ const visible = showAll ? block.items.length : sidebarBlockVisibleCount(block.items.length, block.max);
98
+ const hidden = block.items.length - visible;
99
+ const label = /* @__PURE__ */ jsxs(Fragment, { children: [
100
+ block.icon !== void 0 && /* @__PURE__ */ jsxs("span", { className: classes.blockIcon, children: [
101
+ block.icon,
102
+ rail === "dot" && /* @__PURE__ */ jsx("span", { className: classes.blockDot, "aria-hidden": true })
103
+ ] }),
104
+ /* @__PURE__ */ jsx(BlockLabel, { children: block.label }),
105
+ block.count ? /* @__PURE__ */ jsx(NavCountBadge, { count: block.count }) : null
106
+ ] });
107
+ const body = /* @__PURE__ */ jsxs("div", { className: classes.blockItems, children: [
108
+ block.items.slice(0, visible).map((item) => /* @__PURE__ */ jsx(BlockRow, { item }, item.key)),
109
+ hidden > 0 || showAll ? /* @__PURE__ */ jsx(
110
+ UnstyledButton,
111
+ {
112
+ className: classes.blockMore,
113
+ onClick: () => setShowAll(!showAll),
114
+ "aria-expanded": showAll,
115
+ children: showAll ? "Show less" : "Show more"
116
+ }
117
+ ) : null
118
+ ] });
119
+ return /* @__PURE__ */ jsx("div", { className: classes.block, "data-rail": rail, children: block.collapsible === true ? /* @__PURE__ */ jsxs(Fragment, { children: [
120
+ /* @__PURE__ */ jsxs(
121
+ UnstyledButton,
122
+ {
123
+ className: `${classes.blockHeader} ${classes.blockHeaderButton}`,
124
+ onClick: () => setFolded(!folded),
125
+ "aria-expanded": !folded,
126
+ children: [
127
+ label,
128
+ /* @__PURE__ */ jsx(IconChevron, { open: !folded })
129
+ ]
130
+ }
131
+ ),
132
+ /* @__PURE__ */ jsx(Collapse, { expanded: !folded, children: body })
133
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
134
+ /* @__PURE__ */ jsx("div", { className: classes.blockHeader, children: label }),
135
+ body
136
+ ] }) });
137
+ }
138
+ function ProgressBlock({ block }) {
139
+ const percent = block.total > 0 ? Math.min(100, Math.max(0, block.value / block.total * 100)) : 0;
140
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
141
+ /* @__PURE__ */ jsxs("div", { className: classes.blockProgressHead, children: [
142
+ /* @__PURE__ */ jsx(BlockLabel, { children: block.label }),
143
+ /* @__PURE__ */ jsxs(Text, { component: "span", className: classes.blockRowMeta, children: [
144
+ block.value,
145
+ " of ",
146
+ block.total
147
+ ] })
148
+ ] }),
149
+ /* @__PURE__ */ jsx(Progress, { value: percent, size: "xs", "aria-label": block.label })
150
+ ] });
151
+ return /* @__PURE__ */ jsx("div", { className: classes.block, "data-rail": sidebarBlockRail(block), children: block.onClick !== void 0 ? /* @__PURE__ */ jsx(UnstyledButton, { className: classes.blockProgress, onClick: block.onClick, children: inner }) : /* @__PURE__ */ jsx("div", { className: classes.blockProgress, children: inner }) });
152
+ }
153
+ function CustomBlock({ block }) {
154
+ return /* @__PURE__ */ jsx("div", { className: classes.block, "data-rail": "hidden", children: block.node });
155
+ }
156
+ function SidebarBlockView({
157
+ block
158
+ }) {
159
+ if (block.kind === "list") return /* @__PURE__ */ jsx(ListBlock, { block });
160
+ if (block.kind === "progress") return /* @__PURE__ */ jsx(ProgressBlock, { block });
161
+ return /* @__PURE__ */ jsx(CustomBlock, { block });
162
+ }
163
+ export {
164
+ IconChevron,
165
+ SidebarBlockToneDot,
166
+ SidebarBlockView,
167
+ SidebarProgressRing,
168
+ usePersistedFold
169
+ };
@@ -1,3 +1,14 @@
1
+ import type { BarAction } from '../controls/actions.js';
2
+ /**
3
+ * The ⌘K row's trailing icon buttons — ONE or TWO, by type (`docs/CONTROLS-SPEC.md` §2.3).
4
+ *
5
+ * The tuple IS the enforcement: a third button pushes the search trigger under the width where its
6
+ * placeholder stays readable, and there is no third thing a sidebar search row is for. Each entry
7
+ * renders icon-only at the `icon` tier (24px, `--ai-size-icon`) with `label` demoted to the
8
+ * accessible name; a `kind: 'menu'` entry becomes an `OverflowMenu` kebab, a `kind: 'custom'` one
9
+ * is mounted verbatim.
10
+ */
11
+ export type SidebarSearchActions = [BarAction] | [BarAction, BarAction];
1
12
  export type SidebarSearchConfig = {
2
13
  /** Opens the search palette — e.g. `openSpotlight` from `basalt-ui/commands`. */
3
14
  onOpen: () => void;
@@ -6,7 +17,8 @@ export type SidebarSearchConfig = {
6
17
  /** Keyboard-shortcut hint shown on the right (e.g. '⌘K'). Auto-detected mac/other when omitted. */
7
18
  shortcut?: string;
8
19
  };
9
- export declare function SidebarSearch({ onOpen, placeholder, shortcut, collapsed, }: SidebarSearchConfig & {
20
+ export declare function SidebarSearch({ onOpen, placeholder, shortcut, actions, collapsed, }: SidebarSearchConfig & {
21
+ actions?: SidebarSearchActions;
10
22
  collapsed?: boolean;
11
23
  }): import("react").JSX.Element;
12
24
  //# sourceMappingURL=sidebar-search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar-search.d.ts","sourceRoot":"","sources":["../../src/shell/sidebar-search.tsx"],"names":[],"mappings":"AAYA,MAAM,MAAM,mBAAmB,GAAG;IAChC,iFAAiF;IACjF,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAsBD,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,WAAuB,EACvB,QAAQ,EACR,SAAS,GACV,EAAE,mBAAmB,GAAG;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,+BAiC/C"}
1
+ {"version":3,"file":"sidebar-search.d.ts","sourceRoot":"","sources":["../../src/shell/sidebar-search.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAGpD;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;AAEvE,MAAM,MAAM,mBAAmB,GAAG;IAChC,iFAAiF;IACjF,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAgED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,WAAuB,EACvB,QAAQ,EACR,OAAO,EACP,SAAS,GACV,EAAE,mBAAmB,GAAG;IAAE,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,+BA6C/E"}
@@ -1,5 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { ActionIcon, Kbd, Text, UnstyledButton } from "@mantine/core";
3
+ import { OverflowMenu } from "../controls/actions.js";
3
4
  import classes from "./sidebar-search.module.css";
4
5
  function IconSearch() {
5
6
  return /* @__PURE__ */ jsxs(
@@ -21,10 +22,37 @@ function IconSearch() {
21
22
  }
22
23
  );
23
24
  }
25
+ function initial(label) {
26
+ return [...label][0]?.toUpperCase() ?? "?";
27
+ }
28
+ function SearchAction({ action }) {
29
+ if (action.kind === "custom") return action.node;
30
+ if (action.kind === "menu") {
31
+ return /* @__PURE__ */ jsx(OverflowMenu, { actions: action.items, trigger: "kebab", label: action.label });
32
+ }
33
+ const shared = {
34
+ variant: "subtle",
35
+ size: "icon",
36
+ className: classes.actionBtn,
37
+ "aria-label": action.label,
38
+ disabled: action.disabled === true,
39
+ ...action.onClick !== void 0 && { onClick: action.onClick }
40
+ };
41
+ const body = action.icon ?? initial(action.label);
42
+ const Anchor = action.Anchor;
43
+ if (Anchor !== void 0) {
44
+ return /* @__PURE__ */ jsx(ActionIcon, { component: Anchor, ...shared, children: body });
45
+ }
46
+ if (action.link !== void 0) {
47
+ return /* @__PURE__ */ jsx(ActionIcon, { component: "a", href: action.link.to, ...shared, children: body });
48
+ }
49
+ return /* @__PURE__ */ jsx(ActionIcon, { ...shared, children: body });
50
+ }
24
51
  function SidebarSearch({
25
52
  onOpen,
26
53
  placeholder = "Search\u2026",
27
54
  shortcut,
55
+ actions,
28
56
  collapsed
29
57
  }) {
30
58
  const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPod|iPad/i.test(navigator.platform);
@@ -42,7 +70,7 @@ function SidebarSearch({
42
70
  }
43
71
  );
44
72
  }
45
- return /* @__PURE__ */ jsxs(
73
+ const trigger = /* @__PURE__ */ jsxs(
46
74
  UnstyledButton,
47
75
  {
48
76
  type: "button",
@@ -56,6 +84,11 @@ function SidebarSearch({
56
84
  ]
57
85
  }
58
86
  );
87
+ if (actions === void 0) return trigger;
88
+ return /* @__PURE__ */ jsxs("div", { className: classes.row, children: [
89
+ trigger,
90
+ actions.map((action) => /* @__PURE__ */ jsx(SearchAction, { action }, action.key))
91
+ ] });
59
92
  }
60
93
  export {
61
94
  SidebarSearch
@@ -5,6 +5,18 @@
5
5
  * button as a matched pair, so the two must never drift onto different depth tokens.
6
6
  */
7
7
 
8
+ /*
9
+ * The row that appears ONLY when `search.actions` is supplied — one or two icon-only buttons beside
10
+ * the trigger. Without actions the trigger stays the row's sole node (no wrapper), so the DOM of
11
+ * every consumer that doesn't use them is untouched.
12
+ */
13
+ .row {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: var(--vx-space-sidebar-search-gap);
17
+ min-width: 0;
18
+ }
19
+
8
20
  .trigger {
9
21
  display: flex;
10
22
  align-items: center;
@@ -74,3 +86,17 @@
74
86
  background-color: color-mix(in srgb, var(--vx-ink) 6%, transparent) !important;
75
87
  color: var(--vx-ink) !important;
76
88
  }
89
+
90
+ /* Trailing search-row action — the same ghost treatment as `.ghostIcon` / `.railBtn`, at the `icon`
91
+ * tier (24px) the component asks for explicitly. Never re-points `--ai-size`: unlike `.railBtn`,
92
+ * this button is NOT a second state of the trigger and must not track its height. */
93
+ .actionBtn {
94
+ flex: none;
95
+ color: var(--vx-faint) !important;
96
+ border-radius: var(--vx-radius-ctrl) !important;
97
+ }
98
+
99
+ .actionBtn:hover {
100
+ background-color: color-mix(in srgb, var(--vx-ink) 6%, transparent) !important;
101
+ color: var(--vx-ink) !important;
102
+ }
@@ -4,8 +4,10 @@
4
4
  * (`noUncheckedIndexedAccess`, `noPropertyAccessFromIndexSignature`, `exactOptionalPropertyTypes`).
5
5
  */
6
6
  declare const classes: {
7
+ readonly row: string
7
8
  readonly trigger: string
8
9
  readonly label: string
9
10
  readonly railBtn: string
11
+ readonly actionBtn: string
10
12
  }
11
13
  export default classes