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,146 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button, Drawer } from "@mantine/core";
3
+ import { useId } from "react";
4
+ import classes from "./controls.module.css";
5
+ function FilterSheet({
6
+ opened,
7
+ onClose,
8
+ onResetAll,
9
+ title = "Filters",
10
+ children
11
+ }) {
12
+ return /* @__PURE__ */ jsx(
13
+ Drawer,
14
+ {
15
+ opened,
16
+ onClose,
17
+ position: "bottom",
18
+ radius: "sm",
19
+ classNames: { header: classes.sheetHeader, title: classes.sheetTitle },
20
+ title: /* @__PURE__ */ jsxs(Fragment, { children: [
21
+ /* @__PURE__ */ jsx("span", { className: classes.sheetTitleText, children: title }),
22
+ /* @__PURE__ */ jsx(Button, { variant: "subtle", size: "ctl", onClick: onResetAll, children: "Reset all" })
23
+ ] }),
24
+ children: /* @__PURE__ */ jsx("div", { className: classes.sheetBody, children })
25
+ }
26
+ );
27
+ }
28
+ function CheckGlyph() {
29
+ return /* @__PURE__ */ jsx(
30
+ "svg",
31
+ {
32
+ width: 16,
33
+ height: 16,
34
+ viewBox: "0 0 24 24",
35
+ fill: "none",
36
+ stroke: "currentColor",
37
+ strokeWidth: 2.25,
38
+ strokeLinecap: "round",
39
+ strokeLinejoin: "round",
40
+ "aria-hidden": true,
41
+ children: /* @__PURE__ */ jsx("path", { d: "M5 12.5l5 5l9 -11" })
42
+ }
43
+ );
44
+ }
45
+ function SheetOptionList({
46
+ mode,
47
+ selected,
48
+ options,
49
+ onToggle,
50
+ labelId
51
+ }) {
52
+ const name = useId();
53
+ return (
54
+ // A real `<fieldset>`, not `role="group"` on a div — the native element carries the grouping
55
+ // semantics and gets its own box reset in the module (`.sheetList`). `aria-labelledby` points at
56
+ // the `SheetField` heading rather than a `<legend>`: the heading is already visible above the
57
+ // list, and a legend would render it a second time.
58
+ /* @__PURE__ */ jsx("fieldset", { className: classes.sheetList, "aria-labelledby": labelId, children: options.map((option) => {
59
+ const isSelected = selected.includes(option.value);
60
+ return /* @__PURE__ */ jsxs(
61
+ "label",
62
+ {
63
+ className: classes.sheetOption,
64
+ ...isSelected && { "data-selected": true },
65
+ ...option.disabled === true && { "data-disabled": true },
66
+ children: [
67
+ /* @__PURE__ */ jsx(
68
+ "input",
69
+ {
70
+ className: classes.sheetOptionInput,
71
+ type: mode === "single" ? "radio" : "checkbox",
72
+ name,
73
+ value: option.value,
74
+ checked: isSelected,
75
+ disabled: option.disabled === true,
76
+ "aria-label": option.label,
77
+ onChange: () => {
78
+ onToggle(option.value);
79
+ }
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx("span", { className: classes.sheetOptionLabel, children: option.label }),
83
+ isSelected && /* @__PURE__ */ jsx("span", { className: classes.sheetOptionCheck, "aria-hidden": true, children: /* @__PURE__ */ jsx(CheckGlyph, {}) })
84
+ ]
85
+ },
86
+ option.value
87
+ );
88
+ }) })
89
+ );
90
+ }
91
+ function SheetDisclosure({
92
+ label,
93
+ expanded,
94
+ onToggle,
95
+ children
96
+ }) {
97
+ return /* @__PURE__ */ jsxs("div", { className: classes.sheetList, children: [
98
+ /* @__PURE__ */ jsxs(
99
+ "button",
100
+ {
101
+ type: "button",
102
+ className: classes.sheetOption,
103
+ "aria-expanded": expanded,
104
+ ...expanded && { "data-selected": true },
105
+ onClick: onToggle,
106
+ children: [
107
+ /* @__PURE__ */ jsx("span", { className: classes.sheetOptionLabel, children: label }),
108
+ expanded && /* @__PURE__ */ jsx("span", { className: classes.sheetOptionCheck, "aria-hidden": true, children: /* @__PURE__ */ jsx(CheckGlyph, {}) })
109
+ ]
110
+ }
111
+ ),
112
+ expanded && /* @__PURE__ */ jsx("div", { className: classes.sheetDisclosureBody, children })
113
+ ] });
114
+ }
115
+ function SheetField({ label, labelId, children }) {
116
+ return /* @__PURE__ */ jsxs("div", { className: classes.sheetField, children: [
117
+ /* @__PURE__ */ jsx("span", { className: classes.sheetLabel, ...labelId !== void 0 && { id: labelId }, children: label }),
118
+ children
119
+ ] });
120
+ }
121
+ function useControlName(label, inSheet) {
122
+ const labelId = useId();
123
+ return {
124
+ labelId,
125
+ nameProps: inSheet ? { "aria-labelledby": labelId } : { "aria-label": label }
126
+ };
127
+ }
128
+ function SheetRow({ children }) {
129
+ return /* @__PURE__ */ jsx("div", { className: classes.sheetRow, children });
130
+ }
131
+ const sheetRowClass = classes.sheetRow;
132
+ const sheetRowClassNames = {
133
+ root: classes.sheetRow,
134
+ body: classes.sheetRowBody,
135
+ label: classes.sheetRowLabel
136
+ };
137
+ export {
138
+ FilterSheet,
139
+ SheetDisclosure,
140
+ SheetField,
141
+ SheetOptionList,
142
+ SheetRow,
143
+ sheetRowClass,
144
+ sheetRowClassNames,
145
+ useControlName
146
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The control tier's own inline glyphs (`docs/CONTROLS-SPEC.md` §3).
3
+ *
4
+ * basalt ships NO icon dependency — every `icon` slot in the public API takes a `ReactNode` the
5
+ * consumer fills. These are the few glyphs the FRAMEWORK draws for itself, where a default icon is
6
+ * part of the control's identity rather than a caller's decision: a range filter reads as a date
7
+ * picker because it carries a calendar, a compare filter because it carries the swap arrows, and
8
+ * the mobile `Filters (n)` pill because it carries a funnel. Same convention the shell already uses
9
+ * for its own chrome (`shell/sidebar-search.tsx`, `shell/app-sidebar.tsx`): a 24×24 `viewBox`,
10
+ * `stroke="currentColor"`, sized down by the `size` prop.
11
+ *
12
+ * INTERNAL — not on the `./controls` barrel. A consumer wanting a different glyph passes `icon`.
13
+ */
14
+ import type { ReactNode } from 'react';
15
+ /** `RangeFilter`'s default leading icon. */
16
+ export declare function CalendarGlyph(): ReactNode;
17
+ /** `CompareFilter`'s default leading icon — the arrows-swap glyph. */
18
+ export declare function SwapGlyph(): ReactNode;
19
+ /** The mobile `Filters (n)` pill's leading icon. */
20
+ export declare function FunnelGlyph(): ReactNode;
21
+ /**
22
+ * Every pill's trailing affordance — 14px, muted, and an SVG rather than the `⇅` character it
23
+ * replaced. A text glyph rendered at the label's own font metrics, so it sat off the optical centre
24
+ * and shifted with the font stack; the path is centred in its own box.
25
+ */
26
+ export declare function UpDownGlyph(): ReactNode;
27
+ //# sourceMappingURL=glyphs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyphs.d.ts","sourceRoot":"","sources":["../../src/controls/glyphs.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAwBtC,4CAA4C;AAC5C,wBAAgB,aAAa,IAAI,SAAS,CASzC;AAED,sEAAsE;AACtE,wBAAgB,SAAS,IAAI,SAAS,CAOrC;AAED,oDAAoD;AACpD,wBAAgB,WAAW,IAAI,SAAS,CAMvC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,SAAS,CAOvC"}
@@ -0,0 +1,49 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ const ICON_SIZE = 16;
3
+ const AFFORDANCE_SIZE = 14;
4
+ function Stroke({ size = ICON_SIZE, children }) {
5
+ return /* @__PURE__ */ jsx(
6
+ "svg",
7
+ {
8
+ width: size,
9
+ height: size,
10
+ viewBox: "0 0 24 24",
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ strokeWidth: 1.75,
14
+ strokeLinecap: "round",
15
+ strokeLinejoin: "round",
16
+ "aria-hidden": true,
17
+ children
18
+ }
19
+ );
20
+ }
21
+ function CalendarGlyph() {
22
+ return /* @__PURE__ */ jsxs(Stroke, { children: [
23
+ /* @__PURE__ */ jsx("rect", { x: 3, y: 5, width: 18, height: 16, rx: 2.5 }),
24
+ /* @__PURE__ */ jsx("path", { d: "M3 10h18" }),
25
+ /* @__PURE__ */ jsx("path", { d: "M8 3v4" }),
26
+ /* @__PURE__ */ jsx("path", { d: "M16 3v4" })
27
+ ] });
28
+ }
29
+ function SwapGlyph() {
30
+ return /* @__PURE__ */ jsxs(Stroke, { children: [
31
+ /* @__PURE__ */ jsx("path", { d: "M4 8h13l-3.5 -3.5" }),
32
+ /* @__PURE__ */ jsx("path", { d: "M20 16h-13l3.5 3.5" })
33
+ ] });
34
+ }
35
+ function FunnelGlyph() {
36
+ return /* @__PURE__ */ jsx(Stroke, { children: /* @__PURE__ */ jsx("path", { d: "M4 4h16l-6.5 8v7l-3 -1.5v-5.5z" }) });
37
+ }
38
+ function UpDownGlyph() {
39
+ return /* @__PURE__ */ jsxs(Stroke, { size: AFFORDANCE_SIZE, children: [
40
+ /* @__PURE__ */ jsx("path", { d: "M8 9l4 -4l4 4" }),
41
+ /* @__PURE__ */ jsx("path", { d: "M8 15l4 4l4 -4" })
42
+ ] });
43
+ }
44
+ export {
45
+ CalendarGlyph,
46
+ FunnelGlyph,
47
+ SwapGlyph,
48
+ UpDownGlyph
49
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `basalt-ui/controls` — the Mantine-coupled control tier (`docs/CONTROLS-SPEC.md` §3).
3
+ *
4
+ * Every control here renders at the `ctl` tier from the home that hosts it and owns its own
5
+ * desktop/mobile swap in CSS (law C9). Actions and sync land in wave 3; the filter/tab family
6
+ * follows in wave 4.
7
+ */
8
+ export { ActionGroup, OverflowMenu, DESKTOP_SECONDARY_MAX, MOBILE_GLOBAL_BAR_MAX, barActionMobile, globalActionMobile, type BarAction, type BarActionItem, type BarActionMenu, type BarActionCustom, type ActionGroupProps, type GlobalAction, type OverflowMenuProps, } from './actions.js';
9
+ export { SyncButton, formatAge, AGE_REFRESH_MS, type SyncButtonProps } from './sync-button.js';
10
+ export { ControlGroup, type ControlGroupProps } from './control-group.js';
11
+ export { FilterSet, type FilterSetProps } from './filter-set.js';
12
+ export { RangeFilter, type RangeCustomPickerProps, type RangeFilterProps } from './range-filter.js';
13
+ export { CompareFilter, COMPARE_VALUES, type CompareFilterProps, type CompareValue, } from './compare-filter.js';
14
+ export { SelectFilter, type FilterOption, type SelectFilterEnumProps, type SelectFilterProps, type SelectFilterStringProps, } from './select-filter.js';
15
+ export { MultiSelectFilter, type MultiSelectFilterProps } from './multi-select-filter.js';
16
+ export { NumberFilter, type NumberFilterOption, type NumberFilterProps } from './number-filter.js';
17
+ export { SearchFilter, type SearchFilterProps } from './search-filter.js';
18
+ export { ToggleFilter, type ToggleFilterProps } from './toggle-filter.js';
19
+ export { ViewTabs, type ViewTabsOption, type ViewTabsProps } from './view-tabs.js';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controls/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAMtE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAChG,OAAO,EACL,aAAa,EACb,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACtF,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC/F,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,46 @@
1
+ import {
2
+ ActionGroup,
3
+ OverflowMenu,
4
+ DESKTOP_SECONDARY_MAX,
5
+ MOBILE_GLOBAL_BAR_MAX,
6
+ barActionMobile,
7
+ globalActionMobile
8
+ } from "./actions.js";
9
+ import { SyncButton, formatAge, AGE_REFRESH_MS } from "./sync-button.js";
10
+ import { ControlGroup } from "./control-group.js";
11
+ import { FilterSet } from "./filter-set.js";
12
+ import { RangeFilter } from "./range-filter.js";
13
+ import {
14
+ CompareFilter,
15
+ COMPARE_VALUES
16
+ } from "./compare-filter.js";
17
+ import {
18
+ SelectFilter
19
+ } from "./select-filter.js";
20
+ import { MultiSelectFilter } from "./multi-select-filter.js";
21
+ import { NumberFilter } from "./number-filter.js";
22
+ import { SearchFilter } from "./search-filter.js";
23
+ import { ToggleFilter } from "./toggle-filter.js";
24
+ import { ViewTabs } from "./view-tabs.js";
25
+ export {
26
+ AGE_REFRESH_MS,
27
+ ActionGroup,
28
+ COMPARE_VALUES,
29
+ CompareFilter,
30
+ ControlGroup,
31
+ DESKTOP_SECONDARY_MAX,
32
+ FilterSet,
33
+ MOBILE_GLOBAL_BAR_MAX,
34
+ MultiSelectFilter,
35
+ NumberFilter,
36
+ OverflowMenu,
37
+ RangeFilter,
38
+ SearchFilter,
39
+ SelectFilter,
40
+ SyncButton,
41
+ ToggleFilter,
42
+ ViewTabs,
43
+ barActionMobile,
44
+ formatAge,
45
+ globalActionMobile
46
+ };
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { FieldHandle, MultiField } from '../state.js';
3
+ import type { FilterOption } from './select-filter.js';
4
+ export type MultiSelectFilterProps<T extends string> = {
5
+ readonly field: FieldHandle<MultiField<T>>;
6
+ readonly label: string;
7
+ readonly icon?: ReactNode;
8
+ /**
9
+ * The plural noun in the `3 channels` count.
10
+ *
11
+ * @default the label, lowercased
12
+ */
13
+ readonly noun?: string;
14
+ /**
15
+ * Overrides `field.options` at render — a runtime catalogue whose labels carry live data
16
+ * (`web · 1.2k`). The VALUES still belong to the field: a multi field is a closed set, so this
17
+ * relabels and reorders rows, it does not open new ones. Unlike `SelectFilter` there is no
18
+ * string-field shape to make it required.
19
+ */
20
+ readonly options?: readonly FilterOption[];
21
+ };
22
+ export declare function MultiSelectFilter<T extends string>({ field, label, icon, noun, options: optionsProp, }: MultiSelectFilterProps<T>): ReactNode;
23
+ //# sourceMappingURL=multi-select-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-select-filter.d.ts","sourceRoot":"","sources":["../../src/controls/multi-select-filter.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAMnD,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,MAAM,IAAI;IACrD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;CAC3C,CAAA;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,EAClD,KAAK,EACL,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,OAAO,EAAE,WAAW,GACrB,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,SAAS,CAsFvC"}
@@ -0,0 +1,89 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button, Checkbox, Stack } from "@mantine/core";
3
+ import classes from "./controls.module.css";
4
+ import { useFilterRegistration, useFilterSurface } from "./filter-context.js";
5
+ import { FilterPill } from "./filter-pill.js";
6
+ import { SheetField, SheetOptionList, useControlName } from "./filter-sheet.js";
7
+ function MultiSelectFilter({
8
+ field,
9
+ label,
10
+ icon,
11
+ noun,
12
+ options: optionsProp
13
+ }) {
14
+ const [value, setValue] = field.use();
15
+ const surface = useFilterSurface();
16
+ const isDefault = field.isDefault(value);
17
+ useFilterRegistration(!isDefault, () => {
18
+ field.clear();
19
+ });
20
+ const options = optionsProp ?? field.options;
21
+ const inSheet = surface === "sheet";
22
+ const carriesInformation = value.length > 0 && value.length < options.length;
23
+ const { labelId } = useControlName(label, inSheet);
24
+ if (inSheet) {
25
+ return /* @__PURE__ */ jsx(SheetField, { label, labelId, children: /* @__PURE__ */ jsx(
26
+ SheetOptionList,
27
+ {
28
+ mode: "multi",
29
+ labelId,
30
+ selected: value,
31
+ options,
32
+ onToggle: (next) => {
33
+ const has = value.includes(next);
34
+ setValue(has ? value.filter((v) => v !== next) : [...value, next]);
35
+ }
36
+ }
37
+ ) });
38
+ }
39
+ const body = (
40
+ // Mantine's own `label`, for the same reason `enum-filter.tsx` uses it — `Checkbox.Group`
41
+ // overwrites any `aria-label`/`aria-labelledby` with its own. Named from `label`, never from the
42
+ // pill text, which reads `3 channels` and describes the selection rather than the filter.
43
+ /* @__PURE__ */ jsxs(
44
+ Checkbox.Group,
45
+ {
46
+ label,
47
+ classNames: { label: classes.groupLabel },
48
+ value: [...value],
49
+ onChange: (next) => {
50
+ setValue(next);
51
+ },
52
+ children: [
53
+ /* @__PURE__ */ jsx(Stack, { gap: 2, children: options.map((option) => /* @__PURE__ */ jsx(
54
+ Checkbox,
55
+ {
56
+ value: option.value,
57
+ label: option.label,
58
+ ...option.disabled === true && { disabled: true }
59
+ },
60
+ option.value
61
+ )) }),
62
+ !isDefault && /* @__PURE__ */ jsx(
63
+ Button,
64
+ {
65
+ variant: "subtle",
66
+ size: "ctl",
67
+ onClick: () => {
68
+ field.clear();
69
+ },
70
+ children: "Clear"
71
+ }
72
+ )
73
+ ]
74
+ }
75
+ )
76
+ );
77
+ return /* @__PURE__ */ jsx(
78
+ FilterPill,
79
+ {
80
+ label: carriesInformation ? `${value.length} ${noun ?? label.toLowerCase()}` : label,
81
+ active: !isDefault,
82
+ ...icon !== void 0 && { icon },
83
+ children: /* @__PURE__ */ jsx("div", { className: classes.optionList, children: body })
84
+ }
85
+ );
86
+ }
87
+ export {
88
+ MultiSelectFilter
89
+ };
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { FieldHandle, NumberField } from '../state.js';
3
+ /** One row of a numeric preset set. Narrower than `FilterOption` — a preset is never `disabled`:
4
+ * the set is declared at the call site, so a row that should not be offered is left out. */
5
+ export type NumberFilterOption = {
6
+ readonly value: number;
7
+ readonly label: string;
8
+ };
9
+ export type NumberFilterProps = {
10
+ readonly field: FieldHandle<NumberField>;
11
+ /** The popover/sheet heading and the accessible name — never the pill text, which reads the
12
+ * VALUE (see `EnumFilter`'s doc for why a pill may only print what is selected). */
13
+ readonly label: string;
14
+ readonly icon?: ReactNode;
15
+ /** Present → the radio-list form. Absent → the stepper form. */
16
+ readonly options?: readonly NumberFilterOption[];
17
+ /**
18
+ * The stepper's increment. Ignored in the `options` form, which has no stepper.
19
+ *
20
+ * @default 1 — and for an `int: true` field that is also the only sane grain, so it is what the
21
+ * field's own declaration produces without this prop.
22
+ */
23
+ readonly step?: number;
24
+ };
25
+ export declare function NumberFilter({ field, label, icon, options, step }: NumberFilterProps): ReactNode;
26
+ //# sourceMappingURL=number-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number-filter.d.ts","sourceRoot":"","sources":["../../src/controls/number-filter.tsx"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAQxD;6FAC6F;AAC7F,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,CAAA;IACxC;yFACqF;IACrF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,iBAAiB,GAAG,SAAS,CAsBhG"}
@@ -0,0 +1,143 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { NumberInput } from "@mantine/core";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import classes from "./controls.module.css";
5
+ import { EnumFilter } from "./enum-filter.js";
6
+ import { useFilterRegistration, useFilterSurface } from "./filter-context.js";
7
+ import { FilterPill } from "./filter-pill.js";
8
+ import { SheetField, useControlName } from "./filter-sheet.js";
9
+ function NumberFilter({ field, label, icon, options, step }) {
10
+ if (options !== void 0) {
11
+ return /* @__PURE__ */ jsx(
12
+ NumberChoice,
13
+ {
14
+ field,
15
+ label,
16
+ options,
17
+ ...icon !== void 0 && { icon }
18
+ }
19
+ );
20
+ }
21
+ return /* @__PURE__ */ jsx(
22
+ NumberStepper,
23
+ {
24
+ field,
25
+ label,
26
+ ...icon !== void 0 && { icon },
27
+ ...step !== void 0 && { step }
28
+ }
29
+ );
30
+ }
31
+ function NumberChoice({
32
+ field,
33
+ label,
34
+ icon,
35
+ options
36
+ }) {
37
+ const [value, setValue] = field.use();
38
+ const rows = options.map((option) => ({ value: String(option.value), label: option.label }));
39
+ const choice = {
40
+ fallback: String(field.fallback),
41
+ options: rows,
42
+ use: () => [
43
+ String(value),
44
+ (next) => {
45
+ setValue(Number(next));
46
+ }
47
+ ],
48
+ isDefault: (v) => field.isDefault(Number(v)),
49
+ clear: () => {
50
+ field.clear();
51
+ }
52
+ };
53
+ return /* @__PURE__ */ jsx(
54
+ EnumFilter,
55
+ {
56
+ field: choice,
57
+ label,
58
+ numeric: true,
59
+ options: rows,
60
+ ...icon !== void 0 && { icon }
61
+ }
62
+ );
63
+ }
64
+ function NumberStepper({
65
+ field,
66
+ label,
67
+ icon,
68
+ step
69
+ }) {
70
+ const [value, setValue] = field.use();
71
+ const surface = useFilterSurface();
72
+ const inSheet = surface === "sheet";
73
+ const isDefault = field.isDefault(value);
74
+ useFilterRegistration(!isDefault, () => {
75
+ field.clear();
76
+ });
77
+ const { labelId, nameProps } = useControlName(label, inSheet);
78
+ const draft = useCommittedNumber(value, setValue, { min: field.min, max: field.max });
79
+ const input = /* @__PURE__ */ jsx(
80
+ NumberInput,
81
+ {
82
+ ...nameProps,
83
+ size: "ctl",
84
+ ...field.min !== void 0 && { min: field.min },
85
+ ...field.max !== void 0 && { max: field.max },
86
+ ...field.int && { allowDecimal: false },
87
+ ...step !== void 0 ? { step } : field.int && { step: 1 },
88
+ value: draft.value,
89
+ onChange: draft.set,
90
+ onBlur: draft.commit,
91
+ onKeyDown: (event) => {
92
+ if (event.key !== "Enter") return;
93
+ event.preventDefault();
94
+ draft.commit();
95
+ }
96
+ }
97
+ );
98
+ if (inSheet) {
99
+ return /* @__PURE__ */ jsx(SheetField, { label, labelId, children: input });
100
+ }
101
+ return /* @__PURE__ */ jsx(
102
+ FilterPill,
103
+ {
104
+ label: String(value),
105
+ numeric: true,
106
+ active: !isDefault,
107
+ ...icon !== void 0 && { icon },
108
+ children: /* @__PURE__ */ jsx("div", { className: classes.optionList, children: input })
109
+ }
110
+ );
111
+ }
112
+ function useCommittedNumber(value, setValue, bounds) {
113
+ const [draft, setDraft] = useState(value);
114
+ const seen = useRef(value);
115
+ const commitRef = useRef(setValue);
116
+ commitRef.current = setValue;
117
+ useEffect(() => {
118
+ if (seen.current === value) return;
119
+ seen.current = value;
120
+ setDraft(value);
121
+ }, [value]);
122
+ return {
123
+ value: draft,
124
+ set: setDraft,
125
+ commit: () => {
126
+ const parsed = typeof draft === "number" ? draft : Number.parseFloat(draft);
127
+ if (!Number.isFinite(parsed)) {
128
+ setDraft(value);
129
+ return;
130
+ }
131
+ let next = parsed;
132
+ if (bounds.min !== void 0 && next < bounds.min) next = bounds.min;
133
+ if (bounds.max !== void 0 && next > bounds.max) next = bounds.max;
134
+ setDraft(next);
135
+ if (next === value) return;
136
+ seen.current = next;
137
+ commitRef.current(next);
138
+ }
139
+ };
140
+ }
141
+ export {
142
+ NumberFilter
143
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=peer-boundary.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-boundary.test.d.ts","sourceRoot":"","sources":["../../src/controls/peer-boundary.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=props-contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"props-contract.test.d.ts","sourceRoot":"","sources":["../../src/controls/props-contract.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+ import type { FieldHandle, RangeField } from '../state.js';
3
+ /**
4
+ * The contract a custom-range picker satisfies — `basalt-ui/controls-dates`' `DateRangePicker`, or a
5
+ * consumer's own. Dates are ISO (`YYYY-MM-DD`), the same shape `field.range` stores and
6
+ * `toWindow()` emits, so the picker never learns the store's encoding.
7
+ */
8
+ export type RangeCustomPickerProps = {
9
+ readonly value: {
10
+ readonly from?: string | undefined;
11
+ readonly to?: string | undefined;
12
+ };
13
+ readonly onChange: (next: {
14
+ from: string;
15
+ to: string;
16
+ }) => void;
17
+ };
18
+ /** Everything about the props that does not depend on the field's `custom` flag. */
19
+ type RangeFilterBase<P extends string, C extends boolean> = {
20
+ /**
21
+ * A range handle from either store factory. Generic over the field's `custom` flag so a
22
+ * `field.range({ ... })` WITHOUT `custom` (whose handle is `RangeField<P, false>`, and whose
23
+ * values therefore never include `'custom'`) binds with no cast — pinning `C` to its default
24
+ * `boolean` here made the setter contravariantly incompatible and forced one at every call site.
25
+ */
26
+ readonly field: FieldHandle<RangeField<P, C>>;
27
+ /** Leading pill icon. Defaults to a calendar glyph — a range filter reads as a date control, so
28
+ * the glyph is part of the control's identity, not a per-call-site decision. */
29
+ readonly icon?: ReactNode;
30
+ /** Sheet-form heading. The pill's own label is the VALUE, which is what a bar reads. @default 'Range' */
31
+ readonly label?: string;
32
+ };
33
+ export type RangeFilterProps<P extends string, C extends boolean = boolean> = RangeFilterBase<P, C> & {
34
+ /**
35
+ * Rendered when the field declares `custom: true`. Omitted → presets only, which stays LEGAL
36
+ * (a range that only ever shows presets is a real configuration) but warns once in dev when the
37
+ * field allows a custom window: the affordance is then unreachable, and nothing else says so.
38
+ */
39
+ readonly customPicker?: ComponentType<RangeCustomPickerProps>;
40
+ };
41
+ export declare function RangeFilter<P extends string, C extends boolean = boolean>(props: RangeFilterProps<P, C>): ReactNode;
42
+ export {};
43
+ //# sourceMappingURL=range-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"range-filter.d.ts","sourceRoot":"","sources":["../../src/controls/range-filter.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAwCnE;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAA;IACxF,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CAChE,CAAA;AAED,oFAAoF;AACpF,KAAK,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,IAAI;IAC1D;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC7C;qFACiF;IACjF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,yGAAyG;IACzG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,eAAe,CAC3F,CAAC,EACD,CAAC,CACF,GAAG;IACF;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAA;CAC9D,CAAA;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,GAAG,OAAO,EACvE,KAAK,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,SAAS,CAyGX"}