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
package/dist/shell/index.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { AppShell } from "@mantine/core";
|
|
3
|
-
import { useMemo } from "react";
|
|
2
|
+
import { AppShell, Box } from "@mantine/core";
|
|
3
|
+
import { Fragment, useMemo } from "react";
|
|
4
4
|
import { AppSidebar } from "./app-sidebar.js";
|
|
5
5
|
import { MobileNav, accountRowCount } from "./app-mobile-nav.js";
|
|
6
|
-
import { projectMobileNav } from "./mobile-nav-model.js";
|
|
6
|
+
import { blockRowCount, projectMobileNav } from "./mobile-nav-model.js";
|
|
7
7
|
import { AppBreadcrumbs } from "./app-breadcrumbs.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { PageBarOutlet, PageBarProvider, usePageKebabClaimed } from "./page-bar.js";
|
|
9
|
+
import { OverflowMenu, globalActionAsBarAction, globalActionMobile } from "../controls/actions.js";
|
|
10
|
+
import { CtlSlot, useBasaltSpacing } from "../theme/index.js";
|
|
10
11
|
import { createPersistedState } from "../state.js";
|
|
11
12
|
import headerClasses from "./app-header.module.css";
|
|
12
13
|
import mobileNavClasses from "./app-mobile-nav.module.css";
|
|
13
14
|
import { AppSidebar as AppSidebar2 } from "./app-sidebar.js";
|
|
14
15
|
import { NavCountBadge } from "./nav-count-badge.js";
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
SidebarSearch
|
|
18
|
+
} from "./sidebar-search.js";
|
|
16
19
|
import { SidebarAccount } from "./app-sidebar-account.js";
|
|
17
20
|
import { MobileNav as MobileNav2 } from "./app-mobile-nav.js";
|
|
18
21
|
import {
|
|
@@ -22,7 +25,7 @@ import {
|
|
|
22
25
|
MOBILE_MORE_KEY
|
|
23
26
|
} from "./mobile-nav-model.js";
|
|
24
27
|
import { AppBreadcrumbs as AppBreadcrumbs2 } from "./app-breadcrumbs.js";
|
|
25
|
-
import {
|
|
28
|
+
import { PageBar } from "./page-bar.js";
|
|
26
29
|
function findActiveCrumb(sections) {
|
|
27
30
|
for (const section of sections) {
|
|
28
31
|
const found = findActiveWithParent(section.items);
|
|
@@ -74,8 +77,9 @@ function BasaltShell({
|
|
|
74
77
|
sections,
|
|
75
78
|
mobileNav,
|
|
76
79
|
globalActions,
|
|
77
|
-
|
|
80
|
+
sidebarBlocks,
|
|
78
81
|
settingsMenuItems,
|
|
82
|
+
settingsMenu,
|
|
79
83
|
storageKey = "basalt-sidebar-collapsed",
|
|
80
84
|
collapsed: collapsedProp,
|
|
81
85
|
onCollapsedChange,
|
|
@@ -93,18 +97,19 @@ function BasaltShell({
|
|
|
93
97
|
onCollapsedChange?.(next);
|
|
94
98
|
};
|
|
95
99
|
const activeCrumb = findActiveCrumb(sections);
|
|
96
|
-
const extraMoreRows = accountRowCount(account) + (settingsMenuItems?.length ?? 0) + (
|
|
100
|
+
const extraMoreRows = accountRowCount(account) + (settingsMenuItems?.length ?? 0) + blockRowCount(sidebarBlocks);
|
|
97
101
|
const model = useMemo(
|
|
98
102
|
() => projectMobileNav(sections, { config: mobileNav, extraMoreRows }),
|
|
99
103
|
[sections, mobileNav, extraMoreRows]
|
|
100
104
|
);
|
|
101
|
-
return /* @__PURE__ */ jsx(
|
|
105
|
+
return /* @__PURE__ */ jsx(PageBarProvider, { globalActions: globalActions ?? [], children: /* @__PURE__ */ jsxs(
|
|
102
106
|
AppShell,
|
|
103
107
|
{
|
|
104
108
|
h: "100dvh",
|
|
105
109
|
layout: "alt",
|
|
106
110
|
header: {
|
|
107
|
-
height
|
|
111
|
+
// ONE height at every width (law C14): nothing reserves a second mobile row any more.
|
|
112
|
+
height: step.appShellHeaderHeight
|
|
108
113
|
},
|
|
109
114
|
navbar: {
|
|
110
115
|
width: {
|
|
@@ -120,8 +125,8 @@ function BasaltShell({
|
|
|
120
125
|
children: [
|
|
121
126
|
/* @__PURE__ */ jsx(AppShell.Header, { px: "md", withBorder: false, children: /* @__PURE__ */ jsxs("div", { className: headerClasses.bar, children: [
|
|
122
127
|
/* @__PURE__ */ jsx("div", { className: headerClasses.lead, children: /* @__PURE__ */ jsx(AppBreadcrumbs, { ...activeCrumb }) }),
|
|
123
|
-
/* @__PURE__ */ jsx(
|
|
124
|
-
|
|
128
|
+
/* @__PURE__ */ jsx(PageBarOutlet, { className: headerClasses.pageBar }),
|
|
129
|
+
/* @__PURE__ */ jsx(HeaderGlobalActions, { actions: globalActions ?? [] })
|
|
125
130
|
] }) }),
|
|
126
131
|
/* @__PURE__ */ jsx(AppShell.Navbar, { p: 0, withBorder: false, children: /* @__PURE__ */ jsx(
|
|
127
132
|
AppSidebar,
|
|
@@ -130,8 +135,9 @@ function BasaltShell({
|
|
|
130
135
|
sections,
|
|
131
136
|
collapsed,
|
|
132
137
|
onToggleCollapse: toggleCollapse,
|
|
133
|
-
|
|
138
|
+
...sidebarBlocks !== void 0 && { blocks: sidebarBlocks },
|
|
134
139
|
...settingsMenuItems !== void 0 && { settingsMenuItems },
|
|
140
|
+
...settingsMenu !== void 0 && { settingsMenu },
|
|
135
141
|
...account !== void 0 && { account },
|
|
136
142
|
...search !== void 0 && { search }
|
|
137
143
|
}
|
|
@@ -143,13 +149,31 @@ function BasaltShell({
|
|
|
143
149
|
model,
|
|
144
150
|
config: mobileNav,
|
|
145
151
|
...account !== void 0 && { account },
|
|
146
|
-
...settingsMenuItems !== void 0 && { settingsMenuItems }
|
|
152
|
+
...settingsMenuItems !== void 0 && { settingsMenuItems },
|
|
153
|
+
...sidebarBlocks !== void 0 && { blocks: sidebarBlocks }
|
|
147
154
|
}
|
|
148
155
|
) })
|
|
149
156
|
]
|
|
150
157
|
}
|
|
151
158
|
) });
|
|
152
159
|
}
|
|
160
|
+
function HeaderGlobalActions({ actions }) {
|
|
161
|
+
const kebabClaimed = usePageKebabClaimed();
|
|
162
|
+
if (actions.length === 0) return null;
|
|
163
|
+
const more = actions.filter((action, index) => globalActionMobile(action, index) === "more").map(globalActionAsBarAction);
|
|
164
|
+
return (
|
|
165
|
+
// `CtlSlot` here for the same reason a home wraps its own slot (law C5): `globalActions` are
|
|
166
|
+
// consumer NODES, so basalt cannot pass them a size — it can only provide the tier they resolve
|
|
167
|
+
// against. Without it a `<NotificationBell/>`'s internal `ActionIcon` fell back to Mantine's
|
|
168
|
+
// `md` (28px) beside a 30px page group.
|
|
169
|
+
/* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsxs("div", { className: headerClasses.global, children: [
|
|
170
|
+
actions.map(
|
|
171
|
+
(action, index) => globalActionMobile(action, index) === "bar" ? /* @__PURE__ */ jsx(Fragment, { children: action.node }, action.key) : /* @__PURE__ */ jsx(Box, { visibleFrom: "sm", children: action.node }, action.key)
|
|
172
|
+
),
|
|
173
|
+
!kebabClaimed && more.length > 0 && /* @__PURE__ */ jsx(Box, { hiddenFrom: "sm", children: /* @__PURE__ */ jsx(OverflowMenu, { actions: more, trigger: "kebab", label: "More actions" }) })
|
|
174
|
+
] }) })
|
|
175
|
+
);
|
|
176
|
+
}
|
|
153
177
|
export {
|
|
154
178
|
AppBreadcrumbs2 as AppBreadcrumbs,
|
|
155
179
|
AppSidebar2 as AppSidebar,
|
|
@@ -159,9 +183,7 @@ export {
|
|
|
159
183
|
MOBILE_MORE_KEY,
|
|
160
184
|
MobileNav2 as MobileNav,
|
|
161
185
|
NavCountBadge,
|
|
162
|
-
|
|
163
|
-
PageActionsOutlet2 as PageActionsOutlet,
|
|
164
|
-
PageHeaderProvider2 as PageHeaderProvider,
|
|
186
|
+
PageBar,
|
|
165
187
|
SidebarAccount,
|
|
166
188
|
SidebarSearch,
|
|
167
189
|
projectMobileNav2 as projectMobileNav
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* ever rendering below the fold (6 x 44px + 8px padding = 272px against 415px of headroom on the
|
|
14
14
|
* smallest supported viewport), so past that the surface becomes a bottom sheet.
|
|
15
15
|
*/
|
|
16
|
-
import type { MobileNavConfig, MobileNavModel, SidebarSection } from '../nav/types.js';
|
|
16
|
+
import type { MobileNavConfig, MobileNavModel, SidebarBlock, SidebarSection } from '../nav/types.js';
|
|
17
17
|
/** Hard cap on bar slots, INCLUDING More. Five 72px slots is the widest a 360px viewport holds. */
|
|
18
18
|
export declare const MOBILE_MAX_TABS_DEFAULT = 5;
|
|
19
19
|
/**
|
|
@@ -27,12 +27,23 @@ export declare const MOBILE_MORE_KEY = "__more";
|
|
|
27
27
|
export type ProjectMobileNavOptions = {
|
|
28
28
|
config?: MobileNavConfig | undefined;
|
|
29
29
|
/**
|
|
30
|
-
* Extra rows the More surface will hold — account + settings +
|
|
30
|
+
* Extra rows the More surface will hold — account + settings + sidebar blocks. Drives both
|
|
31
31
|
* `needsMore` and the menu-vs-sheet threshold. The model cannot see those rows (they are
|
|
32
32
|
* Mantine-rendered chrome, not `SidebarItem`s), so the shell counts them and passes the total.
|
|
33
33
|
*/
|
|
34
34
|
extraMoreRows?: number;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* How many More-surface rows `blocks` contribute — the `accountRowCount` sibling for sidebar
|
|
38
|
+
* blocks, and for the same reason: `BasaltShell` needs the number BEFORE the projection runs (it
|
|
39
|
+
* feeds `extraMoreRows`, which picks `menu` vs `sheet` against `menuMax`) while the renderer is
|
|
40
|
+
* what decides how many rows a block actually becomes.
|
|
41
|
+
*
|
|
42
|
+
* ONE row per block, never one per item: a list block projects to a single `Awaiting action · 3`
|
|
43
|
+
* row that opens a nested sheet of its items. Counting the items instead would push a 12-item
|
|
44
|
+
* block's More surface to a sheet on the strength of rows that surface never paints.
|
|
45
|
+
*/
|
|
46
|
+
export declare function blockRowCount(blocks: readonly SidebarBlock[] | undefined): number;
|
|
36
47
|
/**
|
|
37
48
|
* Projects `sections` onto the bar. Implements §2.3's thirteen rules in order, and is the ONLY
|
|
38
49
|
* place slot selection happens — `MobileNav` receives a finished model and paints it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-nav-model.d.ts","sourceRoot":"","sources":["../../src/shell/mobile-nav-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,
|
|
1
|
+
{"version":3,"file":"mobile-nav-model.d.ts","sourceRoot":"","sources":["../../src/shell/mobile-nav-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGd,YAAY,EAEZ,cAAc,EACf,MAAM,cAAc,CAAA;AAGrB,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,iGAAiG;AACjG,eAAO,MAAM,eAAe,WAAW,CAAA;AAEvC,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,GAAG,MAAM,CAQjF;AAgSD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EAAE,EAC1B,IAAI,CAAC,EAAE,uBAAuB,GAC7B,cAAc,CA2EhB"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { sidebarBlockMobile } from "./sidebar-block-model.js";
|
|
1
2
|
const MOBILE_MAX_TABS_DEFAULT = 5;
|
|
2
3
|
const MOBILE_MENU_MAX_DEFAULT = 6;
|
|
3
4
|
const MOBILE_MORE_KEY = "__more";
|
|
5
|
+
function blockRowCount(blocks) {
|
|
6
|
+
if (blocks === void 0) return 0;
|
|
7
|
+
return blocks.filter((block) => {
|
|
8
|
+
if (sidebarBlockMobile(block) !== "more") return false;
|
|
9
|
+
return block.kind !== "list" || block.items.length > 0;
|
|
10
|
+
}).length;
|
|
11
|
+
}
|
|
4
12
|
const DEV = process.env["NODE_ENV"] !== "production";
|
|
5
13
|
function placementOf(item, sectionHidden) {
|
|
6
14
|
if (sectionHidden) return "hidden";
|
|
@@ -17,6 +25,9 @@ function hasActiveDestination(item) {
|
|
|
17
25
|
if (item.active) return true;
|
|
18
26
|
return item.children?.some(hasActiveDestination) ?? false;
|
|
19
27
|
}
|
|
28
|
+
function isActiveDestination(item) {
|
|
29
|
+
return item.active === true;
|
|
30
|
+
}
|
|
20
31
|
function countRows(items) {
|
|
21
32
|
return items.reduce((total, item) => total + 1 + countRows(item.children ?? []), 0);
|
|
22
33
|
}
|
|
@@ -51,13 +62,15 @@ function itemCandidate(item) {
|
|
|
51
62
|
return {
|
|
52
63
|
key: item.key,
|
|
53
64
|
covered: /* @__PURE__ */ new Set([item.key]),
|
|
65
|
+
// `isActiveDestination`, not the rollup: this slot COVERS one key, so it reads one key
|
|
66
|
+
// (`hasActiveDestination`'s doc has the whole accounting).
|
|
54
67
|
toSlot: () => ({
|
|
55
68
|
kind: "link",
|
|
56
69
|
key: item.key,
|
|
57
70
|
label: item.label,
|
|
58
71
|
short: item.short ?? item.label,
|
|
59
72
|
icon: item.icon,
|
|
60
|
-
active:
|
|
73
|
+
active: isActiveDestination(item),
|
|
61
74
|
item
|
|
62
75
|
})
|
|
63
76
|
};
|
|
@@ -240,5 +253,6 @@ export {
|
|
|
240
253
|
MOBILE_MAX_TABS_DEFAULT,
|
|
241
254
|
MOBILE_MENU_MAX_DEFAULT,
|
|
242
255
|
MOBILE_MORE_KEY,
|
|
256
|
+
blockRowCount,
|
|
243
257
|
projectMobileNav
|
|
244
258
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sidebar nav count badge (docs/DESIGN-SPEC.md §5): mono
|
|
2
|
+
* Sidebar nav count badge (docs/DESIGN-SPEC.md §5): mono micro (11px), ink-8% bg, radius 5, height 16,
|
|
3
3
|
* padding 0 5px, muted text; `marginLeft: auto` pins it to the row end on any render path.
|
|
4
4
|
* `styles` (inline) rather than a token color prop, since none of Mantine's variant/color
|
|
5
5
|
* combinations land on the ink-tint idiom. Returns `null` for a zero/empty count so the badge slot
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ActionGroupProps, BarAction, GlobalAction } from '../controls/actions.js';
|
|
3
|
+
import type { SyncButtonProps } from '../controls/sync-button.js';
|
|
4
|
+
/** The custom property row 2's measured height is published on. */
|
|
5
|
+
export declare const PAGE_BAR_HEIGHT_VAR = "--basalt-page-bar-h";
|
|
6
|
+
/**
|
|
7
|
+
* Internal — mounted by `BasaltShell`. Owns the portal target, and the single-kebab claim that
|
|
8
|
+
* lets the shell fall back to its OWN kebab on a route that renders no `PageBar`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function PageBarProvider({ globalActions, children, }: {
|
|
11
|
+
globalActions: readonly GlobalAction[];
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}): ReactNode;
|
|
14
|
+
/** Internal — the app-shell header node the active page's row 1 portals into. */
|
|
15
|
+
export declare function PageBarOutlet({ className }: {
|
|
16
|
+
className?: string;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
/** Internal — `true` while a page's `ActionGroup` owns the header's one mobile kebab. */
|
|
19
|
+
export declare function usePageKebabClaimed(): boolean;
|
|
20
|
+
export type PageBarProps = {
|
|
21
|
+
/**
|
|
22
|
+
* Read only when there is no `BasaltShell` outlet (a shell-less app such as linewatch). Inside a
|
|
23
|
+
* shell the breadcrumb names the page and this is ignored.
|
|
24
|
+
*/
|
|
25
|
+
title?: string;
|
|
26
|
+
icon?: ReactNode;
|
|
27
|
+
actions?: ActionGroupProps;
|
|
28
|
+
/** `scope` is fixed to `'page'` — a global sync belongs in the shell's `globalActions` (law C12). */
|
|
29
|
+
sync?: Omit<SyncButtonProps, 'scope'>;
|
|
30
|
+
/** `FilterSet` descendants only (law C1). */
|
|
31
|
+
filters?: ReactNode;
|
|
32
|
+
/** Right-aligned row-2 actions ("Manage metrics"). */
|
|
33
|
+
filtersEnd?: BarAction[];
|
|
34
|
+
/** One `ViewTabs`. */
|
|
35
|
+
tabs?: ReactNode;
|
|
36
|
+
/**
|
|
37
|
+
* Added to the bar's ROOT — the shell-less `<div data-basalt-page-bar="standalone">`, or row 2's
|
|
38
|
+
* sticky wrapper (`data-basalt-page-bar="shell"`) inside a `BasaltShell`. The seam for the two
|
|
39
|
+
* things only the consumer's own layout knows: bleeding the sticky bar across its container's
|
|
40
|
+
* gutters (`margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter)`) and drawing
|
|
41
|
+
* a hairline under it (`border-bottom: 1px solid var(--vx-border)`).
|
|
42
|
+
*
|
|
43
|
+
* Scope that CSS through this class, not through a global `[data-basalt-page-bar]` selector: the
|
|
44
|
+
* data attributes are stable enough to READ (a shell-less consumer can style
|
|
45
|
+
* `[data-basalt-page-bar="standalone"]` in a pinch, and this doc is the promise that the value
|
|
46
|
+
* stays), but a global rule reaches every page in the app including the ones that want neither.
|
|
47
|
+
*/
|
|
48
|
+
className?: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function PageBar({ title, icon, actions, sync, filters, filtersEnd, tabs, className, }: PageBarProps): ReactNode;
|
|
51
|
+
//# sourceMappingURL=page-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-bar.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,OAAO,CAAA;AAKjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AA0BxD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,SAAS,YAAY,EAAE,CAAA;IACtC,QAAQ,EAAE,SAAS,CAAA;CACpB,GAAG,SAAS,CA2BZ;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGlE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,MAAM,MAAM,YAAY,GAAG;IACzB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,qGAAqG;IACrG,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IACrC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AA2DD,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,OAAO,EACP,UAAU,EACV,IAAI,EACJ,SAAS,GACV,EAAE,YAAY,GAAG,SAAS,CAmG1B"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useLayoutEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
11
|
+
import { createPortal } from "react-dom";
|
|
12
|
+
import { CtlSlot } from "../theme/index.js";
|
|
13
|
+
import { BarActionRow, BarExtrasProvider, globalActionAsBarAction } from "../controls/actions.js";
|
|
14
|
+
import { SyncButton } from "../controls/sync-button.js";
|
|
15
|
+
import classes from "./page-bar.module.css";
|
|
16
|
+
const PAGE_BAR_HEIGHT_VAR = "--basalt-page-bar-h";
|
|
17
|
+
const NO_SLOTS = {
|
|
18
|
+
target: null,
|
|
19
|
+
setTarget: () => {
|
|
20
|
+
},
|
|
21
|
+
inShell: false,
|
|
22
|
+
mobileMoreActions: [],
|
|
23
|
+
kebabClaims: 0,
|
|
24
|
+
claimKebab: () => () => {
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const PageBarContext = createContext(NO_SLOTS);
|
|
28
|
+
function PageBarProvider({
|
|
29
|
+
globalActions,
|
|
30
|
+
children
|
|
31
|
+
}) {
|
|
32
|
+
const [target, setTarget] = useState(null);
|
|
33
|
+
const [kebabClaims, setKebabClaims] = useState(0);
|
|
34
|
+
const claimKebab = useCallback(() => {
|
|
35
|
+
setKebabClaims((n) => n + 1);
|
|
36
|
+
return () => setKebabClaims((n) => n - 1);
|
|
37
|
+
}, []);
|
|
38
|
+
const mobileMoreActions = globalActions.filter((action, index) => (action.mobile ?? (index < 2 ? "bar" : "more")) === "more").map(globalActionAsBarAction);
|
|
39
|
+
const value = {
|
|
40
|
+
target,
|
|
41
|
+
setTarget,
|
|
42
|
+
inShell: true,
|
|
43
|
+
mobileMoreActions,
|
|
44
|
+
kebabClaims,
|
|
45
|
+
claimKebab
|
|
46
|
+
};
|
|
47
|
+
return /* @__PURE__ */ jsx(PageBarContext.Provider, { value, children: /* @__PURE__ */ jsx(BarExtrasProvider, { value: { mobileMoreActions, claimKebab }, children }) });
|
|
48
|
+
}
|
|
49
|
+
function PageBarOutlet({ className }) {
|
|
50
|
+
const { setTarget } = useContext(PageBarContext);
|
|
51
|
+
return /* @__PURE__ */ jsx("div", { ref: setTarget, className });
|
|
52
|
+
}
|
|
53
|
+
function usePageKebabClaimed() {
|
|
54
|
+
return useContext(PageBarContext).kebabClaims > 0;
|
|
55
|
+
}
|
|
56
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
57
|
+
function useMeasuredHeightVar(active) {
|
|
58
|
+
const ref = useRef(null);
|
|
59
|
+
useIsomorphicLayoutEffect(() => {
|
|
60
|
+
const node = ref.current;
|
|
61
|
+
if (!active || node === null) return;
|
|
62
|
+
const publish = () => {
|
|
63
|
+
const { height } = node.getBoundingClientRect();
|
|
64
|
+
if (height > 0) {
|
|
65
|
+
document.documentElement.style.setProperty(PAGE_BAR_HEIGHT_VAR, `${height}px`);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
publish();
|
|
69
|
+
if (typeof ResizeObserver === "undefined") {
|
|
70
|
+
return () => document.documentElement.style.removeProperty(PAGE_BAR_HEIGHT_VAR);
|
|
71
|
+
}
|
|
72
|
+
const observer = new ResizeObserver(publish);
|
|
73
|
+
observer.observe(node);
|
|
74
|
+
return () => {
|
|
75
|
+
observer.disconnect();
|
|
76
|
+
document.documentElement.style.removeProperty(PAGE_BAR_HEIGHT_VAR);
|
|
77
|
+
};
|
|
78
|
+
}, [active]);
|
|
79
|
+
return ref;
|
|
80
|
+
}
|
|
81
|
+
function rootClass(own, extra) {
|
|
82
|
+
return [own, extra].filter(Boolean).join(" ");
|
|
83
|
+
}
|
|
84
|
+
function PageBar({
|
|
85
|
+
title,
|
|
86
|
+
icon,
|
|
87
|
+
actions,
|
|
88
|
+
sync,
|
|
89
|
+
filters,
|
|
90
|
+
filtersEnd,
|
|
91
|
+
tabs,
|
|
92
|
+
className
|
|
93
|
+
}) {
|
|
94
|
+
const { target, inShell } = useContext(PageBarContext);
|
|
95
|
+
const filtersEndActions = filtersEnd ?? [];
|
|
96
|
+
const hasRow2 = tabs !== void 0 || filters !== void 0 || filtersEndActions.length > 0;
|
|
97
|
+
const hasLead = !inShell && (title !== void 0 || icon !== void 0);
|
|
98
|
+
const hasRow1 = actions !== void 0 || sync !== void 0 || hasLead || filtersEndActions.length > 0;
|
|
99
|
+
const measureRef = useMeasuredHeightVar(hasRow2 || !inShell && hasRow1);
|
|
100
|
+
const row1 = hasRow1 ? /* @__PURE__ */ jsxs("div", { className: classes.row1, children: [
|
|
101
|
+
hasLead && /* @__PURE__ */ jsxs("div", { className: classes.lead, children: [
|
|
102
|
+
icon !== void 0 && /* @__PURE__ */ jsx("span", { className: classes.icon, "aria-hidden": true, children: icon }),
|
|
103
|
+
title !== void 0 && /* @__PURE__ */ jsx("h1", { className: classes.title, children: title })
|
|
104
|
+
] }),
|
|
105
|
+
/* @__PURE__ */ jsx("div", { className: classes.row1End, children: (actions !== void 0 || filtersEndActions.length > 0 || sync !== void 0) && /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsx(
|
|
106
|
+
BarActionRow,
|
|
107
|
+
{
|
|
108
|
+
...actions,
|
|
109
|
+
host: "page",
|
|
110
|
+
mobileOnly: filtersEndActions,
|
|
111
|
+
...sync !== void 0 && { syncNode: /* @__PURE__ */ jsx(SyncButton, { ...sync, scope: "page" }) }
|
|
112
|
+
}
|
|
113
|
+
) }) })
|
|
114
|
+
] }) : null;
|
|
115
|
+
const row2 = hasRow2 ? /* @__PURE__ */ jsxs("div", { className: classes.row2, children: [
|
|
116
|
+
tabs !== void 0 && /* @__PURE__ */ jsx(CtlSlot, { children: tabs }),
|
|
117
|
+
filters !== void 0 && /* @__PURE__ */ jsx("div", { className: classes.filters, children: /* @__PURE__ */ jsx(CtlSlot, { children: filters }) }),
|
|
118
|
+
filtersEndActions.length > 0 && /* @__PURE__ */ jsx("div", { className: classes.filtersEnd, children: /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsx(BarActionRow, { secondary: filtersEndActions, host: "slot", viewport: "desktop" }) }) })
|
|
119
|
+
] }) : null;
|
|
120
|
+
if (!inShell) {
|
|
121
|
+
if (row1 === null && row2 === null) return null;
|
|
122
|
+
return /* @__PURE__ */ jsxs(
|
|
123
|
+
"div",
|
|
124
|
+
{
|
|
125
|
+
ref: measureRef,
|
|
126
|
+
className: rootClass(classes.bar, className),
|
|
127
|
+
"data-basalt-page-bar": "standalone",
|
|
128
|
+
children: [
|
|
129
|
+
row1,
|
|
130
|
+
row2
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
136
|
+
row1 !== null && target !== null && createPortal(row1, target),
|
|
137
|
+
row2 !== null && /* @__PURE__ */ jsx(
|
|
138
|
+
"div",
|
|
139
|
+
{
|
|
140
|
+
ref: measureRef,
|
|
141
|
+
className: rootClass(classes.row2Sticky, className),
|
|
142
|
+
"data-basalt-page-bar": "shell",
|
|
143
|
+
children: row2
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
] });
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
PAGE_BAR_HEIGHT_VAR,
|
|
150
|
+
PageBar,
|
|
151
|
+
PageBarOutlet,
|
|
152
|
+
PageBarProvider,
|
|
153
|
+
usePageKebabClaimed
|
|
154
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `PageBar` layout (docs/CONTROLS-SPEC.md §2.1).
|
|
3
|
+
*
|
|
4
|
+
* Law C7 in CSS form: `flex-wrap: nowrap` on every row and NO `overflow-x` anywhere. A home that
|
|
5
|
+
* scrolls sideways hides its own controls; the fold is computed instead — `More` for actions
|
|
6
|
+
* (`ActionGroup`), a `Filters (n)` sheet for filters (`FilterSet`'s job, wave 4). The filter
|
|
7
|
+
* container below only supplies `min-width: 0` so that fold has a box to measure against.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* The shell-less bar: both rows, sticky at the top of the document. One layer under the AppShell
|
|
11
|
+
* header (`--app-shell-header-z-index`, default 100) so it never wins a stacking fight with ordinary
|
|
12
|
+
* in-flow content — Mantine's Input section, Indicator, sticky table headers, and Slider thumbs all
|
|
13
|
+
* sit at `z-index: 1` or higher, which used to paint over a bare `z-index: 1` bar while scrolling. */
|
|
14
|
+
.bar {
|
|
15
|
+
position: sticky;
|
|
16
|
+
top: 0;
|
|
17
|
+
z-index: calc(var(--app-shell-header-z-index, 100) - 1);
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: var(--vx-space-stack-xs);
|
|
21
|
+
background-color: var(--vx-surface-bg);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Row 2 inside a shell: in the page flow, sticky under the 48px header. `--app-shell-header-height`
|
|
26
|
+
* is Mantine's own AppShell variable, so the offset tracks the header at any density with no React
|
|
27
|
+
* state and no measurement. `z-index` sits one layer below the header itself (see `.bar` above) —
|
|
28
|
+
* high enough to stay above ordinary page content, never above the AppShell header.
|
|
29
|
+
*/
|
|
30
|
+
.row2Sticky {
|
|
31
|
+
position: sticky;
|
|
32
|
+
top: var(--app-shell-header-height, 0px);
|
|
33
|
+
z-index: calc(var(--app-shell-header-z-index, 100) - 1);
|
|
34
|
+
background-color: var(--vx-surface-bg);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.row1 {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-wrap: nowrap;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: var(--vx-space-control-gap);
|
|
42
|
+
min-width: 0;
|
|
43
|
+
min-height: var(--vx-space-page-bar-row-height);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.lead {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-wrap: nowrap;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: var(--vx-space-control-gap);
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.icon {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
color: var(--vx-muted);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* The shell-less page title (law C8: the breadcrumb names the page inside a shell, this does it
|
|
63
|
+
* outside one). Head-font treatment, same 88%/550 as every `WidgetHeader` title.
|
|
64
|
+
*/
|
|
65
|
+
.title {
|
|
66
|
+
margin: 0;
|
|
67
|
+
font-family: var(--basalt-font-head);
|
|
68
|
+
font-size: var(--vx-text-xl);
|
|
69
|
+
font-weight: 550;
|
|
70
|
+
font-stretch: 88%;
|
|
71
|
+
color: var(--vx-ink);
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
text-overflow: ellipsis;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* Icons inside a row-1 button are 16px and sit 6px from their label. Mantine spends
|
|
79
|
+
* `--mantine-spacing-xs` (11px) on a Button's section gap regardless of the size tier, which is the
|
|
80
|
+
* BODY tier's number — at 30px it detached the glyph from the word it belongs to. Scoped to the row
|
|
81
|
+
* rather than themed on `Button` globally: a `size="md"` button in a form still wants Mantine's gap.
|
|
82
|
+
*/
|
|
83
|
+
.row1 :global(.mantine-Button-section[data-position='left']),
|
|
84
|
+
.row2 :global(.mantine-Button-section[data-position='left']) {
|
|
85
|
+
margin-inline-end: var(--vx-space-control-gap);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.row1 :global(.mantine-Button-section) > svg,
|
|
89
|
+
.row1 :global(.mantine-ActionIcon-root) > svg {
|
|
90
|
+
width: 1rem;
|
|
91
|
+
height: 1rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Actions and sync are right-aligned; the lead (or the breadcrumb, in a shell) holds the left. */
|
|
95
|
+
.row1End {
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-wrap: nowrap;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: flex-end;
|
|
100
|
+
gap: var(--vx-space-control-gap);
|
|
101
|
+
flex: 1 1 auto;
|
|
102
|
+
min-width: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* Row 2's rhythm is 8px · pills · 8px, NOT a 40px reserved row.
|
|
107
|
+
*
|
|
108
|
+
* `min-height: var(--vx-space-page-bar-row-height)` (40px) left a 30px pill floating inside a 40px
|
|
109
|
+
* box with the 5px above and below it belonging to nothing — and because the row sat in a `Stack`
|
|
110
|
+
* that already spaced it, the visible gap between the header and the pills read as ~19px while the
|
|
111
|
+
* gap from the pills to the first card read as ~19px too. Symmetric padding on the row itself is the
|
|
112
|
+
* same total height (46px vs 40px) with both halves owned by one declaration, so the sticky bar's
|
|
113
|
+
* published height covers exactly the band it paints.
|
|
114
|
+
*/
|
|
115
|
+
.row2 {
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-wrap: nowrap;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: var(--vx-space-control-gap);
|
|
120
|
+
min-width: 0;
|
|
121
|
+
padding-block: var(--vx-space-stack-sm);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.filters {
|
|
125
|
+
display: flex;
|
|
126
|
+
flex-wrap: nowrap;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: var(--vx-space-control-gap);
|
|
129
|
+
flex: 1 1 auto;
|
|
130
|
+
min-width: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.filtersEnd {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-wrap: nowrap;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: var(--vx-space-control-gap);
|
|
138
|
+
margin-inline-start: auto;
|
|
139
|
+
flex-shrink: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
* Law C15 — every touch target inside a home is at least `touchControlHeight` (36px at level 0)
|
|
144
|
+
* below `sm`, while the VISIBLE box stays the 30px `ctl` tier. `::after` and not `::before`:
|
|
145
|
+
* Mantine's Button owns `::before` for its loading overlay (`@mantine/core/styles/Button.css`),
|
|
146
|
+
* so a hit area drawn there would fight it. `pointer-events` stays default so the overlay is the
|
|
147
|
+
* one receiving the tap.
|
|
148
|
+
*
|
|
149
|
+
* `overflow: visible` is LOAD-BEARING, not tidying. Mantine's Button and ActionIcon roots both set
|
|
150
|
+
* `overflow: hidden` (`Button.css`, `ActionIcon.css`), which clips an overlay that by definition
|
|
151
|
+
* extends past the 30px box — so without this the 36px target simply does not exist and a tap 3px
|
|
152
|
+
* above the control misses. What `overflow: hidden` buys those roots is clipping the loading
|
|
153
|
+
* overlay's blur, and that overlay is only painted under `[data-loading]`, which is excluded below.
|
|
154
|
+
*/
|
|
155
|
+
@media (max-width: 47.99375em) {
|
|
156
|
+
.row1 :is(button, a),
|
|
157
|
+
.row2 :is(button, a) {
|
|
158
|
+
position: relative;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.row1 :is(button, a):not([data-loading]),
|
|
162
|
+
.row2 :is(button, a):not([data-loading]) {
|
|
163
|
+
overflow: visible;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.row1 :is(button, a)::after,
|
|
167
|
+
.row2 :is(button, a)::after {
|
|
168
|
+
content: '';
|
|
169
|
+
position: absolute;
|
|
170
|
+
top: 50%;
|
|
171
|
+
left: 50%;
|
|
172
|
+
translate: -50% -50%;
|
|
173
|
+
width: 100%;
|
|
174
|
+
height: 100%;
|
|
175
|
+
min-width: var(--vx-space-touch-control-height);
|
|
176
|
+
min-height: var(--vx-space-touch-control-height);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed CSS-module declaration (the S4 CSS-modules path). Named string props — not an index
|
|
3
|
+
* signature — so dot access stays verbatim under the package's strict flags.
|
|
4
|
+
*/
|
|
5
|
+
declare const classes: {
|
|
6
|
+
readonly bar: string
|
|
7
|
+
readonly row2Sticky: string
|
|
8
|
+
readonly row1: string
|
|
9
|
+
readonly lead: string
|
|
10
|
+
readonly icon: string
|
|
11
|
+
readonly title: string
|
|
12
|
+
readonly row2: string
|
|
13
|
+
readonly row1End: string
|
|
14
|
+
readonly filters: string
|
|
15
|
+
readonly filtersEnd: string
|
|
16
|
+
}
|
|
17
|
+
export default classes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-bar.test.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.test.tsx"],"names":[],"mappings":""}
|