basalt-ui 1.25.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 +18 -0
- package/MIGRATING.md +546 -20
- package/README.md +45 -32
- 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/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 +37 -15
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +88 -58
- 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/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 +96 -11
- package/src/data/data-table.tsx +220 -84
- 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 -329
- 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,246 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
source: basalt-ui
|
|
3
|
-
description: App bootstrapping for basalt-ui consumers — composing basaltViteConfig + basaltAppPlugin in vite.config.ts, plugin ordering, the required Mantine CSS layer order, and the PWA/favicon/manifest surface (bring-your-own icons, opt-in serviceWorker).
|
|
4
|
-
paths:
|
|
5
|
-
- 'vite.config.ts'
|
|
6
|
-
- 'apps/**/vite.config.ts'
|
|
7
|
-
- 'index.html'
|
|
8
|
-
- 'apps/**/index.html'
|
|
9
|
-
- 'src/main.tsx'
|
|
10
|
-
- 'apps/**/src/main.tsx'
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Basalt App — Bootstrapping & Vite Config
|
|
14
|
-
|
|
15
|
-
`basalt-ui/vite` ships two entry points with a deliberate split: `basaltViteConfig` is **config
|
|
16
|
-
only**, `basaltAppPlugin` is the **plugin** half. This rule covers composing them, where
|
|
17
|
-
`basaltAppPlugin` sits relative to other plugins, and the head/PWA/manifest surface it generates.
|
|
18
|
-
|
|
19
|
-
## Composing the vite config
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
// vite.config.ts
|
|
23
|
-
import react from '@vitejs/plugin-react'
|
|
24
|
-
import { basaltAppPlugin, basaltViteConfig } from 'basalt-ui/vite'
|
|
25
|
-
import { defineConfig } from 'vite'
|
|
26
|
-
|
|
27
|
-
export default defineConfig({
|
|
28
|
-
...basaltViteConfig({ port: 5173, apiTarget: 'http://localhost:3000' }),
|
|
29
|
-
plugins: [react(), ...basaltAppPlugin({ name: 'MyApp', description: '…' })],
|
|
30
|
-
})
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`basaltAppPlugin(opts)` returns `PluginOption[]` (a Vite plugin array, not a single plugin) — spread
|
|
34
|
-
it into your own `plugins` array with `...basaltAppPlugin({...})`, same as any other Vite plugin
|
|
35
|
-
that ships more than one plugin instance.
|
|
36
|
-
|
|
37
|
-
`basaltViteConfig` **never returns `plugins`** and never will — it stays config-only by contract.
|
|
38
|
-
Plugins carry app-specific concerns (router codegen, PWA, `react-compiler` babel options) that don't
|
|
39
|
-
belong in a shared, app-agnostic spine; keeping them out of `basaltViteConfig` means adopting the
|
|
40
|
-
preset never silently injects a plugin you didn't ask for, and `basaltAppPlugin` can be adopted (or
|
|
41
|
-
skipped) independently of the rest of the preset.
|
|
42
|
-
|
|
43
|
-
## Plugin ordering
|
|
44
|
-
|
|
45
|
-
`basaltAppPlugin`'s hooks (`transformIndexHtml`, `generateBundle`, `configureServer`) don't
|
|
46
|
-
generate a virtual module another plugin resolves against — unlike
|
|
47
|
-
`@content-collections/vite`, whose plugin-ordering rule (agent/rules/basalt-content.md) requires
|
|
48
|
-
`contentCollections()` to run FIRST because it generates the virtual `content-collections` module
|
|
49
|
-
other plugins/transforms may depend on. `basaltAppPlugin` has no equivalent requirement, so when
|
|
50
|
-
composing both:
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
plugins: [
|
|
54
|
-
contentCollections(), // FIRST — see basalt-content.md
|
|
55
|
-
react(), // or tanstackStart()
|
|
56
|
-
...basaltAppPlugin({ name: 'MyApp' }), // position is free — see below
|
|
57
|
-
]
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Position is a readability convention here, not a correctness requirement — including with
|
|
61
|
-
`serviceWorker` enabled. `vite-plugin-pwa` expands to five plugins that **self-order** via
|
|
62
|
-
`enforce` (`vite-plugin-pwa` is `enforce: 'pre'`; `:build`, `:info` and `:pwa-assets` are
|
|
63
|
-
`enforce: 'post'`), and Vite sorts by `enforce` before array position. Its precache manifest is
|
|
64
|
-
built in `closeBundle`, where `workbox-build` globs the **output directory on disk** — every other
|
|
65
|
-
plugin's transforms and emits have already landed there. So no ordering relationship exists between
|
|
66
|
-
`basaltAppPlugin` and `react()`/`tanstackStart()`.
|
|
67
|
-
|
|
68
|
-
The one real caveat is a plugin that **emits files from its own `closeBundle`**: if it runs after
|
|
69
|
-
`vite-plugin-pwa`'s, those files are written too late to be precached (the `rollup-plugin-copy`
|
|
70
|
-
class of bug). `vite-plugin-pwa` exposes `integration.closeBundleOrder` to sequence against it.
|
|
71
|
-
|
|
72
|
-
## What `basalt-ui init` writes into `package.json`
|
|
73
|
-
|
|
74
|
-
`init` is not only a file-scaffolder. It also patches two keys, and on an existing app it is a
|
|
75
|
-
**lint-debt event, not a no-op** — the shipped preset switches on whole oxlint plugins the repo was
|
|
76
|
-
never linted against, so previously-clean code lands with real findings on the first run. `init`
|
|
77
|
-
prints the plugin list; run `oxlint .` and triage it before your next commit rather than
|
|
78
|
-
blanket-disabling a plugin.
|
|
79
|
-
|
|
80
|
-
- **`basalt.roots`** — inferred from the real layout (workspace packages depending on basalt or
|
|
81
|
-
Mantine, else every workspace `src/`, else `src`). Everything derives from it: `check-theme`'s
|
|
82
|
-
scan, the seeded CI `oxfmt` globs, the default scan exemption. Correct it if your sources live
|
|
83
|
-
elsewhere; without it a workspace repo scaffolds a guard that scans zero files.
|
|
84
|
-
- **`scripts["lint:basalt"]`** — `oxlint . && basalt-ui check-theme`. Previously the docs told you
|
|
85
|
-
to wire this and nothing wrote it.
|
|
86
|
-
- **`basalt.profile: 'tokens-only'`** — for a consumer on the `--vx-*` layer with no Mantine. It
|
|
87
|
-
must be DECLARED (or passed as `--tokens-only`); `check-theme` deliberately never infers it,
|
|
88
|
-
because inferring from a missing `@mantine/core` would silence 17 kinds on any repo that keeps
|
|
89
|
-
Mantine in a different workspace package. `doctor` does infer it, because its profile only changes
|
|
90
|
-
which ADVICE it prints and never what it enforces — and it names the key to write down.
|
|
91
|
-
|
|
92
|
-
`init` keeps an existing `.oxlintrc.json` rather than clobbering it, and now names what keeping it
|
|
93
|
-
costs (the whole basalt lint half stays off). `--merge-lint` splices the preset's `extends` in; it
|
|
94
|
-
refuses on a commented config rather than deleting the comments.
|
|
95
|
-
|
|
96
|
-
Run `basalt-ui doctor` afterwards to confirm the wiring took. A check that cannot RUN is reported as
|
|
97
|
-
`SKIPPED` and exits non-zero on its own — "All checks passed" is only printable when every check ran.
|
|
98
|
-
|
|
99
|
-
### The lefthook preset overrides YOU, not the other way round
|
|
100
|
-
|
|
101
|
-
`doctor`'s `lefthook-preset` check asks whether a pre-commit gate EXISTS — not whether your config
|
|
102
|
-
contains the extends string. Since 1.22.0 it asks `lefthook dump`, which resolves `extends`,
|
|
103
|
-
`include` and per-command `root:`. **Wiring the jobs yourself passes**: linewatch runs all three with
|
|
104
|
-
`root: 'web/'` precisely because `extends` merges commands _without_ their working directory, and the
|
|
105
|
-
old text match warned there and prescribed a change that would have broken it.
|
|
106
|
-
|
|
107
|
-
A broken `extends` target is still a hard fail — lefthook merges a missing target into **zero**
|
|
108
|
-
commands and exits 0, so `lefthook dump` looks clean and the repo has no gate with nothing saying so.
|
|
109
|
-
A provably absent gate is a warn; a repo where `lefthook dump` could not run gets an advisory warning
|
|
110
|
-
naming what it could not see. n/a with no lefthook at all. `sync` reports the same seam, since it is
|
|
111
|
-
the command an upgrade actually runs and neither file is one basalt owns.
|
|
112
|
-
|
|
113
|
-
**An `extends` target WINS on a colliding key.** Declare `pre-commit.commands.oxfmt.run` (or
|
|
114
|
-
`glob:`) in your own file and the preset's value is what runs — yours is discarded silently. Only
|
|
115
|
-
keys the preset does not define merge in. So `env:`, `exclude:`, `skip:` and a differently-named
|
|
116
|
-
command of your own are yours; `run:` and `glob:` on a shipped command are not. The sanctioned seam
|
|
117
|
-
is therefore `BASALT_BIN`: the guard job runs `${BASALT_BIN:-bunx --no-install basalt-ui}`, and the
|
|
118
|
-
shell default is the whole reason the seam exists at all. `init` renders it with the resolved local
|
|
119
|
-
bin. `--no-install` is deliberate — a bare `bunx basalt-ui` in a repo where basalt is not resolvable
|
|
120
|
-
from the root silently downloads a different copy from npm and gates the commit with a version
|
|
121
|
-
nobody pinned.
|
|
122
|
-
|
|
123
|
-
## `index.html` and `public/` are scanned
|
|
124
|
-
|
|
125
|
-
`check-theme` resolves `.html` / `.webmanifest` / `.json` as markup (colour kinds only), and each
|
|
126
|
-
`basalt.roots` entry's PARENT contributes its `index.html` and its `public/` tree — exactly the Vite
|
|
127
|
-
layout above. So a raw hex in a `theme-color` meta or a webmanifest's `background_color` is a
|
|
128
|
-
finding now; those are the two colours nothing re-derives on a scheme change. Nothing else in the
|
|
129
|
-
parent is walked, and `.json` is never blanket-scanned — name one in `basalt.include`.
|
|
130
|
-
|
|
131
|
-
## `__APP_VERSION__`
|
|
132
|
-
|
|
133
|
-
`basaltViteConfig` injects the `__APP_VERSION__` define, and since 1.20.0 ships the ambient
|
|
134
|
-
declaration with it (`src/register.ts` → `dist/register.d.ts`, re-exported by the root barrel). An
|
|
135
|
-
app that imports anything from `basalt-ui` has it in scope — delete your hand-written ambient block
|
|
136
|
-
and its `oxlint-disable`. A subpath-only consumer does not get it, which is the same set as the
|
|
137
|
-
consumers not on this Vite preset anyway.
|
|
138
|
-
|
|
139
|
-
## CSS layer order (Mantine)
|
|
140
|
-
|
|
141
|
-
`basalt-ui/styles.css` must load AFTER every `@mantine/*/styles.layer.css` bundle — the layered
|
|
142
|
-
bundles order `@layer mantine, basalt` so basalt's rules (including the iOS 16px input floor) win;
|
|
143
|
-
an unlayered Mantine import outranks `@layer basalt` regardless of specificity. In `main.tsx`:
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
import '@mantine/core/styles.layer.css'
|
|
147
|
-
// ...other @mantine/*/styles.layer.css bundles for any batteries you install
|
|
148
|
-
import 'basalt-ui/styles.css'
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## Head, manifest, and icons — bring your own
|
|
152
|
-
|
|
153
|
-
`basaltAppPlugin` injects dual `theme-color` tags (light/dark media queries) and an anti-FOUC inline
|
|
154
|
-
`<style>` background, both resolved from the `SURFACE.bg` token — never hand-compute these hexes,
|
|
155
|
-
they track the palette automatically. It also injects the `apple-mobile-web-app-*` /
|
|
156
|
-
`mobile-web-app-capable` meta set, a `darkreader-lock` meta (default on), a viewport tag with
|
|
157
|
-
`viewport-fit=cover` (skipped when the consumer's own `index.html` already declares one), and
|
|
158
|
-
site-wide OG/Twitter defaults from `options.seo`.
|
|
159
|
-
|
|
160
|
-
**Set `colorScheme` to whatever the app passes as `defaultColorScheme`** — `'dark'` (the default),
|
|
161
|
-
`'light'`, `'auto'` (declares `color-scheme: light dark` and picks the pre-boot background off
|
|
162
|
-
`prefers-color-scheme`), or `false` (paint the background, declare no `color-scheme`).
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
plugins: [react(), ...basaltAppPlugin({ name: 'Argo', colorScheme: 'auto' })]
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Before 1.21.0 the anti-FOUC rule emitted `html{color-scheme:dark}` **unlayered**, which outranks
|
|
169
|
-
Mantine's own `@layer mantine` declaration regardless of specificity — a light-scheme consumer got
|
|
170
|
-
dark scrollbars, dark `<select>` popups and dark date pickers permanently, with no opt-out. The rule
|
|
171
|
-
is now scoped to `html:not([data-mantine-color-scheme])`, so it expires the instant MantineProvider
|
|
172
|
-
resolves the real scheme. A mismatched `colorScheme` is therefore a flash of the wrong surface, not
|
|
173
|
-
a stuck one.
|
|
174
|
-
|
|
175
|
-
**The plugin hoists your `<meta charset>`.** Every injected tag defaults to `head-prepend`, which
|
|
176
|
-
landed ~20 tags ahead of the consumer's own encoding declaration — measured at byte 1653 on a
|
|
177
|
-
realistic shell, past the 1024-byte window the HTML spec gives it, with no consumer-side fix
|
|
178
|
-
available. The plugin now re-emits the declaration as its own first head-prepend tag (46 bytes after
|
|
179
|
-
the fix) and runs `order: 'pre'` so nothing else can land ahead of it. The legacy `http-equiv` form
|
|
180
|
-
is detected and left verbatim.
|
|
181
|
-
|
|
182
|
-
Icons are **bring-your-own** — basalt-ui takes no `sharp`/image-processing dependency. The plugin
|
|
183
|
-
expects these filenames under `public/` (or `icons.dir` if customized):
|
|
184
|
-
|
|
185
|
-
```text
|
|
186
|
-
favicon.ico
|
|
187
|
-
favicon.svg
|
|
188
|
-
favicon-96x96.png
|
|
189
|
-
apple-touch-icon.png
|
|
190
|
-
web-app-manifest-192x192.png
|
|
191
|
-
web-app-manifest-512x512.png
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Generate the set with `@vite-pwa/assets-generator` (or realfavicongenerator.net) from a single
|
|
195
|
-
source image — it lives in the CONSUMER's own devDependencies, not basalt-ui's. `basalt-ui doctor`
|
|
196
|
-
warns (does not fail) when `public/` exists but is missing one of these files — and it now
|
|
197
|
-
parses `basaltAppPlugin`'s `icons` option out of your vite config first, so it checks the files you
|
|
198
|
-
declared rather than the six it assumes. `icons: false` or an empty array is "nothing to check"; an
|
|
199
|
-
unparseable or absent config falls back to the six defaults. Adopting the array is no longer a way
|
|
200
|
-
to earn a warning.
|
|
201
|
-
|
|
202
|
-
**If your `public/` does not match those six filenames, name your icons instead.** `icons` takes
|
|
203
|
-
`false | { dir?: string } | readonly BasaltAppIcon[]`, and an array uses the manifest's own field
|
|
204
|
-
names verbatim:
|
|
205
|
-
|
|
206
|
-
```ts
|
|
207
|
-
basaltAppPlugin({
|
|
208
|
-
name: 'rb',
|
|
209
|
-
icons: [{ src: '/favicon.svg', sizes: 'any', type: 'image/svg+xml' }],
|
|
210
|
-
})
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
`BasaltAppIcon` is `{ src, sizes?, type?, purpose?, rel? }`, `rel` being one of `'icon'`,
|
|
214
|
-
`'shortcut icon'`, `'apple-touch-icon'`, `'mask-icon'`. **Every entry becomes a manifest icon; an
|
|
215
|
-
entry reaches the head only when it names a `rel`** — which is what lets an app whose `index.html`
|
|
216
|
-
already links its favicon take the generated manifest without a duplicate tag. An empty array reads
|
|
217
|
-
as `false` (no `icons` member, rather than an empty one). `{ dir }` and the default are byte-identical
|
|
218
|
-
to what they always emitted.
|
|
219
|
-
|
|
220
|
-
One SVG at `sizes: 'any'` is a complete, installable icon set for a single-page app — and it is the
|
|
221
|
-
shape every non-scaffolded consumer already has. If you keep a hand-written `manifest.webmanifest`
|
|
222
|
-
only because the plugin could not name your icon, delete it: the generated manifest reproduces it
|
|
223
|
-
member for member, including the two hexes derived from `SURFACE.bg` rather than hand-copied, and it
|
|
224
|
-
takes the permanent `theme-allow-file` on that file with it. **Delete that app's own
|
|
225
|
-
`<link rel="manifest">` in the same change** — the plugin injects its own, so leaving both ships two
|
|
226
|
-
manifest links pointing at different files, one of them a 404 the moment the hand-written file goes.
|
|
227
|
-
|
|
228
|
-
`basaltAppPlugin` also emits `site.webmanifest` (served in dev too) with explicit `id`/`scope`/
|
|
229
|
-
`start_url` — pass `manifest: false` to skip it, or `icons: false` to skip the icons. Since 1.22.0
|
|
230
|
-
`icons: false` omits BOTH the head `<link>` tags and the manifest's `icons` member; before that it
|
|
231
|
-
skipped only the head, so a manifest shipped naming two PNGs the app never builds.
|
|
232
|
-
|
|
233
|
-
## Service worker (opt-in)
|
|
234
|
-
|
|
235
|
-
`serviceWorker` defaults to `false` — no service worker unless you ask for it. `true` composes the
|
|
236
|
-
optional peer `vite-plugin-pwa` with argo-derived workbox defaults (`autoUpdate` registration,
|
|
237
|
-
`cleanupOutdatedCaches`, a `/index.html` navigate fallback that denies `/api/*`); pass an object to
|
|
238
|
-
deep-merge overrides on top. When the peer (plus its own peers `workbox-build`/`workbox-window`) is
|
|
239
|
-
not installed, the plugin degrades to a one-line console warning — no service worker, no throw:
|
|
240
|
-
|
|
241
|
-
```bash
|
|
242
|
-
bun add -D vite-plugin-pwa workbox-build workbox-window
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
`basaltAppPlugin` always emits its own `site.webmanifest` — `serviceWorker`'s `manifest: false` is
|
|
246
|
-
baked into the defaults so `vite-plugin-pwa` never emits a competing one.
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
source: basalt-ui
|
|
3
|
-
description: Command bus, overlay controller, Spotlight projection, shortcuts display, and composable overlay mount from the shipped ./commands battery. @mantine/modals + @mantine/spotlight are optional peers; @mantine/notifications is already a peer from ./notifications.
|
|
4
|
-
paths:
|
|
5
|
-
- 'src/**'
|
|
6
|
-
- 'apps/**/src/**'
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Basalt Commands
|
|
10
|
-
|
|
11
|
-
basalt-ui ships `./commands` — a Mantine-coupled command bus + overlay controller battery providing
|
|
12
|
-
`defineCommands`, `runCommand`, `defineOverlays`, `overlays`, `toSpotlightActions`,
|
|
13
|
-
`toShortcutList`, `ShortcutsHelp`, and `BasaltOverlays` on top of `@mantine/modals` and
|
|
14
|
-
`@mantine/spotlight`. Both are **optional peers** — install them before using this battery. The
|
|
15
|
-
default `BasaltOverlays` also mounts `<Notifications>` (or pass `notifications={false}`), so
|
|
16
|
-
`@mantine/notifications` is needed too:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
bun add @mantine/modals @mantine/spotlight @mantine/notifications
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Also import Spotlight styles in `main.tsx` — use the **layered** bundle, since the unlayered one
|
|
23
|
-
outranks basalt's `@layer basalt` styles regardless of specificity:
|
|
24
|
-
|
|
25
|
-
```tsx
|
|
26
|
-
import '@mantine/spotlight/styles.layer.css'
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Mount BasaltOverlays
|
|
30
|
-
|
|
31
|
-
`BasaltOverlays` is the composable overlay mount — it bundles `ModalsProvider`, `Spotlight`, and
|
|
32
|
-
`Notifications` into a single mount point. Put it inside `BasaltProvider`, before the router. It
|
|
33
|
-
**replaces** `<BasaltNotifications />` from `basalt-ui/notifications` — do NOT mount both in the
|
|
34
|
-
same tree (that would double-mount `<Notifications />`).
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
import { BasaltOverlays } from 'basalt-ui/commands'
|
|
38
|
-
import { BasaltProvider } from 'basalt-ui'
|
|
39
|
-
import '@mantine/spotlight/styles.layer.css'
|
|
40
|
-
|
|
41
|
-
createRoot(root).render(
|
|
42
|
-
<BasaltProvider>
|
|
43
|
-
<BasaltOverlays>
|
|
44
|
-
<App />
|
|
45
|
-
</BasaltOverlays>
|
|
46
|
-
</BasaltProvider>,
|
|
47
|
-
)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Each layer is enabled by default; pass `false` to disable:
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
<BasaltOverlays spotlight={false} notifications={false}>
|
|
54
|
-
<App />
|
|
55
|
-
</BasaltOverlays>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## defineCommands + runCommand
|
|
59
|
-
|
|
60
|
-
`defineCommands` is a const-generic factory (mirrors `defineSeries`/`defineNotifications`) for
|
|
61
|
-
typed command registries. Augment `BasaltRegister.commands` once, then use `runCommand` — unknown
|
|
62
|
-
ids are tsc errors.
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
// commands.ts (app-side) — define and augment once:
|
|
66
|
-
import { defineCommands } from 'basalt-ui/commands'
|
|
67
|
-
|
|
68
|
-
export const COMMANDS = defineCommands({
|
|
69
|
-
'file:save': { label: 'Save file', group: 'File', shortcut: 'Mod+S', run: () => save() },
|
|
70
|
-
'file:open': { label: 'Open file', group: 'File', shortcut: 'Mod+O', run: () => open() },
|
|
71
|
-
'data:sync': { label: 'Sync data', group: 'Data', run: async () => sync() },
|
|
72
|
-
'help:docs': { label: 'Open docs', group: 'Help', run: () => window.open('/docs') },
|
|
73
|
-
'edit:focus': {
|
|
74
|
-
label: 'Focus editor',
|
|
75
|
-
group: 'Edit',
|
|
76
|
-
shortcut: 'Mod+E',
|
|
77
|
-
when: () => isEditorPage(), // hides from palette when not on editor page
|
|
78
|
-
run: () => focusEditor(),
|
|
79
|
-
},
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
declare module 'basalt-ui' {
|
|
83
|
-
interface BasaltRegister {
|
|
84
|
-
commands: typeof COMMANDS
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
// usage.ts — typed runCommand; unknown ids are tsc errors:
|
|
91
|
-
import { runCommand } from 'basalt-ui/commands'
|
|
92
|
-
|
|
93
|
-
runCommand('file:save') // ✓ sync
|
|
94
|
-
await runCommand('data:sync') // ✓ async
|
|
95
|
-
runCommand('nonexistent') // ✗ tsc error
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### CommandId + CommandRunContext
|
|
99
|
-
|
|
100
|
-
`CommandId` is `Extract<keyof Slot<'commands', CommandMap>, string>`.
|
|
101
|
-
Un-augmented: `never` → `runCommand` is effectively uncallable.
|
|
102
|
-
Augmented: the exact registered key literal union.
|
|
103
|
-
|
|
104
|
-
`CommandRunContext` is minimal (`{ close?: () => void }`) — pass it to close the command palette
|
|
105
|
-
before executing:
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
runCommand('file:save', { close: () => spotlight.close() })
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### CRITICAL — runtime stash pattern
|
|
112
|
-
|
|
113
|
-
The `BasaltRegister` slot augmentation is **type-only and erased at runtime**. `defineCommands`
|
|
114
|
-
stashes the spec map in a module-level `activeCommands` variable; `runCommand` reads from THAT
|
|
115
|
-
stash — NOT from `{} as Commands`. This is the same pattern as `defineNotifications` / `emit`.
|
|
116
|
-
Always call `defineCommands` once before any call to `runCommand` (typically imported at app boot).
|
|
117
|
-
|
|
118
|
-
### TS circular-inference footgun
|
|
119
|
-
|
|
120
|
-
Calling `overlays.open(...)` inside a `run` handler, in the **same module that also augments**
|
|
121
|
-
`BasaltRegister.commands` (i.e. the file with `declare module 'basalt-ui' { interface
|
|
122
|
-
BasaltRegister { commands: typeof COMMANDS } }`), can trip TS into inferring `COMMANDS` as
|
|
123
|
-
`implicitly has type 'any'` — the `run` return type and the `commands` slot type end up in a
|
|
124
|
-
circular inference loop. Fix: give that `run` an explicit `: void` (or `: Promise<void>` for async)
|
|
125
|
-
return annotation. Prefer splitting overlay-opening commands into a separate file from the
|
|
126
|
-
`defineCommands`/`declare module` augmentation when practical — it sidesteps the cycle entirely.
|
|
127
|
-
|
|
128
|
-
## defineOverlays + overlays
|
|
129
|
-
|
|
130
|
-
`defineOverlays` registers ephemeral modal overlays. `overlays.open` is the imperative controller.
|
|
131
|
-
|
|
132
|
-
```ts
|
|
133
|
-
// overlays.ts (app-side) — define and augment once:
|
|
134
|
-
import { defineOverlays } from 'basalt-ui/commands'
|
|
135
|
-
|
|
136
|
-
export const OVERLAYS = defineOverlays({
|
|
137
|
-
'user:edit': {
|
|
138
|
-
title: 'Edit user',
|
|
139
|
-
render: (p: { id: string; name: string }) => <UserEditForm id={p.id} name={p.name} />,
|
|
140
|
-
},
|
|
141
|
-
'confirm:delete': {
|
|
142
|
-
title: 'Delete item?',
|
|
143
|
-
render: (p: { name: string }) => <ConfirmBody name={p.name} />,
|
|
144
|
-
},
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
declare module 'basalt-ui' {
|
|
148
|
-
interface BasaltRegister { overlays: typeof OVERLAYS }
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
// usage.ts — typed overlays.open; unknown keys are tsc errors:
|
|
154
|
-
import { overlays } from 'basalt-ui/commands'
|
|
155
|
-
|
|
156
|
-
overlays.open('user:edit', { id: '42', name: 'Alice' }) // ✓ typed
|
|
157
|
-
overlays.open('confirm:delete', { name: 'photo.jpg' }) // ✓
|
|
158
|
-
overlays.open('nonexistent', {}) // ✗ tsc error
|
|
159
|
-
overlays.close() // close all open modals
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
`OverlayKey` is `Extract<keyof Slot<'overlays', OverlayMap>, string>`.
|
|
163
|
-
The same runtime-stash pattern applies — `defineOverlays` stashes to `activeOverlays`.
|
|
164
|
-
|
|
165
|
-
### Imperative vs route (when to use each)
|
|
166
|
-
|
|
167
|
-
| Use `overlays.open` | Use route mask (`./router-tanstack`) |
|
|
168
|
-
| ------------------------------------- | ------------------------------------------ |
|
|
169
|
-
| Ephemeral confirms, quick-edit panels | Shareable/back-button/refreshable overlays |
|
|
170
|
-
| No URL needed | URL-addressable dialogs |
|
|
171
|
-
| Triggered from commands or buttons | Triggered by navigation |
|
|
172
|
-
|
|
173
|
-
## Spotlight projection (toSpotlightActions)
|
|
174
|
-
|
|
175
|
-
`toSpotlightActions()` projects the active command registry to `SpotlightActionData[]` for Mantine
|
|
176
|
-
Spotlight. Commands where `when()` returns `false` are excluded. `BasaltOverlays` calls it
|
|
177
|
-
automatically with `shortcut="mod + K"`.
|
|
178
|
-
|
|
179
|
-
```ts
|
|
180
|
-
import { toSpotlightActions } from 'basalt-ui/commands'
|
|
181
|
-
import { Spotlight, spotlight } from '@mantine/spotlight'
|
|
182
|
-
|
|
183
|
-
// Manual Spotlight mount (if not using BasaltOverlays):
|
|
184
|
-
<Spotlight actions={toSpotlightActions()} shortcut="mod + K" />
|
|
185
|
-
|
|
186
|
-
// Programmatically open the palette:
|
|
187
|
-
spotlight.open()
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
## Shortcut display (toShortcutList + ShortcutsHelp)
|
|
191
|
-
|
|
192
|
-
`toShortcutList()` returns commands with a `shortcut` field as a flat list. `ShortcutsHelp`
|
|
193
|
-
renders them in a grouped Mantine layout with platform-aware labels (⌘ on mac, Ctrl elsewhere).
|
|
194
|
-
|
|
195
|
-
**Display + live binding.** `ShortcutsHelp` renders the shortcuts for reference, and the same
|
|
196
|
-
`shortcut` field is bound to live keys by `useCommandHotkeys()` (via the `@tanstack/react-hotkeys`
|
|
197
|
-
optional peer). Spotlight `mod + K` covers the palette trigger.
|
|
198
|
-
|
|
199
|
-
```tsx
|
|
200
|
-
import { ShortcutsHelp } from 'basalt-ui/commands'
|
|
201
|
-
|
|
202
|
-
// In a help modal or settings panel:
|
|
203
|
-
;<ShortcutsHelp title="Keyboard shortcuts" maw={480} />
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
```ts
|
|
207
|
-
import { toShortcutList } from 'basalt-ui/commands'
|
|
208
|
-
const list = toShortcutList()
|
|
209
|
-
// [{ id: 'file:save', label: 'Save file', shortcut: 'Mod+S', group: 'File' }, ...]
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## Live keybindings (useCommandHotkeys)
|
|
213
|
-
|
|
214
|
-
`useCommandHotkeys()` binds all registered command shortcuts to their `run()` functions via
|
|
215
|
-
`@tanstack/react-hotkeys`. It is an **optional-peer** hook — degrades to a no-op when the peer is
|
|
216
|
-
absent; core resolves cleanly either way. No `HotkeysProvider` wrapper is required.
|
|
217
|
-
|
|
218
|
-
Install the optional peer:
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
bun add @tanstack/react-hotkeys
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Mount pattern
|
|
225
|
-
|
|
226
|
-
**BasaltOverlays mounts it automatically** (via the internal `HotkeysMount` component) when
|
|
227
|
-
`hotkeys={true}` (default). No extra setup needed if using `BasaltOverlays`.
|
|
228
|
-
|
|
229
|
-
For manual mounting, call `useCommandHotkeys()` once in the component tree, after `defineCommands`:
|
|
230
|
-
|
|
231
|
-
```tsx
|
|
232
|
-
import { useCommandHotkeys } from 'basalt-ui/commands'
|
|
233
|
-
|
|
234
|
-
function App() {
|
|
235
|
-
// Activate all registered command shortcuts — call once per tree, not per-command.
|
|
236
|
-
useCommandHotkeys()
|
|
237
|
-
return <RouterProvider router={router} />
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
If you want hotkeys active only on a specific page:
|
|
242
|
-
|
|
243
|
-
```tsx
|
|
244
|
-
function EditorPage() {
|
|
245
|
-
useCommandHotkeys()
|
|
246
|
-
return <EditorLayout />
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### toHotkeyBindings
|
|
251
|
-
|
|
252
|
-
`toHotkeyBindings()` projects the command registry into the `UseHotkeyDefinition[]` array that
|
|
253
|
-
`useHotkeys` from `@tanstack/react-hotkeys` expects. Commands without a `shortcut` are skipped.
|
|
254
|
-
Commands where `when()` returns false at press time are silently skipped.
|
|
255
|
-
|
|
256
|
-
```ts
|
|
257
|
-
import { toHotkeyBindings } from 'basalt-ui/commands'
|
|
258
|
-
import { useHotkeys } from '@tanstack/react-hotkeys'
|
|
259
|
-
|
|
260
|
-
// Manual binding — equivalent to useCommandHotkeys() but you control the options:
|
|
261
|
-
function MyHotkeys() {
|
|
262
|
-
useHotkeys(toHotkeyBindings(), { preventDefault: true })
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Shortcut string format
|
|
267
|
-
|
|
268
|
-
Shortcut strings use the `Mod+Key` convention: `Mod` resolves to `⌘` on macOS and `Ctrl`
|
|
269
|
-
elsewhere. The `@tanstack/hotkeys` package handles platform resolution at runtime.
|
|
270
|
-
|
|
271
|
-
```ts
|
|
272
|
-
defineCommands({
|
|
273
|
-
'file:save': { label: 'Save', shortcut: 'Mod+S', run: () => save() },
|
|
274
|
-
'file:close': { label: 'Close', shortcut: 'Mod+W', run: () => close() },
|
|
275
|
-
'edit:find': { label: 'Find', shortcut: 'Mod+F', run: () => find() },
|
|
276
|
-
'view:zoom': { label: 'Zoom', shortcut: 'Mod+Shift+Z', run: () => zoom() },
|
|
277
|
-
})
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
## Spotlight live store (basaltSpotlight)
|
|
281
|
-
|
|
282
|
-
`BasaltOverlays` mounts Spotlight against a basalt-owned store (`createSpotlight()` singleton).
|
|
283
|
-
Import `openSpotlight` / `closeSpotlight` from `basalt-ui/commands` instead of Mantine's global
|
|
284
|
-
`spotlight` to target this instance:
|
|
285
|
-
|
|
286
|
-
```ts
|
|
287
|
-
import { openSpotlight, closeSpotlight } from 'basalt-ui/commands'
|
|
288
|
-
|
|
289
|
-
// Open the palette programmatically:
|
|
290
|
-
openSpotlight()
|
|
291
|
-
|
|
292
|
-
// From a command handler (close palette, then run):
|
|
293
|
-
runCommand('palette:open', { close: () => closeSpotlight() })
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
Spotlight actions are synced from the registry via `toSpotlightActions()` at mount time. Shortcuts
|
|
297
|
-
displayed in the Spotlight action description are platform-formatted via `formatShortcutDisplay`
|
|
298
|
-
(`⌘S` on mac, `Ctrl+S` on windows).
|
|
299
|
-
|
|
300
|
-
## defineOverlay (single-overlay helper)
|
|
301
|
-
|
|
302
|
-
Mirror of `defineCommand` — types a single overlay without registering it. Use when splitting
|
|
303
|
-
overlay definitions across files.
|
|
304
|
-
|
|
305
|
-
```ts
|
|
306
|
-
import { defineOverlay } from 'basalt-ui/commands'
|
|
307
|
-
|
|
308
|
-
const confirmDelete = defineOverlay<{ name: string }>({
|
|
309
|
-
title: 'Confirm delete',
|
|
310
|
-
render: ({ name }) => <Text>Delete "{name}"?</Text>,
|
|
311
|
-
})
|
|
312
|
-
// Then include in defineOverlays({ 'confirm:delete': confirmDelete })
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
WARNING: `defineOverlay` only TYPES — it does NOT register. Only `defineOverlays(map)` registers.
|
|
316
|
-
|
|
317
|
-
## Deferred
|
|
318
|
-
|
|
319
|
-
- **`SidebarItem.command?: CommandId`** — cross-ref between the shell sidebar and the command bus
|
|
320
|
-
is a later integration step. Do not add it now.
|