basalt-ui 1.24.0 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -6
- package/CHANGELOG.md +25 -0
- package/MIGRATING.md +547 -21
- package/README.md +46 -33
- package/agent/rules/basalt-batteries.md +205 -0
- package/agent/rules/basalt-charts.md +125 -555
- package/agent/rules/basalt-controls.md +155 -0
- package/agent/rules/basalt-mantine.md +146 -439
- package/agent/rules/basalt-state.md +113 -96
- package/agent/rules/basalt-tokens.md +126 -288
- package/agent/skills/basalt-app/SKILL.md +70 -195
- package/agent/skills/basalt-charts/SKILL.md +54 -234
- package/agent/skills/basalt-design/SKILL.md +56 -248
- package/agent/templates/CLAUDE-block.md.tpl +31 -40
- package/agent/templates/DESIGN.md.tpl +22 -51
- package/configs/oxlint-plugin.js +1275 -31
- package/configs/oxlint-plugin.test.ts +947 -11
- package/configs/oxlint.json +11 -3
- package/dist/charts/cursor/resolve.d.ts +18 -0
- package/dist/charts/cursor/resolve.d.ts.map +1 -1
- package/dist/charts/cursor/resolve.js +15 -6
- package/dist/charts/cursor/scope.d.ts +6 -3
- package/dist/charts/cursor/scope.d.ts.map +1 -1
- package/dist/charts/cursor/store.d.ts +5 -1
- package/dist/charts/cursor/store.d.ts.map +1 -1
- package/dist/charts/cursor/store.js +4 -4
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
- package/dist/charts/hooks/useChartCursor.js +9 -8
- package/dist/charts/index.d.ts +2 -2
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.d.ts +34 -9
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +29 -114
- package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
- package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
- package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
- package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
- package/dist/charts/sparklines/BarSparkline.js +18 -4
- package/dist/cli/check-coverage.test.d.ts +2 -0
- package/dist/cli/check-coverage.test.d.ts.map +1 -0
- package/dist/cli/index.d.ts +67 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +225 -7
- package/dist/connectivity/connectivity-indicator.d.ts +4 -1
- package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
- package/dist/connectivity/connectivity-indicator.js +1 -1
- package/dist/content/index.d.ts +5 -3
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +4 -2
- package/dist/content/prose.module.css +7 -28
- package/dist/controls/actions.d.ts +169 -0
- package/dist/controls/actions.d.ts.map +1 -0
- package/dist/controls/actions.js +287 -0
- package/dist/controls/actions.module.css +62 -0
- package/dist/controls/actions.module.css.d.ts +13 -0
- package/dist/controls/actions.test.d.ts +2 -0
- package/dist/controls/actions.test.d.ts.map +1 -0
- package/dist/controls/actions.type-guard.test.d.ts +2 -0
- package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
- package/dist/controls/compare-filter.d.ts +41 -0
- package/dist/controls/compare-filter.d.ts.map +1 -0
- package/dist/controls/compare-filter.js +23 -0
- package/dist/controls/control-group.d.ts +19 -0
- package/dist/controls/control-group.d.ts.map +1 -0
- package/dist/controls/control-group.js +20 -0
- package/dist/controls/control-group.test.d.ts +2 -0
- package/dist/controls/control-group.test.d.ts.map +1 -0
- package/dist/controls/controls.module.css +542 -0
- package/dist/controls/controls.module.css.d.ts +35 -0
- package/dist/controls/controls.router.test.d.ts +2 -0
- package/dist/controls/controls.router.test.d.ts.map +1 -0
- package/dist/controls/enum-filter.d.ts +30 -0
- package/dist/controls/enum-filter.d.ts.map +1 -0
- package/dist/controls/enum-filter.js +91 -0
- package/dist/controls/filter-context.d.ts +55 -0
- package/dist/controls/filter-context.d.ts.map +1 -0
- package/dist/controls/filter-context.js +69 -0
- package/dist/controls/filter-pill.d.ts +28 -0
- package/dist/controls/filter-pill.d.ts.map +1 -0
- package/dist/controls/filter-pill.js +70 -0
- package/dist/controls/filter-set.d.ts +13 -0
- package/dist/controls/filter-set.d.ts.map +1 -0
- package/dist/controls/filter-set.js +125 -0
- package/dist/controls/filter-set.test.d.ts +2 -0
- package/dist/controls/filter-set.test.d.ts.map +1 -0
- package/dist/controls/filter-sheet.d.ts +117 -0
- package/dist/controls/filter-sheet.d.ts.map +1 -0
- package/dist/controls/filter-sheet.js +146 -0
- package/dist/controls/glyphs.d.ts +27 -0
- package/dist/controls/glyphs.d.ts.map +1 -0
- package/dist/controls/glyphs.js +49 -0
- package/dist/controls/index.d.ts +19 -0
- package/dist/controls/index.d.ts.map +1 -0
- package/dist/controls/index.js +44 -0
- package/dist/controls/multi-select-filter.d.ts +23 -0
- package/dist/controls/multi-select-filter.d.ts.map +1 -0
- package/dist/controls/multi-select-filter.js +89 -0
- package/dist/controls/peer-boundary.test.d.ts +2 -0
- package/dist/controls/peer-boundary.test.d.ts.map +1 -0
- package/dist/controls/props-contract.test.d.ts +2 -0
- package/dist/controls/props-contract.test.d.ts.map +1 -0
- package/dist/controls/range-filter.d.ts +29 -0
- package/dist/controls/range-filter.d.ts.map +1 -0
- package/dist/controls/range-filter.js +120 -0
- package/dist/controls/search-filter.d.ts +10 -0
- package/dist/controls/search-filter.d.ts.map +1 -0
- package/dist/controls/search-filter.js +81 -0
- package/dist/controls/select-filter.d.ts +74 -0
- package/dist/controls/select-filter.d.ts.map +1 -0
- package/dist/controls/select-filter.js +10 -0
- package/dist/controls/sync-button.d.ts +38 -0
- package/dist/controls/sync-button.d.ts.map +1 -0
- package/dist/controls/sync-button.js +119 -0
- package/dist/controls/sync-button.module.css +63 -0
- package/dist/controls/sync-button.module.css.d.ts +11 -0
- package/dist/controls/sync-button.test.d.ts +2 -0
- package/dist/controls/sync-button.test.d.ts.map +1 -0
- package/dist/controls/toggle-filter.d.ts +9 -0
- package/dist/controls/toggle-filter.d.ts.map +1 -0
- package/dist/controls/toggle-filter.js +42 -0
- package/dist/controls/view-tabs.d.ts +23 -0
- package/dist/controls/view-tabs.d.ts.map +1 -0
- package/dist/controls/view-tabs.js +59 -0
- package/dist/controls-dates/date-range-picker.d.ts +4 -0
- package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
- package/dist/controls-dates/date-range-picker.js +18 -0
- package/dist/controls-dates/index.d.ts +9 -0
- package/dist/controls-dates/index.d.ts.map +1 -0
- package/dist/controls-dates/index.js +4 -0
- package/dist/dashboard/delta-badge.d.ts +6 -12
- package/dist/dashboard/delta-badge.d.ts.map +1 -1
- package/dist/dashboard/delta-badge.js +1 -50
- package/dist/dashboard/query-state.d.ts +1 -1
- package/dist/dashboard/settings-section.d.ts +6 -4
- package/dist/dashboard/settings-section.d.ts.map +1 -1
- package/dist/dashboard/settings-section.js +23 -16
- package/dist/dashboard/settings-section.module.css +10 -0
- package/dist/dashboard/settings-section.module.css.d.ts +1 -0
- package/dist/dashboard/settings-section.test.d.ts +2 -0
- package/dist/dashboard/settings-section.test.d.ts.map +1 -0
- package/dist/dashboard/stat-card.d.ts +44 -9
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +80 -48
- package/dist/dashboard/stat-card.module.css +82 -0
- package/dist/dashboard/stat-card.module.css.d.ts +11 -0
- package/dist/data/data-table.d.ts +102 -20
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +166 -65
- package/dist/data/table.d.ts +1 -1
- package/dist/data/table.d.ts.map +1 -1
- package/dist/guard/grace.test.d.ts +20 -0
- package/dist/guard/grace.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts +66 -3
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +82 -7
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -10
- package/dist/nav/types.d.ts +78 -2
- package/dist/nav/types.d.ts.map +1 -1
- package/dist/notifications/bell.d.ts +4 -1
- package/dist/notifications/bell.d.ts.map +1 -1
- package/dist/notifications/index.d.ts +1 -1
- package/dist/router-tanstack/field.d.ts +14 -0
- package/dist/router-tanstack/field.d.ts.map +1 -0
- package/dist/router-tanstack/field.js +4 -0
- package/dist/router-tanstack/index.d.ts +6 -2
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/index.js +4 -0
- package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
- package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/multi-search-param-store.js +19 -27
- package/dist/router-tanstack/search-param-store.d.ts +3 -87
- package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-param-store.js +16 -45
- package/dist/router-tanstack/search-store.d.ts +81 -0
- package/dist/router-tanstack/search-store.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.js +187 -0
- package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
- package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.test.d.ts +2 -0
- package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
- package/dist/router-tanstack/use-nav.d.ts +0 -2
- package/dist/router-tanstack/use-nav.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.js +34 -12
- package/dist/router-tanstack/use-nav.test.d.ts +2 -0
- package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
- package/dist/section/index.d.ts +2 -0
- package/dist/section/index.d.ts.map +1 -0
- package/dist/section/index.js +4 -0
- package/dist/section/section.d.ts +24 -0
- package/dist/section/section.d.ts.map +1 -0
- package/dist/section/section.js +126 -0
- package/dist/section/section.module.css +104 -0
- package/dist/section/section.module.css.d.ts +11 -0
- package/dist/section/section.test.d.ts +2 -0
- package/dist/section/section.test.d.ts.map +1 -0
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.js +18 -15
- package/dist/shell/app-header.module.css +77 -37
- package/dist/shell/app-header.module.css.d.ts +2 -1
- package/dist/shell/app-mobile-nav.d.ts +9 -2
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +98 -11
- package/dist/shell/app-mobile-nav.module.css +8 -0
- package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
- package/dist/shell/app-sidebar.d.ts +55 -28
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +116 -67
- package/dist/shell/app-sidebar.module.css +303 -20
- package/dist/shell/app-sidebar.module.css.d.ts +19 -1
- package/dist/shell/index.d.ts +55 -21
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +40 -18
- package/dist/shell/mobile-nav-model.d.ts +13 -2
- package/dist/shell/mobile-nav-model.d.ts.map +1 -1
- package/dist/shell/mobile-nav-model.js +15 -1
- package/dist/shell/nav-count-badge.d.ts +1 -1
- package/dist/shell/page-bar.d.ts +51 -0
- package/dist/shell/page-bar.d.ts.map +1 -0
- package/dist/shell/page-bar.js +154 -0
- package/dist/shell/page-bar.module.css +178 -0
- package/dist/shell/page-bar.module.css.d.ts +17 -0
- package/dist/shell/page-bar.test.d.ts +2 -0
- package/dist/shell/page-bar.test.d.ts.map +1 -0
- package/dist/shell/sidebar-block-model.d.ts +48 -0
- package/dist/shell/sidebar-block-model.d.ts.map +1 -0
- package/dist/shell/sidebar-block-model.js +39 -0
- package/dist/shell/sidebar-blocks.d.ts +32 -0
- package/dist/shell/sidebar-blocks.d.ts.map +1 -0
- package/dist/shell/sidebar-blocks.js +169 -0
- package/dist/shell/sidebar-search.d.ts +13 -1
- package/dist/shell/sidebar-search.d.ts.map +1 -1
- package/dist/shell/sidebar-search.js +34 -1
- package/dist/shell/sidebar-search.module.css +26 -0
- package/dist/shell/sidebar-search.module.css.d.ts +2 -0
- package/dist/state/fields.d.ts +392 -0
- package/dist/state/fields.d.ts.map +1 -0
- package/dist/state/fields.js +369 -0
- package/dist/state/fields.test.d.ts +2 -0
- package/dist/state/fields.test.d.ts.map +1 -0
- package/dist/state/persisted.d.ts +48 -0
- package/dist/state/persisted.d.ts.map +1 -0
- package/dist/state/persisted.js +105 -0
- package/dist/state.d.ts +11 -56
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +14 -120
- package/dist/styles.css +13 -0
- package/dist/surfaces.d.ts +106 -24
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +155 -19
- package/dist/theme/controls.module.css +16 -1
- package/dist/theme/ctl-theme.d.ts +32 -0
- package/dist/theme/ctl-theme.d.ts.map +1 -0
- package/dist/theme/ctl-theme.js +54 -0
- package/dist/theme/ctl-theme.test.d.ts +2 -0
- package/dist/theme/ctl-theme.test.d.ts.map +1 -0
- package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
- package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
- package/dist/theme/divider-law.test.d.ts +2 -0
- package/dist/theme/divider-law.test.d.ts.map +1 -0
- package/dist/theme/icon-slot.d.ts +44 -0
- package/dist/theme/icon-slot.d.ts.map +1 -0
- package/dist/theme/icon-slot.js +21 -0
- package/dist/theme/icon-slot.module.css +47 -0
- package/dist/theme/icon-slot.module.css.d.ts +8 -0
- package/dist/theme/icon-slot.test.d.ts +2 -0
- package/dist/theme/icon-slot.test.d.ts.map +1 -0
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +82 -5
- package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
- package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
- package/dist/theme/segmented-control.module.css +20 -0
- package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
- package/dist/tokens/index.d.ts +12 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +38 -8
- package/dist/tokens/palette.d.ts +97 -61
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +76 -38
- package/dist/tokens.css +10 -2
- package/dist/widget-header/delta-badge.d.ts +13 -0
- package/dist/widget-header/delta-badge.d.ts.map +1 -0
- package/dist/widget-header/delta-badge.js +29 -0
- package/dist/widget-header/delta-badge.test.d.ts +2 -0
- package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
- package/dist/widget-header/index.d.ts +3 -0
- package/dist/widget-header/index.d.ts.map +1 -0
- package/dist/widget-header/index.js +6 -0
- package/dist/widget-header/no-mantine.test.d.ts +2 -0
- package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
- package/dist/widget-header/widget-header.d.ts +37 -0
- package/dist/widget-header/widget-header.d.ts.map +1 -0
- package/dist/widget-header/widget-header.js +77 -0
- package/dist/widget-header/widget-header.module.css +335 -0
- package/dist/widget-header/widget-header.module.css.d.ts +24 -0
- package/dist/widget-header/widget-header.test.d.ts +2 -0
- package/dist/widget-header/widget-header.test.d.ts.map +1 -0
- package/llms.txt +30 -17
- package/package.json +14 -1
- package/src/charts/cursor/resolve.test.ts +29 -1
- package/src/charts/cursor/resolve.ts +38 -6
- package/src/charts/cursor/scope.tsx +6 -3
- package/src/charts/cursor/store.test.ts +19 -1
- package/src/charts/cursor/store.ts +10 -5
- package/src/charts/hooks/useChartCursor.ts +16 -8
- package/src/charts/index.ts +2 -2
- package/src/charts/primitives/CartesianChart.test.tsx +124 -0
- package/src/charts/primitives/ChartCard.test.tsx +66 -0
- package/src/charts/primitives/ChartCard.tsx +86 -146
- package/src/charts/sparklines/BarSparkline.tsx +43 -5
- package/src/cli/check-coverage.test.ts +297 -0
- package/src/cli/check-theme.test.ts +38 -14
- package/src/cli/index.ts +392 -16
- package/src/cli/init-sync.test.ts +97 -0
- package/src/connectivity/connectivity-indicator.tsx +16 -9
- package/src/content/index.ts +16 -3
- package/src/content/prose.module.css +7 -28
- package/src/content/prose.module.css.test.ts +8 -24
- package/src/controls/actions.module.css +62 -0
- package/src/controls/actions.module.css.d.ts +13 -0
- package/src/controls/actions.test.tsx +252 -0
- package/src/controls/actions.tsx +655 -0
- package/src/controls/actions.type-guard.test.ts +63 -0
- package/src/controls/compare-filter.tsx +64 -0
- package/src/controls/control-group.test.tsx +229 -0
- package/src/controls/control-group.tsx +63 -0
- package/src/controls/controls.module.css +542 -0
- package/src/controls/controls.module.css.d.ts +35 -0
- package/src/controls/controls.router.test.tsx +637 -0
- package/src/controls/enum-filter.tsx +159 -0
- package/src/controls/filter-context.tsx +153 -0
- package/src/controls/filter-pill.tsx +120 -0
- package/src/controls/filter-set.test.tsx +472 -0
- package/src/controls/filter-set.tsx +225 -0
- package/src/controls/filter-sheet.tsx +285 -0
- package/src/controls/glyphs.tsx +81 -0
- package/src/controls/index.ts +48 -0
- package/src/controls/multi-select-filter.tsx +140 -0
- package/src/controls/peer-boundary.test.ts +85 -0
- package/src/controls/props-contract.test.tsx +106 -0
- package/src/controls/range-filter.tsx +184 -0
- package/src/controls/search-filter.tsx +123 -0
- package/src/controls/select-filter.tsx +89 -0
- package/src/controls/sync-button.module.css +63 -0
- package/src/controls/sync-button.module.css.d.ts +11 -0
- package/src/controls/sync-button.test.tsx +183 -0
- package/src/controls/sync-button.tsx +197 -0
- package/src/controls/toggle-filter.tsx +60 -0
- package/src/controls/view-tabs.tsx +110 -0
- package/src/controls-dates/date-range-picker.tsx +42 -0
- package/src/controls-dates/index.ts +8 -0
- package/src/dashboard/delta-badge.tsx +4 -74
- package/src/dashboard/query-state.tsx +1 -1
- package/src/dashboard/settings-section.module.css +10 -0
- package/src/dashboard/settings-section.module.css.d.ts +1 -0
- package/src/dashboard/settings-section.test.tsx +56 -0
- package/src/dashboard/settings-section.tsx +33 -30
- package/src/dashboard/stat-card.module.css +82 -0
- package/src/dashboard/stat-card.module.css.d.ts +11 -0
- package/src/dashboard/stat-card.test.tsx +67 -1
- package/src/dashboard/stat-card.tsx +209 -68
- package/src/data/data-table.test.tsx +282 -6
- package/src/data/data-table.tsx +429 -94
- package/src/data/table.ts +2 -0
- package/src/guard/check-source.test.ts +126 -13
- package/src/guard/grace.test.ts +149 -0
- package/src/guard/index.ts +172 -29
- package/src/guard/types.ts +4 -2
- package/src/index.ts +46 -5
- package/src/nav/types.ts +84 -2
- package/src/notifications/bell.tsx +6 -3
- package/src/notifications/index.ts +1 -1
- package/src/router-tanstack/field.ts +31 -0
- package/src/router-tanstack/index.ts +24 -2
- package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
- package/src/router-tanstack/multi-search-param-store.ts +46 -77
- package/src/router-tanstack/search-param-store.ts +42 -159
- package/src/router-tanstack/search-store.router.test.tsx +661 -0
- package/src/router-tanstack/search-store.test.ts +418 -0
- package/src/router-tanstack/search-store.ts +380 -0
- package/src/router-tanstack/use-nav.test.tsx +187 -0
- package/src/router-tanstack/use-nav.ts +72 -14
- package/src/section/index.ts +1 -0
- package/src/section/section.module.css +104 -0
- package/src/section/section.module.css.d.ts +11 -0
- package/src/section/section.test.tsx +204 -0
- package/src/section/section.tsx +220 -0
- package/src/shell/app-breadcrumbs.tsx +44 -31
- package/src/shell/app-header.module.css +77 -37
- package/src/shell/app-header.module.css.d.ts +2 -1
- package/src/shell/app-mobile-nav.module.css +8 -0
- package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
- package/src/shell/app-mobile-nav.tsx +147 -8
- package/src/shell/app-sidebar-account.tsx +2 -2
- package/src/shell/app-sidebar.module.css +303 -20
- package/src/shell/app-sidebar.module.css.d.ts +19 -1
- package/src/shell/app-sidebar.test.tsx +456 -41
- package/src/shell/app-sidebar.tsx +276 -140
- package/src/shell/index.test.tsx +291 -5
- package/src/shell/index.tsx +137 -38
- package/src/shell/mobile-nav-model.test.ts +228 -7
- package/src/shell/mobile-nav-model.ts +53 -3
- package/src/shell/nav-count-badge.tsx +1 -1
- package/src/shell/page-bar.module.css +178 -0
- package/src/shell/page-bar.module.css.d.ts +17 -0
- package/src/shell/page-bar.test.tsx +316 -0
- package/src/shell/page-bar.tsx +315 -0
- package/src/shell/sidebar-block-model.ts +83 -0
- package/src/shell/sidebar-blocks.tsx +301 -0
- package/src/shell/sidebar-search.module.css +26 -0
- package/src/shell/sidebar-search.module.css.d.ts +2 -0
- package/src/shell/sidebar-search.tsx +71 -2
- package/src/state/fields.test.tsx +258 -0
- package/src/state/fields.ts +892 -0
- package/src/state/persisted.ts +211 -0
- package/src/state.ts +46 -240
- package/src/styles.css +13 -0
- package/src/surfaces.ts +203 -36
- package/src/theme/controls.module.css +16 -1
- package/src/theme/ctl-theme.test.tsx +275 -0
- package/src/theme/ctl-theme.tsx +138 -0
- package/src/theme/ctl-tier-coverage.test.ts +170 -0
- package/src/theme/density-relations.test.ts +106 -51
- package/src/theme/divider-law.test.ts +259 -0
- package/src/theme/icon-slot.module.css +47 -0
- package/src/theme/icon-slot.module.css.d.ts +8 -0
- package/src/theme/icon-slot.test.tsx +275 -0
- package/src/theme/icon-slot.tsx +59 -0
- package/src/theme/index.ts +165 -3
- package/src/theme/layout-rhythm-css.test.ts +72 -0
- package/src/theme/radius.test.ts +11 -1
- package/src/theme/segmented-control.module.css +20 -0
- package/src/theme/shadow-surfaces.test.ts +20 -7
- package/src/theme/spacing.test.ts +75 -27
- package/src/theme/use-basalt-spacing.ts +2 -2
- package/src/tokens/density.test.ts +7 -25
- package/src/tokens/index.ts +48 -8
- package/src/tokens/palette.ts +107 -73
- package/src/widget-header/delta-badge.test.tsx +60 -0
- package/src/widget-header/delta-badge.tsx +57 -0
- package/src/widget-header/index.ts +2 -0
- package/src/widget-header/no-mantine.test.ts +33 -0
- package/src/widget-header/widget-header.module.css +335 -0
- package/src/widget-header/widget-header.module.css.d.ts +24 -0
- package/src/widget-header/widget-header.test.tsx +187 -0
- package/src/widget-header/widget-header.tsx +168 -0
- package/agent/rules/basalt-agent.md +0 -452
- package/agent/rules/basalt-app.md +0 -246
- package/agent/rules/basalt-commands.md +0 -320
- package/agent/rules/basalt-content.md +0 -409
- package/agent/rules/basalt-data.md +0 -269
- package/agent/rules/basalt-forms.md +0 -186
- package/agent/rules/basalt-notifications.md +0 -264
- package/agent/rules/basalt-query.md +0 -187
- package/agent/rules/basalt-router.md +0 -336
- package/dist/content/article-filter-bar.d.ts +0 -40
- package/dist/content/article-filter-bar.d.ts.map +0 -1
- package/dist/content/article-filter-bar.js +0 -56
- package/dist/shell/page-header.d.ts +0 -13
- package/dist/shell/page-header.d.ts.map +0 -1
- package/dist/shell/page-header.js +0 -23
- package/src/content/article-filter-bar.tsx +0 -88
- package/src/shell/page-header.tsx +0 -35
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
source: basalt-ui
|
|
3
|
-
description: TanStack Router conventions for basalt-ui apps — file-based routes, typed search params, loader/query coupling, and the one typed nav definition that drives both the desktop sidebar and the mobile bar. Advisory (ships an optional ./router-tanstack adapter).
|
|
4
|
-
paths:
|
|
5
|
-
- 'src/routes/**'
|
|
6
|
-
- 'apps/**/src/routes/**'
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Basalt Router — TanStack Router Conventions
|
|
10
|
-
|
|
11
|
-
basalt-ui is **router-agnostic** — the shell renders every pixel of nav chrome and hosts the
|
|
12
|
-
consumer's router component through one seam, `SidebarItem.Anchor` (a **`NavAnchor`**). With
|
|
13
|
-
TanStack Router you never write that seam by hand: **`defineNav`** declares the whole navigation
|
|
14
|
-
once and **`useNav`** resolves it against the live router, returning the two props `BasaltShell`
|
|
15
|
-
needs. Breadcrumbs come from **`useRouterBreadcrumbs`**, ad-hoc active checks from
|
|
16
|
-
**`useBasaltNav`**. This rule is the recommended opinion layer when you use TanStack Router (the
|
|
17
|
-
basalt-ui default). It is **advisory** — adopt it for consistency; nothing in the framework
|
|
18
|
-
enforces it.
|
|
19
|
-
|
|
20
|
-
## Adding a page
|
|
21
|
-
|
|
22
|
-
1. Create `src/routes/<page-name>.tsx`.
|
|
23
|
-
2. Export `Route` via `createFileRoute('/<page-name>')`.
|
|
24
|
-
3. Add `validateSearch` with a Zod schema parsed via a function — not the schema passed directly.
|
|
25
|
-
4. Define `loaderDeps` to forward search params to the loader (required for search-param-driven queries).
|
|
26
|
-
5. Add a `loader` that calls `ensureQueryData` for each query the page needs.
|
|
27
|
-
6. Register the page in your `defineNav` definition (see "One typed nav definition" below).
|
|
28
|
-
|
|
29
|
-
## Route structure
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
33
|
-
import { useSuspenseQuery } from 'basalt-ui/query'
|
|
34
|
-
import { z } from 'zod'
|
|
35
|
-
import { resourceQueries } from '../lib/queries/resource'
|
|
36
|
-
|
|
37
|
-
const SearchSchema = z.object({
|
|
38
|
-
window: z.enum(['7d', '30d', '90d', 'all']).default('30d'),
|
|
39
|
-
from: z.string().optional(),
|
|
40
|
-
to: z.string().optional(),
|
|
41
|
-
})
|
|
42
|
-
type SearchParams = z.infer<typeof SearchSchema>
|
|
43
|
-
|
|
44
|
-
export const Route = createFileRoute('/resource')({
|
|
45
|
-
validateSearch: (raw: Record<string, unknown>) => SearchSchema.parse(raw),
|
|
46
|
-
loaderDeps: ({ search }: { search: SearchParams }) => ({
|
|
47
|
-
window: search.window,
|
|
48
|
-
from: search.from,
|
|
49
|
-
to: search.to,
|
|
50
|
-
}),
|
|
51
|
-
loader: ({ context, deps }) =>
|
|
52
|
-
Promise.all([
|
|
53
|
-
context.queryClient.ensureQueryData(resourceQueries.summary(deps)),
|
|
54
|
-
context.queryClient.ensureQueryData(resourceQueries.series(deps)),
|
|
55
|
-
]),
|
|
56
|
-
component: ResourcePage,
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
function ResourcePage() {
|
|
60
|
-
const search = Route.useSearch()
|
|
61
|
-
const { data } = useSuspenseQuery(resourceQueries.summary(search))
|
|
62
|
-
// render — charts read VX.* tokens; chrome uses Mantine
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## validateSearch
|
|
67
|
-
|
|
68
|
-
**Scope of the `createSearchParamStore` mandate — read this before reaching for the store.**
|
|
69
|
-
`createSearchParamStore` covers ONE param whose values are a string enum
|
|
70
|
-
(`{ key, param, values, fallback }`); `createMultiSearchParamStore` is the multi-select of the same
|
|
71
|
-
shape. Where that fits — a range picker, a tab, a single filter — it is **mandatory**: use it, don't
|
|
72
|
-
hand-roll persistence.
|
|
73
|
-
|
|
74
|
-
Where it doesn't fit, it doesn't apply. A route whose `validateSearch` is a real Zod object (10+
|
|
75
|
-
keys, nested shapes, cross-field defaults) cannot be expressed through the store at all — three
|
|
76
|
-
consumers hit that wall. **Hand-write `validateSearch` there**, per the rules below. There is no
|
|
77
|
-
schema-backed store: `createSearchSchemaStore` is planned and **not shipped** — do not write code
|
|
78
|
-
against it.
|
|
79
|
-
|
|
80
|
-
- Always parse with the schema-function form: `(raw) => SearchSchema.parse(raw)`.
|
|
81
|
-
- Default values live in the schema (`.default()`), not in the component.
|
|
82
|
-
- Access via `const search = Route.useSearch()`.
|
|
83
|
-
- Update via `navigate({ search: { window: 'all', from: undefined, to: undefined } })` — prefer plain
|
|
84
|
-
objects over reducers to avoid `| undefined` assignability errors when the schema has defaults.
|
|
85
|
-
|
|
86
|
-
### Wire the reader, or the store does nothing (1.21.0)
|
|
87
|
-
|
|
88
|
-
`validateSearch` is only half the store. It restores the value when you arrive with **no** param —
|
|
89
|
-
it cannot do anything about a nav link that declares the param itself, and a link written
|
|
90
|
-
`search: { window: '30d' }` at module scope pins the fallback on every click. That is not
|
|
91
|
-
hypothetical: the reference consumer adopted the store in three features, hand-rolled the
|
|
92
|
-
persistence in all three, and its reader had **zero** call sites. "Remember my window" had never
|
|
93
|
-
once worked, with every individual piece looking correct.
|
|
94
|
-
|
|
95
|
-
`store.linkSearch` is the fix, and it is on the object the factory already returns:
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
const dashboardRange = createSearchParamStore({
|
|
99
|
-
key: 'dashboard-range',
|
|
100
|
-
param: 'range',
|
|
101
|
-
values: ['1d', '7d', '30d'] as const,
|
|
102
|
-
fallback: '30d',
|
|
103
|
-
})
|
|
104
|
-
// → { validateSearch, useStore, readStored, linkSearch }
|
|
105
|
-
|
|
106
|
-
link: linkOptions({ to: '/dashboard', search: dashboardRange.linkSearch })
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**Pass it by reference, never call it.** It is the click-time thunk
|
|
110
|
-
(`() => ({ [param]: readStored() ?? fallback })`); `<Link>` re-evaluates it on every click, so
|
|
111
|
-
arriving from outside the sub-tree restores the last selection. A value computed once at module
|
|
112
|
-
scope goes stale immediately. Hand-rolling the thunk over `readStored()` still works and is what
|
|
113
|
-
you need when the link carries other params too.
|
|
114
|
-
|
|
115
|
-
In dev, `validateSearch` warns once per store when the URL pins the fallback, a different value is
|
|
116
|
-
persisted, and neither public reader has ever been called — the one combination that has exactly
|
|
117
|
-
this cause. Silent in production, silent on a deliberate deep link.
|
|
118
|
-
`createMultiSearchParamStore` gets `linkSearch` but no warning: an empty array in the URL is
|
|
119
|
-
indistinguishable from an absent param, so a literal link there still restores correctly.
|
|
120
|
-
|
|
121
|
-
## loaderDeps
|
|
122
|
-
|
|
123
|
-
- Define `loaderDeps` whenever the loader depends on search params. Without it, search-param changes do
|
|
124
|
-
not re-trigger the loader.
|
|
125
|
-
- The `deps` object is forwarded to the loader and should be passed directly to the `ensureQueryData`
|
|
126
|
-
query factories.
|
|
127
|
-
|
|
128
|
-
## Loaders
|
|
129
|
-
|
|
130
|
-
- Use `ensureQueryData` (not `fetchQuery`) so cached data is reused.
|
|
131
|
-
- The loader receives `context: { queryClient }` — wire it in your router config.
|
|
132
|
-
- Loaders do not return data to the component — components read via `useSuspenseQuery`.
|
|
133
|
-
- When a redirect from another route points here, include the required `search` params or TypeScript
|
|
134
|
-
raises `MakeRequiredSearchParams`.
|
|
135
|
-
|
|
136
|
-
## Route tree
|
|
137
|
-
|
|
138
|
-
`src/routeTree.gen.ts` is auto-generated by the Vite plugin — never edit it. Regenerated by
|
|
139
|
-
`tsr generate` (also during `typecheck`). File names starting with `__` are reserved; only `__root.tsx`
|
|
140
|
-
is valid.
|
|
141
|
-
|
|
142
|
-
The shipped oxlint preset does **not** ban `react-router` or `react-router-dom` — basalt is
|
|
143
|
-
router-agnostic and imposes no mechanical guard. TanStack Router is the recommended/idiomatic
|
|
144
|
-
choice (this rule documents its conventions, and the `./router-tanstack` adapter targets it), but
|
|
145
|
-
using `react-router` is an advisory preference, not an enforced restriction.
|
|
146
|
-
|
|
147
|
-
## ./router-tanstack adapter
|
|
148
|
-
|
|
149
|
-
The optional `./router-tanstack` subpath ships a headless (Mantine-free) TanStack Router bridge.
|
|
150
|
-
Install `@tanstack/react-router` as a peer, then import from `basalt-ui/router-tanstack`.
|
|
151
|
-
|
|
152
|
-
**Prerequisites:**
|
|
153
|
-
|
|
154
|
-
- **The only hard prerequisite is the `Register` module augmentation** (see "The prerequisite that
|
|
155
|
-
makes all of this real" below). `defineNav` / `navGroup` / `navTarget` / `flattenNav` / `useNav` /
|
|
156
|
-
`createSearchParamStore` all work against a hand-written `createRouter({ routeTree })` with
|
|
157
|
-
code-defined routes, no Vite plugin and no generated tree — verified end-to-end in a consumer,
|
|
158
|
-
including that a bad `to` still prints the real route-path union.
|
|
159
|
-
- **`@tanstack/router-plugin/vite` + `tsr generate` are needed only for `staticData`** — that is,
|
|
160
|
-
for `useRouterBreadcrumbs` and any nav metadata read off the route tree. The
|
|
161
|
-
`StaticDataRouteOption` augmentation takes effect once `src/routeTree.gen.ts` exists. In CI and
|
|
162
|
-
after adding routes, run `tsr generate` (or `bun run typecheck`, which triggers it) before relying
|
|
163
|
-
on breadcrumb data.
|
|
164
|
-
- The loader's `context.queryClient` requires `createRootRouteWithContext<{ queryClient: QueryClient }>()`.
|
|
165
|
-
Run `basalt-ui init` to scaffold a `query-client.ts` + `__root.tsx` seed that wires the TanStack
|
|
166
|
-
Router + Query context correctly — the generated `__root.tsx` uses
|
|
167
|
-
`createRootRouteWithContext<{ queryClient }>()` and passes the client through `RouterProvider`.
|
|
168
|
-
|
|
169
|
-
**StaticDataRouteOption augmentation** — declare on any route to drive the adapter:
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
export const Route = createFileRoute('/reports')({
|
|
173
|
-
staticData: { title: 'Reports', navSection: 'Insights' },
|
|
174
|
-
component: ReportsPage,
|
|
175
|
-
})
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
Augmented fields: `title?: string`, `icon?: ReactNode`, `navSection?: string` (all optional — any
|
|
179
|
-
route remains valid without them).
|
|
180
|
-
|
|
181
|
-
**useBasaltNav** — reactive active-state resolution:
|
|
182
|
-
|
|
183
|
-
```ts
|
|
184
|
-
import { useBasaltNav } from 'basalt-ui/router-tanstack'
|
|
185
|
-
|
|
186
|
-
const { currentPath, isActive } = useBasaltNav()
|
|
187
|
-
isActive('/reports') // prefix match (active on /reports/42 too)
|
|
188
|
-
isActive('/reports', { exact: true }) // exact match only
|
|
189
|
-
isActive('/') // always exact (root is never a prefix match)
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**useRouterBreadcrumbs** — ancestor→deepest breadcrumb trail from `staticData.title`:
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
import { useRouterBreadcrumbs } from 'basalt-ui/router-tanstack'
|
|
196
|
-
|
|
197
|
-
function AppBreadcrumbs() {
|
|
198
|
-
const crumbs = useRouterBreadcrumbs()
|
|
199
|
-
return <nav>{crumbs.map((c) => <a key={c.href} href={c.href}>{c.title}</a>)}</nav>
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**One typed nav definition** — `defineNav` / `navGroup` / `navTarget` / `useNav`.
|
|
204
|
-
|
|
205
|
-
The whole navigation — desktop sidebar and mobile bar — is declared once in a **leaf** module
|
|
206
|
-
(`src/lib/nav.tsx`), which imports `@tanstack/react-router` and `basalt-ui/router-tanstack` and
|
|
207
|
-
**never** `routeTree.gen` or `__root.tsx`, so command palettes and redirects can import it without
|
|
208
|
-
closing a cycle.
|
|
209
|
-
|
|
210
|
-
```tsx
|
|
211
|
-
import { linkOptions } from '@tanstack/react-router'
|
|
212
|
-
import { defineNav, navGroup } from 'basalt-ui/router-tanstack'
|
|
213
|
-
import { garminWindow } from './search-params' // a createSearchParamStore
|
|
214
|
-
|
|
215
|
-
const ICON = 18
|
|
216
|
-
|
|
217
|
-
export const NAV = defineNav({
|
|
218
|
-
groups: [
|
|
219
|
-
navGroup({ id: 'health', label: 'Health', icon: <IconHeartbeat size={ICON} /> }, [
|
|
220
|
-
{
|
|
221
|
-
id: 'garmin',
|
|
222
|
-
label: 'Garmin Health',
|
|
223
|
-
short: 'Garmin', // bar/menu label — keep it ≤ 10 chars
|
|
224
|
-
mobile: 'tab', // give this destination its own slot on the mobile bar
|
|
225
|
-
icon: <IconHeartbeat size={ICON} />,
|
|
226
|
-
// NOT `search: { window: '30d' }` — a module-scope literal pins the fallback on
|
|
227
|
-
// every click and silently overrides the store. Pass the reader by reference.
|
|
228
|
-
link: linkOptions({ to: '/garmin-health', search: garminWindow.linkSearch }),
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
id: 'reading',
|
|
232
|
-
label: 'Reading',
|
|
233
|
-
icon: <IconBook size={ICON} />,
|
|
234
|
-
link: linkOptions({ to: '/reading' }),
|
|
235
|
-
children: [
|
|
236
|
-
{
|
|
237
|
-
id: 'reading-stats',
|
|
238
|
-
label: 'Stats',
|
|
239
|
-
icon: <IconChartBar size={ICON} />,
|
|
240
|
-
link: linkOptions({ to: '/reading/stats' }),
|
|
241
|
-
},
|
|
242
|
-
],
|
|
243
|
-
},
|
|
244
|
-
]),
|
|
245
|
-
],
|
|
246
|
-
})
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Rules that are not stylistic:
|
|
250
|
-
|
|
251
|
-
- **Route options live under `link:`, wrapped in TanStack's own `linkOptions`** — never spread flat
|
|
252
|
-
onto the item. `to` / `search` / `params` get the router's full validation there, while `id`,
|
|
253
|
-
`label`, `short`, `icon`, `mobile`, `disabled` and `exact` ride outside it. A flat shape
|
|
254
|
-
(`{ id, label, to }`) validates `to` by ASSIGNABILITY, which does no excess-property checking, so
|
|
255
|
-
a typo'd metadata key would compile silently. A top-level `to:` is a compile error by design.
|
|
256
|
-
- **`search` may be a thunk, and for a store-backed param it MUST be** —
|
|
257
|
-
`search: () => ({ date: format(new Date(), 'yyyy-MM-dd') })` compiles without `as const` and
|
|
258
|
-
re-evaluates at click time, so "today" never goes stale in a long-lived tab. A param backed by
|
|
259
|
-
`createSearchParamStore` takes the store's own `linkSearch` (see "validateSearch" above); a
|
|
260
|
-
literal there is the one shape that defeats the store.
|
|
261
|
-
- **Nesting is `children`**, and nested destinations are indexed by id just like top-level ones.
|
|
262
|
-
- **`NavItemMeta`**'s `mobile` field places the destination on the bar: `'tab'` (own slot),
|
|
263
|
-
`'more'` (the default — reachable through the More overlay) or `'hidden'`. A group claims a slot
|
|
264
|
-
of its own with `mobile: { tab: true }`, and leaves mobile entirely with `mobile: false`.
|
|
265
|
-
|
|
266
|
-
```tsx
|
|
267
|
-
// __root.tsx — four lines replace a hand-written sections array, two render callbacks,
|
|
268
|
-
// a NAV_TARGETS table and one useMatchRoute call per destination.
|
|
269
|
-
import { useNav } from 'basalt-ui/router-tanstack'
|
|
270
|
-
import { NAV } from '../lib/nav'
|
|
271
|
-
|
|
272
|
-
const nav = useNav(NAV, { badges: { calendar: unread } }) // keys autocomplete; a typo is an error
|
|
273
|
-
return (
|
|
274
|
-
<BasaltShell brand={brand} {...nav}>
|
|
275
|
-
{children}
|
|
276
|
-
</BasaltShell>
|
|
277
|
-
)
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**`useNav`** returns `{ sections, mobileNav }` — both are `BasaltShell` props, so spread it. It
|
|
281
|
-
resolves `active` per destination through `useMatchRoute` (prefix match by default, exact when the
|
|
282
|
-
item sets `exact: true`, and always exact for `'/'`), and builds each destination's **`NavAnchor`**
|
|
283
|
-
so `preload="intent"`, middle-click and back/forward keep working. `badges` values that are
|
|
284
|
-
`number` become `SidebarItem.count` (a desktop **`NavCountBadge`** plus an accent dot on the mobile
|
|
285
|
-
slot); anything else becomes `SidebarItem.badge`, which is desktop-only because a count glyph is
|
|
286
|
-
unreadable in a 56px tab.
|
|
287
|
-
|
|
288
|
-
**`navTarget`** returns one destination's link options, typed per id — spread it into `<Link>`,
|
|
289
|
-
`router.navigate()` or `redirect()` instead of restating the route and its default search:
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
|
-
throw redirect(navTarget(NAV, 'garmin')) // exactly { to: '/garmin-health', search: garminWindow.linkSearch }
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
**`flattenNav`** walks the definition depth-first (parent, then children), tagging each destination
|
|
296
|
-
with `groupId` / `groupLabel` — the leaf every other surface reads, e.g. projecting the whole nav
|
|
297
|
-
into Spotlight commands in one `.map()`.
|
|
298
|
-
|
|
299
|
-
### The prerequisite that makes all of this real
|
|
300
|
-
|
|
301
|
-
**Without the `Register` module augmentation, `defineNav` validates NOTHING and reports zero
|
|
302
|
-
errors.** `RegisteredRouter` falls back to `AnyRouter` when the consumer has not written:
|
|
303
|
-
|
|
304
|
-
```ts
|
|
305
|
-
declare module '@tanstack/react-router' {
|
|
306
|
-
interface Register {
|
|
307
|
-
router: typeof router
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
With that missing, every `to` widens to `string`, a nonexistent route path compiles, a missing
|
|
313
|
-
required `search` compiles, and the API looks like it is working while catching nothing. This is
|
|
314
|
-
the single highest-value thing to verify when adopting the config: change one `to` to a garbage
|
|
315
|
-
path and confirm you get a compile error listing the real route paths. If you don't, the
|
|
316
|
-
augmentation is missing (or, on a file-route setup, `tsr generate` has not run).
|
|
317
|
-
|
|
318
|
-
Second thing to know before you migrate: whether a missing `search` is caught depends on the
|
|
319
|
-
schema's OUTPUT type, not on the `validateSearch` signature. If every key of `T` is required —
|
|
320
|
-
which is what a Zod object with defaults produces, since defaults apply on parse and the output has
|
|
321
|
-
no optionals — TanStack raises `MakeRequiredSearchParams` and the compiler catches it. Two routes in
|
|
322
|
-
one consumer had been shipping nav links with no search params behind a `to={target.to as never}`
|
|
323
|
-
cast; removing the callback surfaced both immediately.
|
|
324
|
-
|
|
325
|
-
**The caveat holds only when `T` has no required keys** (every key optional, or the validator
|
|
326
|
-
returns a partial). There, a forgotten `search` compiles — diff the old target table against the new
|
|
327
|
-
definition by hand, key by key. Everywhere else, trust the error.
|
|
328
|
-
|
|
329
|
-
Error messages here are the router's own and they are large: one wrong `to` prints the whole
|
|
330
|
-
route-path union plus a `RouterCore<…>` blob, because TanStack's `Constrain` falls back to the
|
|
331
|
-
whole constraint. That is not misuse — read the union for the intended path and fix the string.
|
|
332
|
-
**Do not reach for `as never` to make it quiet**; the checking is the entire point of the config.
|
|
333
|
-
|
|
334
|
-
Navigation itself (`.navigate()`, `<Link to={…}>`) stays the consumer's typed concern — the adapter
|
|
335
|
-
still exposes no `navigate()` helper, so TanStack's route-tree types remain authoritative. What
|
|
336
|
-
`navTarget` hands back is the ARGUMENT to those calls, never the call.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ArticleFilterBar — a fully controlled, dumb filter bar over an article list's category and tags
|
|
3
|
-
* axes (docs/CONTENT-SPEC.md). It owns no state, no store, no URL knowledge — the store lives in
|
|
4
|
-
* `basalt-ui/router-tanstack` (`createSearchParamStore`/`createMultiSearchParamStore`, headless,
|
|
5
|
-
* Mantine-free); this is the Mantine half. Wire it to that pair per `createSearchParamStore`'s
|
|
6
|
-
* 5-step JSDoc recipe (the filter is its step 3).
|
|
7
|
-
*
|
|
8
|
-
* Category renders as a `SegmentedControl` (DESIGN-SPEC §5); below the `sm` breakpoint it swaps to
|
|
9
|
-
* a `Select` via Mantine's CSS-based `visibleFrom`/`hiddenFrom` props (not `useMediaQuery` — a JS
|
|
10
|
-
* media query renders differently on server vs client and would trip a hydration mismatch, the
|
|
11
|
-
* same hazard `formatArticleDate` is careful to avoid). Tags render as a `Chip.Group` — Chips wrap
|
|
12
|
-
* natively and need no responsive fallback. The tag row is omitted entirely when `tags` is
|
|
13
|
-
* absent/empty.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* <ArticleFilterBar
|
|
17
|
-
* categories={[{ value: 'all', label: 'All' }, { value: 'guide', label: 'Guide' }]}
|
|
18
|
-
* category={category}
|
|
19
|
-
* onCategoryChange={setCategory}
|
|
20
|
-
* tags={ARTICLE_TAGS}
|
|
21
|
-
* selectedTags={tags}
|
|
22
|
-
* onTagsChange={setTags}
|
|
23
|
-
* />
|
|
24
|
-
*/
|
|
25
|
-
import type { CSSProperties } from 'react';
|
|
26
|
-
export type ArticleFilterBarProps = {
|
|
27
|
-
readonly categories: readonly {
|
|
28
|
-
readonly value: string;
|
|
29
|
-
readonly label: string;
|
|
30
|
-
}[];
|
|
31
|
-
readonly category: string;
|
|
32
|
-
readonly onCategoryChange: (next: string) => void;
|
|
33
|
-
readonly tags?: readonly string[];
|
|
34
|
-
readonly selectedTags?: readonly string[];
|
|
35
|
-
readonly onTagsChange?: (next: string[]) => void;
|
|
36
|
-
readonly className?: string;
|
|
37
|
-
readonly style?: CSSProperties;
|
|
38
|
-
};
|
|
39
|
-
export declare function ArticleFilterBar({ categories, category, onCategoryChange, tags, selectedTags, onTagsChange, className, style, }: ArticleFilterBarProps): import("react").JSX.Element;
|
|
40
|
-
//# sourceMappingURL=article-filter-bar.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"article-filter-bar.d.ts","sourceRoot":"","sources":["../../src/content/article-filter-bar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAG1C,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAChD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAC/B,CAAA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,KAAK,GACN,EAAE,qBAAqB,+BAwCvB"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Chip, Group, SegmentedControl, Select, Stack } from "@mantine/core";
|
|
3
|
-
function ArticleFilterBar({
|
|
4
|
-
categories,
|
|
5
|
-
category,
|
|
6
|
-
onCategoryChange,
|
|
7
|
-
tags,
|
|
8
|
-
selectedTags,
|
|
9
|
-
onTagsChange,
|
|
10
|
-
className,
|
|
11
|
-
style
|
|
12
|
-
}) {
|
|
13
|
-
const categoryData = categories.map(({ value, label }) => ({ value, label }));
|
|
14
|
-
const hasTags = tags !== void 0 && tags.length > 0;
|
|
15
|
-
return /* @__PURE__ */ jsxs(
|
|
16
|
-
Stack,
|
|
17
|
-
{
|
|
18
|
-
gap: "sm",
|
|
19
|
-
...className !== void 0 && { className },
|
|
20
|
-
...style !== void 0 && { style },
|
|
21
|
-
children: [
|
|
22
|
-
/* @__PURE__ */ jsx(
|
|
23
|
-
SegmentedControl,
|
|
24
|
-
{
|
|
25
|
-
visibleFrom: "sm",
|
|
26
|
-
data: categoryData,
|
|
27
|
-
value: category,
|
|
28
|
-
onChange: onCategoryChange
|
|
29
|
-
}
|
|
30
|
-
),
|
|
31
|
-
/* @__PURE__ */ jsx(
|
|
32
|
-
Select,
|
|
33
|
-
{
|
|
34
|
-
hiddenFrom: "sm",
|
|
35
|
-
data: categoryData,
|
|
36
|
-
value: category,
|
|
37
|
-
allowDeselect: false,
|
|
38
|
-
onChange: (next) => next !== null && onCategoryChange(next)
|
|
39
|
-
}
|
|
40
|
-
),
|
|
41
|
-
hasTags && /* @__PURE__ */ jsx(
|
|
42
|
-
Chip.Group,
|
|
43
|
-
{
|
|
44
|
-
multiple: true,
|
|
45
|
-
value: selectedTags !== void 0 ? [...selectedTags] : [],
|
|
46
|
-
onChange: (next) => onTagsChange?.(next),
|
|
47
|
-
children: /* @__PURE__ */ jsx(Group, { gap: "xs", wrap: "wrap", children: tags?.map((tag) => /* @__PURE__ */ jsx(Chip, { value: tag, children: tag }, tag)) })
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
export {
|
|
55
|
-
ArticleFilterBar
|
|
56
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
export declare function PageHeaderProvider({ children }: {
|
|
3
|
-
children: ReactNode;
|
|
4
|
-
}): import("react").JSX.Element;
|
|
5
|
-
/** Renders in the app-shell header — the DOM node the active page portals its actions into. */
|
|
6
|
-
export declare function PageActionsOutlet({ className }: {
|
|
7
|
-
className?: string;
|
|
8
|
-
}): import("react").JSX.Element;
|
|
9
|
-
/** Used by a page to render its control row into the top-bar page slot. */
|
|
10
|
-
export declare function PageActions({ children }: {
|
|
11
|
-
children: ReactNode;
|
|
12
|
-
}): import("react").ReactPortal | null;
|
|
13
|
-
//# sourceMappingURL=page-header.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"page-header.d.ts","sourceRoot":"","sources":["../../src/shell/page-header.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAOvE;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGtE;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,sCAGhE"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState } from "react";
|
|
3
|
-
import { createPortal } from "react-dom";
|
|
4
|
-
const TargetContext = createContext(null);
|
|
5
|
-
const SetTargetContext = createContext(() => {
|
|
6
|
-
});
|
|
7
|
-
function PageHeaderProvider({ children }) {
|
|
8
|
-
const [target, setTarget] = useState(null);
|
|
9
|
-
return /* @__PURE__ */ jsx(SetTargetContext.Provider, { value: setTarget, children: /* @__PURE__ */ jsx(TargetContext.Provider, { value: target, children }) });
|
|
10
|
-
}
|
|
11
|
-
function PageActionsOutlet({ className }) {
|
|
12
|
-
const setTarget = useContext(SetTargetContext);
|
|
13
|
-
return /* @__PURE__ */ jsx("div", { ref: setTarget, className });
|
|
14
|
-
}
|
|
15
|
-
function PageActions({ children }) {
|
|
16
|
-
const target = useContext(TargetContext);
|
|
17
|
-
return target ? createPortal(children, target) : null;
|
|
18
|
-
}
|
|
19
|
-
export {
|
|
20
|
-
PageActions,
|
|
21
|
-
PageActionsOutlet,
|
|
22
|
-
PageHeaderProvider
|
|
23
|
-
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ArticleFilterBar — a fully controlled, dumb filter bar over an article list's category and tags
|
|
3
|
-
* axes (docs/CONTENT-SPEC.md). It owns no state, no store, no URL knowledge — the store lives in
|
|
4
|
-
* `basalt-ui/router-tanstack` (`createSearchParamStore`/`createMultiSearchParamStore`, headless,
|
|
5
|
-
* Mantine-free); this is the Mantine half. Wire it to that pair per `createSearchParamStore`'s
|
|
6
|
-
* 5-step JSDoc recipe (the filter is its step 3).
|
|
7
|
-
*
|
|
8
|
-
* Category renders as a `SegmentedControl` (DESIGN-SPEC §5); below the `sm` breakpoint it swaps to
|
|
9
|
-
* a `Select` via Mantine's CSS-based `visibleFrom`/`hiddenFrom` props (not `useMediaQuery` — a JS
|
|
10
|
-
* media query renders differently on server vs client and would trip a hydration mismatch, the
|
|
11
|
-
* same hazard `formatArticleDate` is careful to avoid). Tags render as a `Chip.Group` — Chips wrap
|
|
12
|
-
* natively and need no responsive fallback. The tag row is omitted entirely when `tags` is
|
|
13
|
-
* absent/empty.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* <ArticleFilterBar
|
|
17
|
-
* categories={[{ value: 'all', label: 'All' }, { value: 'guide', label: 'Guide' }]}
|
|
18
|
-
* category={category}
|
|
19
|
-
* onCategoryChange={setCategory}
|
|
20
|
-
* tags={ARTICLE_TAGS}
|
|
21
|
-
* selectedTags={tags}
|
|
22
|
-
* onTagsChange={setTags}
|
|
23
|
-
* />
|
|
24
|
-
*/
|
|
25
|
-
import type { CSSProperties } from 'react'
|
|
26
|
-
import { Chip, Group, SegmentedControl, Select, Stack } from '@mantine/core'
|
|
27
|
-
|
|
28
|
-
export type ArticleFilterBarProps = {
|
|
29
|
-
readonly categories: readonly { readonly value: string; readonly label: string }[]
|
|
30
|
-
readonly category: string
|
|
31
|
-
readonly onCategoryChange: (next: string) => void
|
|
32
|
-
readonly tags?: readonly string[]
|
|
33
|
-
readonly selectedTags?: readonly string[]
|
|
34
|
-
readonly onTagsChange?: (next: string[]) => void
|
|
35
|
-
readonly className?: string
|
|
36
|
-
readonly style?: CSSProperties
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function ArticleFilterBar({
|
|
40
|
-
categories,
|
|
41
|
-
category,
|
|
42
|
-
onCategoryChange,
|
|
43
|
-
tags,
|
|
44
|
-
selectedTags,
|
|
45
|
-
onTagsChange,
|
|
46
|
-
className,
|
|
47
|
-
style,
|
|
48
|
-
}: ArticleFilterBarProps) {
|
|
49
|
-
const categoryData = categories.map(({ value, label }) => ({ value, label }))
|
|
50
|
-
const hasTags = tags !== undefined && tags.length > 0
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<Stack
|
|
54
|
-
gap="sm"
|
|
55
|
-
{...(className !== undefined && { className })}
|
|
56
|
-
{...(style !== undefined && { style })}
|
|
57
|
-
>
|
|
58
|
-
<SegmentedControl
|
|
59
|
-
visibleFrom="sm"
|
|
60
|
-
data={categoryData}
|
|
61
|
-
value={category}
|
|
62
|
-
onChange={onCategoryChange}
|
|
63
|
-
/>
|
|
64
|
-
<Select
|
|
65
|
-
hiddenFrom="sm"
|
|
66
|
-
data={categoryData}
|
|
67
|
-
value={category}
|
|
68
|
-
allowDeselect={false}
|
|
69
|
-
onChange={(next) => next !== null && onCategoryChange(next)}
|
|
70
|
-
/>
|
|
71
|
-
{hasTags && (
|
|
72
|
-
<Chip.Group
|
|
73
|
-
multiple
|
|
74
|
-
value={selectedTags !== undefined ? [...selectedTags] : []}
|
|
75
|
-
onChange={(next) => onTagsChange?.(next)}
|
|
76
|
-
>
|
|
77
|
-
<Group gap="xs" wrap="wrap">
|
|
78
|
-
{tags?.map((tag) => (
|
|
79
|
-
<Chip key={tag} value={tag}>
|
|
80
|
-
{tag}
|
|
81
|
-
</Chip>
|
|
82
|
-
))}
|
|
83
|
-
</Group>
|
|
84
|
-
</Chip.Group>
|
|
85
|
-
)}
|
|
86
|
-
</Stack>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Page-header slot system. The slim app-shell top bar carries a GLOBAL slot (shell-owned) and a
|
|
3
|
-
* PAGE slot: the active route portals its full control row into the bar via `PageActions`, so each
|
|
4
|
-
* page owns its actions without a separate in-body header.
|
|
5
|
-
*
|
|
6
|
-
* Grounded verbatim in argo `apps/dashboard/src/components/app-shell/page-header.tsx`. This is a
|
|
7
|
-
* real, working implementation (the slot/portal mechanism has no app coupling) — not a stub.
|
|
8
|
-
*/
|
|
9
|
-
import { createContext, useContext, useState } from 'react'
|
|
10
|
-
import type { ReactNode } from 'react'
|
|
11
|
-
import { createPortal } from 'react-dom'
|
|
12
|
-
|
|
13
|
-
const TargetContext = createContext<HTMLElement | null>(null)
|
|
14
|
-
const SetTargetContext = createContext<(el: HTMLElement | null) => void>(() => {})
|
|
15
|
-
|
|
16
|
-
export function PageHeaderProvider({ children }: { children: ReactNode }) {
|
|
17
|
-
const [target, setTarget] = useState<HTMLElement | null>(null)
|
|
18
|
-
return (
|
|
19
|
-
<SetTargetContext.Provider value={setTarget}>
|
|
20
|
-
<TargetContext.Provider value={target}>{children}</TargetContext.Provider>
|
|
21
|
-
</SetTargetContext.Provider>
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Renders in the app-shell header — the DOM node the active page portals its actions into. */
|
|
26
|
-
export function PageActionsOutlet({ className }: { className?: string }) {
|
|
27
|
-
const setTarget = useContext(SetTargetContext)
|
|
28
|
-
return <div ref={setTarget} className={className} />
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Used by a page to render its control row into the top-bar page slot. */
|
|
32
|
-
export function PageActions({ children }: { children: ReactNode }) {
|
|
33
|
-
const target = useContext(TargetContext)
|
|
34
|
-
return target ? createPortal(children, target) : null
|
|
35
|
-
}
|