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,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Presentational app sidebar — a collapsible icon-rail with grouped nav sections, a brand header
|
|
3
|
-
* (logo + collapse
|
|
4
|
-
* optional version label).
|
|
3
|
+
* (logo + collapse toggle), and a footer settings menu (consumer-supplied entries + an
|
|
4
|
+
* optional version label). Desktop only — below `sm` the sidebar doesn't render at all; mobile
|
|
5
|
+
* navigation is `MobileNav`'s tab bar / More menu, not this component.
|
|
5
6
|
*
|
|
6
7
|
* Router-agnostic by design: active detection, typed navigation and the collapse store stay in the
|
|
7
8
|
* consumer (or in `BasaltShell`), which feeds resolved `sections` + `collapsed`/`onToggleCollapse`
|
|
@@ -12,7 +13,9 @@
|
|
|
12
13
|
* DESKTOP ONLY. There is no mobile sidebar drawer any more: below the `sm` breakpoint the navbar
|
|
13
14
|
* is permanently collapsed and the bottom bar (`app-mobile-nav.tsx`) is the whole nav. The rail
|
|
14
15
|
* styling is still gated behind a `min-width: sm` media query, and the collapse chevron is
|
|
15
|
-
* `visibleFrom="sm"`.
|
|
16
|
+
* `visibleFrom="sm"`. Originally extracted from argo's
|
|
17
|
+
* `apps/dashboard/src/components/app-shell/app-sidebar.tsx`; argo has since deleted that file in
|
|
18
|
+
* favor of this package.
|
|
16
19
|
*/
|
|
17
20
|
import {
|
|
18
21
|
ActionIcon,
|
|
@@ -29,60 +32,95 @@ import {
|
|
|
29
32
|
UnstyledButton,
|
|
30
33
|
} from '@mantine/core'
|
|
31
34
|
import { useDisclosure } from '@mantine/hooks'
|
|
32
|
-
import { useEffect, useRef
|
|
35
|
+
import { useEffect, useRef } from 'react'
|
|
33
36
|
import type { ReactNode } from 'react'
|
|
34
37
|
import type { BrandConfig, SettingsMenuItem } from './index'
|
|
35
|
-
import type { SidebarItem, SidebarSection } from '../nav/types'
|
|
38
|
+
import type { SidebarBlock, SidebarItem, SidebarSection } from '../nav/types'
|
|
36
39
|
import { NavCountBadge } from './nav-count-badge'
|
|
37
40
|
import { SidebarAccount } from './app-sidebar-account'
|
|
38
|
-
import type { BasaltAccountProps } from './account-types'
|
|
41
|
+
import type { AccountMenuItem, BasaltAccountProps } from './account-types'
|
|
39
42
|
import { SidebarSearch } from './sidebar-search'
|
|
40
|
-
import type { SidebarSearchConfig } from './sidebar-search'
|
|
43
|
+
import type { SidebarSearchActions, SidebarSearchConfig } from './sidebar-search'
|
|
44
|
+
import {
|
|
45
|
+
IconChevron,
|
|
46
|
+
SidebarBlockView,
|
|
47
|
+
SidebarProgressRing,
|
|
48
|
+
usePersistedFold,
|
|
49
|
+
} from './sidebar-blocks'
|
|
50
|
+
import {
|
|
51
|
+
sidebarBlockPlacement,
|
|
52
|
+
sidebarBlockRail,
|
|
53
|
+
sidebarSectionFoldKey,
|
|
54
|
+
} from './sidebar-block-model'
|
|
41
55
|
import { VX } from '../tokens'
|
|
42
56
|
import { useBasaltSpacing } from '../theme'
|
|
43
57
|
import classes from './app-sidebar.module.css'
|
|
44
58
|
|
|
45
59
|
export type AppSidebarProps = {
|
|
46
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Brand identity. Supplying `menu` turns the brand row into a `Name ▾` workspace switcher —
|
|
62
|
+
* the entries are the existing `AccountMenuItem` shape, so a consumer already mapping account
|
|
63
|
+
* actions needs no second vocabulary.
|
|
64
|
+
*/
|
|
65
|
+
brand: BrandConfig & { menu?: AccountMenuItem[] }
|
|
47
66
|
sections: SidebarSection[]
|
|
48
67
|
collapsed: boolean
|
|
49
68
|
onToggleCollapse: () => void
|
|
50
69
|
/**
|
|
51
70
|
* Footer settings-menu entries (theme switcher, devtools, …) — supplied by the consumer.
|
|
52
|
-
* The pinned footer
|
|
53
|
-
*
|
|
54
|
-
*
|
|
71
|
+
* The pinned footer row renders ONLY when this is a non-empty list — apps that put Settings in a
|
|
72
|
+
* nav section (the common case) omit this and get no duplicate footer row.
|
|
73
|
+
*
|
|
74
|
+
* **Three entries or fewer render FLAT**, as one link row each (Settings · Integrations · Invite
|
|
75
|
+
* teammates), and four or more collapse into the single gear "Settings" menu. A menu that opens
|
|
76
|
+
* to show two rows costs a click for nothing; a footer of eight rows costs the nav its height.
|
|
77
|
+
* That threshold is basalt's — `docs/CONTROLS-SPEC.md` §2.3 — and `settingsMenu` is the override
|
|
78
|
+
* for the cases the count cannot see. `brand.version` renders as a faint label under the flat
|
|
79
|
+
* rows, and inside the dropdown in the menu form.
|
|
55
80
|
*/
|
|
56
81
|
settingsMenuItems?: SettingsMenuItem[]
|
|
82
|
+
/**
|
|
83
|
+
* Which footer form `settingsMenuItems` takes. `'auto'` (the default) is the count rule above;
|
|
84
|
+
* `'flat'` and `'menu'` force one regardless of how many entries there are.
|
|
85
|
+
*
|
|
86
|
+
* The override exists because the COUNT is not the whole question. Three rows that are each a
|
|
87
|
+
* theme radio group, a devtools switch and a density slider are three CONTROLS, not three
|
|
88
|
+
* destinations — flat they fill the footer with widgets and push the account row off the fold,
|
|
89
|
+
* and the count rule cannot tell them from three links. Equally, four short destinations may be
|
|
90
|
+
* worth their height on a tall sidebar. `'auto'` stays the default so nothing moves for a
|
|
91
|
+
* consumer that never had an opinion.
|
|
92
|
+
*
|
|
93
|
+
* DESKTOP FOOTER ONLY. Below `sm` there is no sidebar at all and the same entries are flat rows
|
|
94
|
+
* in the bottom bar's More surface either way — a dropdown inside that sheet would be a menu
|
|
95
|
+
* inside a menu.
|
|
96
|
+
*
|
|
97
|
+
* @default 'auto'
|
|
98
|
+
*/
|
|
99
|
+
settingsMenu?: 'auto' | 'flat' | 'menu'
|
|
57
100
|
/**
|
|
58
101
|
* Optional account row rendered below the settings menu in the sidebar footer (see
|
|
59
|
-
* `SidebarAccount` / `BasaltAccountProps`),
|
|
60
|
-
* reproduces today's footer unchanged.
|
|
102
|
+
* `SidebarAccount` / `BasaltAccountProps`) — no separating hairline, the row's own top padding
|
|
103
|
+
* supplies the separation. Omitting it reproduces today's footer unchanged.
|
|
61
104
|
*/
|
|
62
105
|
account?: BasaltAccountProps
|
|
63
106
|
/**
|
|
64
107
|
* Optional search field rendered directly below the brand and ABOVE the nav scroll region — a
|
|
65
|
-
* fixed, non-scrolling row. Pair with basalt-ui/commands' openSpotlight.
|
|
108
|
+
* fixed, non-scrolling row. Pair with basalt-ui/commands' openSpotlight. `actions` adds up to two
|
|
109
|
+
* icon-only buttons to the right of that row.
|
|
66
110
|
*/
|
|
67
|
-
search?: SidebarSearchConfig
|
|
111
|
+
search?: SidebarSearchConfig & { actions?: SidebarSearchActions }
|
|
68
112
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* fighting it for height. Pass `sections={[]}` to use this slot exclusively; the section-spacing
|
|
73
|
-
* rule only fires between adjacent children, so an empty `sections` produces no orphan divider or
|
|
74
|
-
* dead padding above it.
|
|
113
|
+
* Declared blocks (`docs/CONTROLS-SPEC.md` §2.3, law C13) — an "Awaiting action" list, a
|
|
114
|
+
* "Recents" list, a "Getting started" progress row, or a `kind: 'custom'` node for a tree or
|
|
115
|
+
* filter panel no set of rows can express (`kind: 'custom'` is what replaced `navExtra`).
|
|
75
116
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* itself to the rail (it takes `collapsed` and renders its own icon-only form) because the shell
|
|
82
|
-
* owns that control; it cannot adapt content it knows nothing about, so this slot hides instead
|
|
83
|
-
* of squashing it.
|
|
117
|
+
* `placement: 'nav'` appends after `sections` INSIDE the nav scroll region, so a long list
|
|
118
|
+
* scrolls with the nav instead of fighting it for height; `'bottom'` pins above the settings
|
|
119
|
+
* footer. Because these are data and not a `ReactNode` slot, basalt owns both projections a slot
|
|
120
|
+
* could never express: the collapsed rail (a count dot on the icon, a ring on the settings row)
|
|
121
|
+
* and the mobile More sheet — see `sidebarBlockRail` / `sidebarBlockMobile`.
|
|
84
122
|
*/
|
|
85
|
-
|
|
123
|
+
blocks?: SidebarBlock[]
|
|
86
124
|
}
|
|
87
125
|
|
|
88
126
|
/** Inline collapse/expand chevrons — keeps the shell icon-dependency-free. */
|
|
@@ -106,24 +144,6 @@ function IconCollapse({ collapsed }: { collapsed: boolean }) {
|
|
|
106
144
|
)
|
|
107
145
|
}
|
|
108
146
|
|
|
109
|
-
function IconChevron({ open }: { open: boolean }) {
|
|
110
|
-
return (
|
|
111
|
-
<svg
|
|
112
|
-
width={12}
|
|
113
|
-
height={12}
|
|
114
|
-
viewBox="0 0 24 24"
|
|
115
|
-
fill="none"
|
|
116
|
-
stroke="currentColor"
|
|
117
|
-
strokeWidth={2}
|
|
118
|
-
strokeLinecap="round"
|
|
119
|
-
strokeLinejoin="round"
|
|
120
|
-
aria-hidden
|
|
121
|
-
>
|
|
122
|
-
{open ? <path d="M6 9l6 6l6 -6" /> : <path d="M9 6l6 6l-6 6" />}
|
|
123
|
-
</svg>
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
147
|
function IconGear() {
|
|
128
148
|
return (
|
|
129
149
|
<svg
|
|
@@ -146,6 +166,57 @@ function IconGear() {
|
|
|
146
166
|
const HOVER_OPEN_DELAY = 150
|
|
147
167
|
const HOVER_CLOSE_DELAY = 200
|
|
148
168
|
|
|
169
|
+
/** `settingsMenuItems` at or under this count render as flat rows — see the prop's JSDoc. */
|
|
170
|
+
const FLAT_SETTINGS_MAX = 3
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The brand name, and — when `brand.menu` is supplied — the workspace switcher it becomes.
|
|
174
|
+
*
|
|
175
|
+
* The entries are `AccountMenuItem`s: the shell already had exactly this row shape (label, icon,
|
|
176
|
+
* onClick, danger) for the account menu, and a second vocabulary for the same dropdown would be
|
|
177
|
+
* two things to keep in step for no gain. The chevron is inline text, not an icon dependency.
|
|
178
|
+
*/
|
|
179
|
+
function BrandName({
|
|
180
|
+
brand,
|
|
181
|
+
menuWidth,
|
|
182
|
+
}: {
|
|
183
|
+
brand: BrandConfig & { menu?: AccountMenuItem[] }
|
|
184
|
+
menuWidth: number
|
|
185
|
+
}) {
|
|
186
|
+
const label = (
|
|
187
|
+
<Text className={classes.brandName} fz={VX.text.xl} fw={550}>
|
|
188
|
+
{brand.name}
|
|
189
|
+
</Text>
|
|
190
|
+
)
|
|
191
|
+
const menu = brand.menu
|
|
192
|
+
if (menu === undefined || menu.length === 0) return label
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Menu position="bottom-start" withArrow width={menuWidth} zIndex={500}>
|
|
196
|
+
<Menu.Target>
|
|
197
|
+
<UnstyledButton className={classes.brandButton} aria-label={`${brand.name} workspace`}>
|
|
198
|
+
{label}
|
|
199
|
+
{/* `open` is the DOWN glyph — a switcher's affordance points at its dropdown, and this is
|
|
200
|
+
the same chevron every fold in the sidebar uses rather than a second one. */}
|
|
201
|
+
<IconChevron open />
|
|
202
|
+
</UnstyledButton>
|
|
203
|
+
</Menu.Target>
|
|
204
|
+
<Menu.Dropdown>
|
|
205
|
+
{menu.map((entry) => (
|
|
206
|
+
<Menu.Item
|
|
207
|
+
key={entry.key}
|
|
208
|
+
leftSection={entry.icon}
|
|
209
|
+
{...(entry.danger === true && { color: 'red' })}
|
|
210
|
+
onClick={entry.onClick}
|
|
211
|
+
>
|
|
212
|
+
{entry.label}
|
|
213
|
+
</Menu.Item>
|
|
214
|
+
))}
|
|
215
|
+
</Menu.Dropdown>
|
|
216
|
+
</Menu>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
149
220
|
/** True when the item or any descendant is active — drives inline child expansion. */
|
|
150
221
|
function hasActiveDescendant(item: SidebarItem): boolean {
|
|
151
222
|
if (item.active) return true
|
|
@@ -168,6 +239,15 @@ function NavLinkBody({ item, active }: { item: SidebarItem; active: boolean }) {
|
|
|
168
239
|
leftSection: item.icon,
|
|
169
240
|
rightSection: item.badge ?? (item.count ? <NavCountBadge count={item.count} /> : undefined),
|
|
170
241
|
active,
|
|
242
|
+
// Basalt is the one that stamps `aria-current`, not the theme and not a router `<Link>`'s own
|
|
243
|
+
// default (which a consumer's `SidebarItem.Anchor` may compute independently) — mirrors
|
|
244
|
+
// `app-mobile-nav.tsx`'s identical pattern so both surfaces agree on what "active" means.
|
|
245
|
+
'aria-current': active ? ('page' as const) : undefined,
|
|
246
|
+
// `item.ancestor` (a parent on the winner's path — `useNav`'s exclusivity pass) is NEVER
|
|
247
|
+
// active and never carries `aria-current`; it only earns a `data-ancestor` hook for CSS. A
|
|
248
|
+
// leaf item never has `ancestor` set, so this is a no-op there — shared here rather than
|
|
249
|
+
// duplicated per render path.
|
|
250
|
+
...(item.ancestor === true && { 'data-ancestor': true }),
|
|
171
251
|
...(item.onClick !== undefined && { onClick: item.onClick }),
|
|
172
252
|
}
|
|
173
253
|
if (Anchor) {
|
|
@@ -179,18 +259,63 @@ function NavLinkBody({ item, active }: { item: SidebarItem; active: boolean }) {
|
|
|
179
259
|
/**
|
|
180
260
|
* Group label — a micro-label (docs/DESIGN-SPEC.md §3: mono, uppercase, tracked, faint). Typography
|
|
181
261
|
* lives entirely in `.sectionLabel` (app-sidebar.module.css), not Mantine `Text` props, since the
|
|
182
|
-
* treatment is a shell-specific micro-label rather than a themed primitive.
|
|
183
|
-
*
|
|
184
|
-
* the button's hover box double-
|
|
262
|
+
* treatment is a shell-specific micro-label rather than a themed primitive. Flush (no intrinsic
|
|
263
|
+
* inset/margin) at every call site, so the collapsible `sectionHeader` owns the padding instead
|
|
264
|
+
* (otherwise the button's hover box would double-inset and hug the text).
|
|
185
265
|
*/
|
|
186
|
-
function SectionLabel({ children
|
|
266
|
+
function SectionLabel({ children }: { children: ReactNode }) {
|
|
187
267
|
return (
|
|
188
|
-
<Text component="div" px={
|
|
268
|
+
<Text component="div" px={0} mb={0} className={classes.sectionLabel}>
|
|
189
269
|
{children}
|
|
190
270
|
</Text>
|
|
191
271
|
)
|
|
192
272
|
}
|
|
193
273
|
|
|
274
|
+
/**
|
|
275
|
+
* One nav section, owning its own PERSISTED fold.
|
|
276
|
+
*
|
|
277
|
+
* A component per section rather than one state object in `AppSidebar`, because the fold moved from
|
|
278
|
+
* a `useState` keyed by label to `createPersistedState` at `basalt:sidebar-section:<label-slug>` —
|
|
279
|
+
* one hook per section, which can only have a stable hook count inside a component of its own.
|
|
280
|
+
* `defaultCollapsed` is the SEED for that key, so it stops overriding what the user last chose.
|
|
281
|
+
*/
|
|
282
|
+
function NavSection({ section, collapsed }: { section: SidebarSection; collapsed: boolean }) {
|
|
283
|
+
const [folded, setFolded] = usePersistedFold(
|
|
284
|
+
sidebarSectionFoldKey(section.label),
|
|
285
|
+
Boolean(section.defaultCollapsed),
|
|
286
|
+
)
|
|
287
|
+
const items = section.items.map((item) => (
|
|
288
|
+
<NavItemRow key={item.key} item={item} collapsed={collapsed} />
|
|
289
|
+
))
|
|
290
|
+
|
|
291
|
+
if (!section.collapsible) {
|
|
292
|
+
return (
|
|
293
|
+
<div>
|
|
294
|
+
<div className={classes.sectionBand}>
|
|
295
|
+
<SectionLabel>{section.label}</SectionLabel>
|
|
296
|
+
</div>
|
|
297
|
+
<Stack gap={1}>{items}</Stack>
|
|
298
|
+
</div>
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div>
|
|
304
|
+
<UnstyledButton
|
|
305
|
+
className={`${classes.sectionBand} ${classes.sectionHeader}`}
|
|
306
|
+
onClick={() => setFolded(!folded)}
|
|
307
|
+
aria-expanded={!folded}
|
|
308
|
+
>
|
|
309
|
+
<SectionLabel>{section.label}</SectionLabel>
|
|
310
|
+
<IconChevron open={!folded} />
|
|
311
|
+
</UnstyledButton>
|
|
312
|
+
<Collapse expanded={!folded}>
|
|
313
|
+
<Stack gap={1}>{items}</Stack>
|
|
314
|
+
</Collapse>
|
|
315
|
+
</div>
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
194
319
|
function NavItemRow({ item, collapsed }: { item: SidebarItem; collapsed: boolean }) {
|
|
195
320
|
const [opened, { open, close }] = useDisclosure(false)
|
|
196
321
|
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
@@ -350,58 +475,99 @@ export function AppSidebar({
|
|
|
350
475
|
collapsed,
|
|
351
476
|
onToggleCollapse,
|
|
352
477
|
settingsMenuItems,
|
|
478
|
+
settingsMenu = 'auto',
|
|
353
479
|
account,
|
|
354
480
|
search,
|
|
355
|
-
|
|
481
|
+
blocks,
|
|
356
482
|
}: AppSidebarProps) {
|
|
357
483
|
// Density-tracking Menu dropdown width (`SPACE_STEP.sidebarSettingsMenuWidth`) — read the ACTIVE
|
|
358
484
|
// resolved level, not the frozen level-0 constant (see that constant's own doc in
|
|
359
485
|
// `tokens/palette.ts`).
|
|
360
486
|
const { step } = useBasaltSpacing()
|
|
361
487
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
488
|
+
const navBlocks = (blocks ?? []).filter((block) => sidebarBlockPlacement(block) === 'nav')
|
|
489
|
+
const bottomBlocks = (blocks ?? []).filter((block) => sidebarBlockPlacement(block) === 'bottom')
|
|
490
|
+
// The rail's stand-in for a progress block: the first one asking for a ring gets one, on the
|
|
491
|
+
// settings row. JS-gated on `collapsed` rather than CSS (unlike every other rail rule) because
|
|
492
|
+
// the mark moves to a DIFFERENT node — nothing in the block's own subtree can express it. With no
|
|
493
|
+
// settings row there is nowhere to put it, and the block simply has no rail form.
|
|
494
|
+
const railRing = bottomBlocks.find(
|
|
495
|
+
(block) => block.kind === 'progress' && sidebarBlockRail(block) === 'ring',
|
|
366
496
|
)
|
|
367
497
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
498
|
+
// The pinned footer row is OPT-IN: it renders only when the consumer supplies settings-menu
|
|
499
|
+
// entries. Most apps put Settings in a nav section instead — rendering the row unconditionally
|
|
500
|
+
// produced a duplicate "Settings" the consumer couldn't remove. On mobile the same entries are
|
|
501
|
+
// reachable as flat rows in the bottom bar's More surface.
|
|
502
|
+
const settingsItems = settingsMenuItems ?? []
|
|
503
|
+
const versionLabel = brand.version !== undefined ? `${brand.name} v${brand.version}` : undefined
|
|
504
|
+
const ringMark =
|
|
505
|
+
collapsed && railRing !== undefined && railRing.kind === 'progress' ? (
|
|
506
|
+
<SidebarProgressRing value={railRing.value} total={railRing.total} />
|
|
507
|
+
) : null
|
|
508
|
+
|
|
509
|
+
// Three or fewer render as flat link rows; four or more keep the gear menu — unless the consumer
|
|
510
|
+
// forced one form, which is the only thing `settingsMenu` does. See both props' docs.
|
|
511
|
+
const flatSettings =
|
|
512
|
+
settingsMenu === 'flat' ||
|
|
513
|
+
(settingsMenu === 'auto' && settingsItems.length <= FLAT_SETTINGS_MAX)
|
|
514
|
+
const settingsRow =
|
|
515
|
+
settingsItems.length === 0 ? null : flatSettings ? (
|
|
516
|
+
<div className={classes.footerLinks}>
|
|
517
|
+
{settingsItems.map((entry, index) => (
|
|
518
|
+
<UnstyledButton
|
|
519
|
+
key={entry.key}
|
|
520
|
+
className={classes.footerBtn}
|
|
521
|
+
onClick={entry.onClick}
|
|
522
|
+
aria-label={entry.label}
|
|
523
|
+
>
|
|
524
|
+
{/* A FIXED slot, so rows align on one icon column whether or not each ships an icon —
|
|
525
|
+
and the gear fallback is functional, not decorative: in the collapsed rail the label
|
|
526
|
+
is hidden and the glyph IS the row, so an icon-less row would be an empty hover
|
|
527
|
+
target. `SettingsMenuItem.icon` stays optional; supply one per row in the flat form
|
|
528
|
+
and the gear never appears. */}
|
|
529
|
+
<span className={classes.footerIconSlot}>{entry.icon ?? <IconGear />}</span>
|
|
382
530
|
<Text className={classes.footerText} fz={VX.text.md}>
|
|
383
|
-
|
|
531
|
+
{entry.label}
|
|
384
532
|
</Text>
|
|
533
|
+
{/* The rail ring rides the FIRST row only — one progress block is one mark. */}
|
|
534
|
+
{index === 0 ? ringMark : null}
|
|
385
535
|
</UnstyledButton>
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
536
|
+
))}
|
|
537
|
+
{versionLabel !== undefined && (
|
|
538
|
+
<Text component="div" className={classes.footerVersion}>
|
|
539
|
+
{versionLabel}
|
|
540
|
+
</Text>
|
|
541
|
+
)}
|
|
542
|
+
</div>
|
|
543
|
+
) : (
|
|
544
|
+
<Group gap="xs" wrap="nowrap">
|
|
545
|
+
<Menu position="right-start" withArrow width={step.sidebarSettingsMenuWidth} zIndex={500}>
|
|
546
|
+
<Menu.Target>
|
|
547
|
+
<UnstyledButton className={classes.footerBtn} aria-label="Settings">
|
|
548
|
+
<IconGear />
|
|
549
|
+
<Text className={classes.footerText} fz={VX.text.md}>
|
|
550
|
+
Settings
|
|
551
|
+
</Text>
|
|
552
|
+
{ringMark}
|
|
553
|
+
</UnstyledButton>
|
|
554
|
+
</Menu.Target>
|
|
555
|
+
<Menu.Dropdown>
|
|
556
|
+
{settingsItems.map((entry) => (
|
|
557
|
+
<Menu.Item key={entry.key} leftSection={entry.icon} onClick={entry.onClick}>
|
|
558
|
+
{entry.label}
|
|
559
|
+
</Menu.Item>
|
|
560
|
+
))}
|
|
561
|
+
{versionLabel !== undefined && (
|
|
562
|
+
<>
|
|
563
|
+
<Menu.Divider />
|
|
564
|
+
<Menu.Label>{versionLabel}</Menu.Label>
|
|
565
|
+
</>
|
|
566
|
+
)}
|
|
567
|
+
</Menu.Dropdown>
|
|
568
|
+
</Menu>
|
|
569
|
+
</Group>
|
|
570
|
+
)
|
|
405
571
|
|
|
406
572
|
return (
|
|
407
573
|
<Stack gap={0} h="100%" className={classes.root} data-collapsed={collapsed || undefined}>
|
|
@@ -416,9 +582,7 @@ export function AppSidebar({
|
|
|
416
582
|
style={{ display: 'block' }}
|
|
417
583
|
/>
|
|
418
584
|
)}
|
|
419
|
-
<
|
|
420
|
-
{brand.name}
|
|
421
|
-
</Text>
|
|
585
|
+
<BrandName brand={brand} menuWidth={step.sidebarAccountMenuWidth} />
|
|
422
586
|
</Group>
|
|
423
587
|
<ActionIcon
|
|
424
588
|
variant="subtle"
|
|
@@ -450,57 +614,29 @@ export function AppSidebar({
|
|
|
450
614
|
classNames={{ viewport: classes.navViewport }}
|
|
451
615
|
>
|
|
452
616
|
<Stack gap={0} className={classes.nav}>
|
|
453
|
-
{sections.map((section) =>
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
</div>
|
|
460
|
-
<Stack gap={1}>{renderSectionItems(section)}</Stack>
|
|
461
|
-
</div>
|
|
462
|
-
)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const isOpen = !collapsedSections[section.label]
|
|
466
|
-
return (
|
|
467
|
-
<div key={section.label}>
|
|
468
|
-
<UnstyledButton
|
|
469
|
-
className={`${classes.sectionBand} ${classes.sectionHeader}`}
|
|
470
|
-
onClick={() =>
|
|
471
|
-
setCollapsedSections((prev) => ({
|
|
472
|
-
...prev,
|
|
473
|
-
[section.label]: !prev[section.label],
|
|
474
|
-
}))
|
|
475
|
-
}
|
|
476
|
-
aria-expanded={isOpen}
|
|
477
|
-
>
|
|
478
|
-
<SectionLabel flush>{section.label}</SectionLabel>
|
|
479
|
-
<IconChevron open={isOpen} />
|
|
480
|
-
</UnstyledButton>
|
|
481
|
-
<Collapse expanded={isOpen}>
|
|
482
|
-
<Stack gap={1}>{renderSectionItems(section)}</Stack>
|
|
483
|
-
</Collapse>
|
|
484
|
-
</div>
|
|
485
|
-
)
|
|
486
|
-
})}
|
|
487
|
-
{navExtra !== undefined && navExtra !== null && (
|
|
488
|
-
<div className={classes.navExtra}>{navExtra}</div>
|
|
489
|
-
)}
|
|
617
|
+
{sections.map((section) => (
|
|
618
|
+
<NavSection key={section.label} section={section} collapsed={collapsed} />
|
|
619
|
+
))}
|
|
620
|
+
{navBlocks.map((block) => (
|
|
621
|
+
<SidebarBlockView key={block.key} block={block} />
|
|
622
|
+
))}
|
|
490
623
|
</Stack>
|
|
491
624
|
</ScrollArea>
|
|
492
625
|
|
|
493
|
-
{account
|
|
626
|
+
{(bottomBlocks.length > 0 || settingsRow !== null || account !== undefined) && (
|
|
494
627
|
<Stack gap={0} className={classes.footer}>
|
|
628
|
+
{bottomBlocks.map((block) => (
|
|
629
|
+
<SidebarBlockView key={block.key} block={block} />
|
|
630
|
+
))}
|
|
495
631
|
{settingsRow}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
632
|
+
{account !== undefined && (
|
|
633
|
+
<SidebarAccount
|
|
634
|
+
state={account.state}
|
|
635
|
+
{...(account.actions !== undefined && { actions: account.actions })}
|
|
636
|
+
{...(account.showEmail !== undefined && { showEmail: account.showEmail })}
|
|
637
|
+
/>
|
|
638
|
+
)}
|
|
501
639
|
</Stack>
|
|
502
|
-
) : (
|
|
503
|
-
settingsRow
|
|
504
640
|
)}
|
|
505
641
|
</Stack>
|
|
506
642
|
)
|