basalt-ui 1.30.2 → 1.32.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 +1 -1
- package/CHANGELOG.md +54 -0
- package/MIGRATING.md +256 -12
- package/agent/rules/basalt-controls.md +19 -19
- package/agent/rules/basalt-mantine.md +5 -5
- package/configs/oxlint-plugin.js +371 -4
- package/configs/oxlint-plugin.test.ts +327 -2
- package/configs/oxlint.json +2 -1
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +41 -43
- package/dist/agent-chat/thread-workspace.d.ts +2 -2
- package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
- package/dist/agent-chat/thread-workspace.js +3 -4
- package/dist/agent-chat/use-keyboard-inset.d.ts +2 -0
- package/dist/agent-chat/use-keyboard-inset.d.ts.map +1 -0
- package/dist/agent-chat/use-keyboard-inset.js +34 -0
- package/dist/charts/cursor/touch-pin.d.ts +45 -0
- package/dist/charts/cursor/touch-pin.d.ts.map +1 -0
- package/dist/charts/cursor/touch-pin.js +79 -0
- package/dist/charts/hooks/use-discrete-cursor.d.ts +66 -0
- package/dist/charts/hooks/use-discrete-cursor.d.ts.map +1 -0
- package/dist/charts/hooks/use-discrete-cursor.js +198 -0
- package/dist/charts/hooks/use-hysteresis.d.ts +18 -0
- package/dist/charts/hooks/use-hysteresis.d.ts.map +1 -0
- package/dist/charts/hooks/use-hysteresis.js +12 -0
- package/dist/charts/hooks/useBandPlot.d.ts +7 -0
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
- package/dist/charts/hooks/useBandPlot.js +62 -18
- package/dist/charts/hooks/useChartCursor.d.ts +40 -11
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
- package/dist/charts/hooks/useChartCursor.js +102 -11
- package/dist/charts/index.d.ts +0 -1
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +0 -10
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +47 -82
- package/dist/charts/kinds/Donut.d.ts +9 -3
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +110 -48
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +138 -145
- package/dist/charts/kinds/Heatmap.d.ts +7 -0
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +37 -16
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +68 -111
- package/dist/charts/kinds/band-chrome.d.ts +31 -0
- package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
- package/dist/charts/kinds/band-chrome.js +71 -0
- package/dist/charts/kinds/donut-layout.d.ts +47 -0
- package/dist/charts/kinds/donut-layout.d.ts.map +1 -0
- package/dist/charts/kinds/donut-layout.js +52 -0
- package/dist/charts/layout/auto-margin.d.ts +7 -2
- package/dist/charts/layout/auto-margin.d.ts.map +1 -1
- package/dist/charts/layout/auto-margin.js +12 -3
- package/dist/charts/layout/end-labels.d.ts +64 -0
- package/dist/charts/layout/end-labels.d.ts.map +1 -0
- package/dist/charts/layout/end-labels.js +76 -0
- package/dist/charts/primitives/Axes.d.ts +32 -4
- package/dist/charts/primitives/Axes.d.ts.map +1 -1
- package/dist/charts/primitives/Axes.js +62 -16
- package/dist/charts/primitives/CartesianChart.d.ts +7 -6
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +264 -145
- package/dist/charts/primitives/ChartCard.d.ts +0 -14
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +56 -21
- package/dist/charts/primitives/ChartFrame.d.ts +28 -22
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +116 -34
- package/dist/charts/primitives/ChartLegend.d.ts +19 -15
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +86 -73
- package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +20 -4
- package/dist/charts/primitives/Crosshair.js +2 -2
- package/dist/charts/primitives/HoverOverlay.d.ts +29 -9
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +54 -27
- package/dist/charts/primitives/HoverOverlay.module.css +13 -0
- package/dist/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
- package/dist/charts/primitives/chart-card-context.d.ts +33 -0
- package/dist/charts/primitives/chart-card-context.d.ts.map +1 -0
- package/dist/charts/primitives/chart-card-context.js +18 -0
- package/dist/charts/primitives/chart-frame-layout.d.ts +93 -113
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
- package/dist/charts/primitives/chart-frame-layout.js +75 -34
- package/dist/charts/primitives/chart-layout.d.ts +112 -0
- package/dist/charts/primitives/chart-layout.d.ts.map +1 -0
- package/dist/charts/primitives/chart-layout.js +175 -0
- package/dist/charts/primitives/chart-tier.d.ts +13 -11
- package/dist/charts/primitives/chart-tier.d.ts.map +1 -1
- package/dist/charts/primitives/chart-tier.js +42 -13
- package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
- package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
- package/dist/charts/primitives/legend-disclosure.js +153 -0
- package/dist/charts/series.d.ts +4 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/utils/ticks.d.ts +27 -8
- package/dist/charts/utils/ticks.d.ts.map +1 -1
- package/dist/charts/utils/ticks.js +35 -11
- package/dist/common/errors.d.ts +11 -1
- package/dist/common/errors.d.ts.map +1 -1
- package/dist/common/errors.js +16 -2
- package/dist/common/hit-floor.d.ts +10 -0
- package/dist/common/hit-floor.d.ts.map +1 -0
- package/dist/common/hit-floor.js +6 -0
- package/dist/{shell → common}/isomorphic-layout-effect.d.ts +4 -3
- package/dist/common/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/common/use-measured-widths.d.ts +37 -0
- package/dist/common/use-measured-widths.d.ts.map +1 -0
- package/dist/common/use-measured-widths.js +51 -0
- package/dist/common/use-media-query.d.ts +6 -0
- package/dist/common/use-media-query.d.ts.map +1 -0
- package/dist/common/use-media-query.js +40 -0
- package/dist/common/validate.d.ts +9 -0
- package/dist/common/validate.d.ts.map +1 -1
- package/dist/common/validate.js +9 -4
- package/dist/content/article-layout.d.ts +4 -2
- package/dist/content/article-layout.d.ts.map +1 -1
- package/dist/content/article-layout.js +43 -3
- package/dist/content/article-layout.module.css +45 -4
- package/dist/content/article-layout.module.css.d.ts +3 -0
- package/dist/content/code-block.d.ts.map +1 -1
- package/dist/content/code-block.js +40 -2
- package/dist/content/code-block.module.css +32 -2
- package/dist/content/prose.module.css +13 -8
- package/dist/controls/actions.d.ts +29 -6
- package/dist/controls/actions.d.ts.map +1 -1
- package/dist/controls/actions.js +158 -27
- package/dist/controls/actions.module.css +14 -1
- package/dist/controls/actions.module.css.d.ts +1 -0
- package/dist/controls/controls.module.css +13 -31
- package/dist/controls/enum-filter.js +2 -2
- package/dist/controls/filter-context.d.ts +10 -0
- package/dist/controls/filter-context.d.ts.map +1 -1
- package/dist/controls/filter-context.js +2 -0
- package/dist/controls/filter-pill.d.ts.map +1 -1
- package/dist/controls/filter-pill.js +1 -0
- package/dist/controls/index.d.ts +1 -1
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +0 -2
- package/dist/controls/multi-select-filter.js +2 -2
- package/dist/controls/sync-button.module.css +2 -1
- package/dist/dashboard/section.d.ts.map +1 -1
- package/dist/dashboard/section.js +1 -0
- package/dist/dashboard/section.module.css +1 -26
- package/dist/dashboard/stat-card.d.ts +1 -1
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +4 -1
- package/dist/dashboard/stat-card.module.css +6 -9
- package/dist/dashboard/stat-group.module.css +33 -66
- package/dist/dashboard/widget-grid.d.ts.map +1 -1
- package/dist/dashboard/widget-grid.module.css +11 -38
- package/dist/dashboard/widget-header.d.ts +14 -4
- package/dist/dashboard/widget-header.d.ts.map +1 -1
- package/dist/dashboard/widget-header.js +23 -6
- package/dist/dashboard/widget-header.module.css +145 -133
- package/dist/dashboard/widget-header.module.css.d.ts +2 -0
- package/dist/data/data-table-state.d.ts +83 -0
- package/dist/data/data-table-state.d.ts.map +1 -0
- package/dist/data/data-table-state.js +139 -0
- package/dist/data/data-table.d.ts +56 -2
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +436 -336
- package/dist/data/data-table.module.css +94 -0
- package/dist/data/data-table.module.css.d.ts +11 -0
- package/dist/data/virtual-list.d.ts.map +1 -1
- package/dist/data/virtual-list.js +10 -0
- package/dist/forms/form-layout.d.ts.map +1 -1
- package/dist/forms/form-layout.js +3 -3
- package/dist/forms/form-layout.module.css +20 -5
- package/dist/forms/form-layout.module.css.d.ts +1 -0
- package/dist/guard/index.d.ts +8 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +68 -2
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/notifications/center.d.ts.map +1 -1
- package/dist/provider/index.d.ts +20 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +30 -8
- package/dist/shell/app-brand.d.ts.map +1 -1
- package/dist/shell/app-brand.js +1 -0
- package/dist/shell/app-brand.module.css +2 -2
- package/dist/shell/app-header.module.css +18 -8
- package/dist/shell/app-main.module.css +54 -3
- package/dist/shell/app-main.module.css.d.ts +1 -0
- package/dist/shell/app-mobile-nav.module.css +16 -4
- package/dist/shell/app-sidebar.module.css +4 -6
- package/dist/shell/aside-docking.d.ts +31 -0
- package/dist/shell/aside-docking.d.ts.map +1 -0
- package/dist/shell/aside-docking.js +19 -0
- package/dist/shell/index.d.ts +1 -0
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +56 -16
- package/dist/shell/mobile-nav-model.d.ts +1 -1
- package/dist/shell/page-aside.d.ts +22 -3
- package/dist/shell/page-aside.d.ts.map +1 -1
- package/dist/shell/page-aside.js +27 -13
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/page-bar.js +10 -5
- package/dist/shell/page-bar.module.css +10 -34
- package/dist/shell/use-breakpoint.d.ts +3 -4
- package/dist/shell/use-breakpoint.d.ts.map +1 -1
- package/dist/shell/use-breakpoint.js +3 -25
- package/dist/shell/use-size-class.d.ts +6 -0
- package/dist/shell/use-size-class.d.ts.map +1 -0
- package/dist/shell/use-size-class.js +19 -0
- package/dist/state/persisted-or-local.d.ts +4 -1
- package/dist/state/persisted-or-local.d.ts.map +1 -1
- package/dist/state/persisted-or-local.js +4 -3
- package/dist/state/persisted.d.ts +10 -0
- package/dist/state/persisted.d.ts.map +1 -1
- package/dist/state/persisted.js +44 -14
- package/dist/styles.css +41 -0
- package/dist/surfaces.d.ts +4 -4
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +11 -3
- package/dist/theme/controls.module.css +7 -0
- package/dist/theme/ctl-theme.d.ts +1 -0
- package/dist/theme/ctl-theme.d.ts.map +1 -1
- package/dist/theme/ctl-theme.js +12 -4
- package/dist/theme/floating.module.css +37 -0
- package/dist/theme/floating.module.css.d.ts +3 -0
- package/dist/theme/index.d.ts +2 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +34 -5
- package/dist/theme/nav-link.module.css +7 -0
- package/dist/theme-lab/derive-controls.d.ts.map +1 -1
- package/dist/tokens/index.d.ts +7 -16
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +23 -28
- package/dist/tokens/palette.d.ts +5 -27
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +8 -30
- package/dist/tokens/size-classes.d.ts +40 -0
- package/dist/tokens/size-classes.d.ts.map +1 -0
- package/dist/tokens/size-classes.js +29 -0
- package/dist/tokens.css +8 -4
- package/llms.txt +3 -3
- package/package.json +18 -17
- package/src/agent-chat/composer.tsx +74 -47
- package/src/agent-chat/thread-workspace.tsx +8 -11
- package/src/agent-chat/use-keyboard-inset.ts +68 -0
- package/src/charts/cursor/touch-pin.ts +138 -0
- package/src/charts/hooks/use-discrete-cursor.ts +335 -0
- package/src/charts/hooks/use-hysteresis.ts +26 -0
- package/src/charts/hooks/useBandPlot.ts +90 -27
- package/src/charts/hooks/useChartCursor.ts +211 -26
- package/src/charts/index.ts +0 -8
- package/src/charts/kinds/BandStrip.tsx +25 -61
- package/src/charts/kinds/Donut.tsx +153 -62
- package/src/charts/kinds/DualPanel.tsx +21 -26
- package/src/charts/kinds/Heatmap.tsx +61 -22
- package/src/charts/kinds/MirroredBars.tsx +23 -65
- package/src/charts/kinds/band-chrome.tsx +91 -0
- package/src/charts/kinds/donut-layout.ts +82 -0
- package/src/charts/layout/auto-margin.ts +24 -5
- package/src/charts/layout/end-labels.ts +135 -0
- package/src/charts/primitives/Axes.tsx +112 -21
- package/src/charts/primitives/CartesianChart.tsx +282 -111
- package/src/charts/primitives/ChartCard.tsx +119 -48
- package/src/charts/primitives/ChartFrame.tsx +204 -60
- package/src/charts/primitives/ChartLegend.tsx +115 -78
- package/src/charts/primitives/ChartTooltip.tsx +24 -3
- package/src/charts/primitives/Crosshair.tsx +2 -2
- package/src/charts/primitives/HoverOverlay.module.css +13 -0
- package/src/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
- package/src/charts/primitives/HoverOverlay.tsx +62 -20
- package/src/charts/primitives/chart-card-context.ts +46 -0
- package/src/charts/primitives/chart-frame-layout.ts +207 -182
- package/src/charts/primitives/chart-layout.ts +318 -0
- package/src/charts/primitives/chart-tier.tsx +66 -21
- package/src/charts/primitives/legend-disclosure.tsx +177 -0
- package/src/charts/series.ts +4 -1
- package/src/charts/utils/ticks.ts +95 -19
- package/src/common/errors.ts +25 -1
- package/src/common/hit-floor.ts +9 -0
- package/src/{shell → common}/isomorphic-layout-effect.ts +4 -3
- package/src/common/use-measured-widths.ts +88 -0
- package/src/common/use-media-query.ts +56 -0
- package/src/common/validate.ts +17 -4
- package/src/content/article-layout.module.css +45 -4
- package/src/content/article-layout.module.css.d.ts +3 -0
- package/src/content/article-layout.tsx +60 -5
- package/src/content/code-block.module.css +32 -2
- package/src/content/code-block.tsx +47 -3
- package/src/content/prose.module.css +13 -8
- package/src/controls/actions.module.css +14 -1
- package/src/controls/actions.module.css.d.ts +1 -0
- package/src/controls/actions.tsx +223 -75
- package/src/controls/controls.module.css +13 -31
- package/src/controls/enum-filter.tsx +2 -2
- package/src/controls/filter-context.tsx +11 -0
- package/src/controls/filter-pill.tsx +1 -0
- package/src/controls/index.ts +0 -1
- package/src/controls/multi-select-filter.tsx +2 -2
- package/src/controls/sync-button.module.css +2 -1
- package/src/dashboard/section.module.css +1 -26
- package/src/dashboard/section.tsx +1 -0
- package/src/dashboard/stat-card.module.css +6 -9
- package/src/dashboard/stat-card.tsx +6 -3
- package/src/dashboard/stat-group.module.css +33 -66
- package/src/dashboard/widget-grid.module.css +11 -38
- package/src/dashboard/widget-grid.tsx +7 -6
- package/src/dashboard/widget-header.module.css +145 -133
- package/src/dashboard/widget-header.module.css.d.ts +2 -0
- package/src/dashboard/widget-header.tsx +45 -11
- package/src/data/data-table-state.ts +218 -0
- package/src/data/data-table.module.css +94 -0
- package/src/data/data-table.module.css.d.ts +11 -0
- package/src/data/data-table.tsx +547 -299
- package/src/data/virtual-list.tsx +31 -0
- package/src/forms/form-layout.module.css +20 -5
- package/src/forms/form-layout.module.css.d.ts +1 -0
- package/src/forms/form-layout.tsx +12 -10
- package/src/guard/index.ts +188 -3
- package/src/guard/types.ts +3 -2
- package/src/index.ts +2 -0
- package/src/notifications/center.tsx +3 -0
- package/src/provider/index.tsx +60 -7
- package/src/shell/app-brand.module.css +2 -2
- package/src/shell/app-brand.tsx +1 -0
- package/src/shell/app-header.module.css +18 -8
- package/src/shell/app-main.module.css +54 -3
- package/src/shell/app-main.module.css.d.ts +1 -0
- package/src/shell/app-mobile-nav.module.css +16 -4
- package/src/shell/app-sidebar.module.css +4 -6
- package/src/shell/aside-docking.ts +54 -0
- package/src/shell/index.tsx +178 -129
- package/src/shell/mobile-nav-model.ts +1 -1
- package/src/shell/page-aside.tsx +70 -21
- package/src/shell/page-bar.module.css +10 -34
- package/src/shell/page-bar.tsx +10 -5
- package/src/shell/use-breakpoint.ts +9 -45
- package/src/shell/use-size-class.ts +31 -0
- package/src/state/persisted-or-local.ts +10 -4
- package/src/state/persisted.ts +76 -17
- package/src/styles.css +41 -0
- package/src/surfaces.ts +9 -1
- package/src/theme/controls.module.css +7 -0
- package/src/theme/ctl-theme.tsx +15 -3
- package/src/theme/floating.module.css +37 -0
- package/src/theme/floating.module.css.d.ts +3 -0
- package/src/theme/index.ts +46 -8
- package/src/theme/nav-link.module.css +7 -0
- package/src/theme-lab/derive-controls.tsx +2 -0
- package/src/tokens/index.ts +20 -30
- package/src/tokens/palette.ts +6 -28
- package/src/tokens/size-classes.ts +46 -0
- package/dist/shell/isomorphic-layout-effect.d.ts.map +0 -1
- /package/dist/{shell → common}/isomorphic-layout-effect.js +0 -0
|
@@ -131,8 +131,59 @@
|
|
|
131
131
|
* `padding-right`; the block padding keeps coming from the shorthand. The bottom inset already
|
|
132
132
|
* rides `margin-bottom` above (`env(safe-area-inset-bottom)`); the PWA's top inset needs no rule here
|
|
133
133
|
* (the status bar uses the opaque `default` style, per argo's own seed comment). */
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
:global(:root[data-basalt-host]:not([data-basalt-host='web'])) .main {
|
|
135
|
+
padding-right: max(var(--app-shell-padding), env(safe-area-inset-right));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* PUBLIC SHELL BOTTOM INSET — `--app-shell-footer-offset` (stable name, do not rename): the visible
|
|
140
|
+
* bottom-bar height WITHOUT the safe-area inset, and 0 wherever there is no bar (from `sm` up).
|
|
141
|
+
* Anything bottom-anchored (toasts, a chat composer) sits at
|
|
142
|
+
* `calc(var(--app-shell-footer-offset, 0px) + env(safe-area-inset-bottom, 0px))`.
|
|
143
|
+
*
|
|
144
|
+
* Mantine emits it on `:root` for a fixed-mode AppShell (its `cssVariablesSelector`), so a
|
|
145
|
+
* body-level portal reads it; the landscape override below is on `:root` for the same reason.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/*
|
|
149
|
+
* An OPEN aside that does not dock: the region reserved only its rail, so it widens itself over
|
|
150
|
+
* main (`--app-shell-aside-width` is set on this element by `shell/index.tsx`) instead of shrinking
|
|
151
|
+
* it, lifted on the card depth token.
|
|
152
|
+
*/
|
|
153
|
+
.asideOverlay {
|
|
154
|
+
box-shadow: var(--vx-shadow-card);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* Landscape phone: the header and the bottom bar take their compact row, SCALED to the viewport's
|
|
159
|
+
* own height budget rather than a flat step — a flat 40/45 barely shrinks (44→40, 56→45, ≈9-20%)
|
|
160
|
+
* against the height itself halving (812→375), so the chrome's SHARE of the screen nearly doubles
|
|
161
|
+
* (r2 N1: 16.7% portrait → 32.3% landscape even with the flat numbers). `clamp(floor, Ndvh,
|
|
162
|
+
* ceiling)` keeps continuity at the 480px breakpoint edge (Ndvh ≈ ceiling there) and keeps
|
|
163
|
+
* shrinking below it, down to a floor: 36px for the header (room for the brand/burger row), and
|
|
164
|
+
* `--vx-space-mobile-nav-row-height + 1px` (its own seam) for the bar — the row token, not a flat
|
|
165
|
+
* 45px, because `deriveSpacing`'s density knob only FLOORS that token at 44 and is free to scale it
|
|
166
|
+
* taller at density > 0; a hardcoded 45px floor would then clamp the footer BELOW its own row's
|
|
167
|
+
* real height at any positive density, clipping it. The heights are JS numbers in `AppShell` props,
|
|
168
|
+
* so the override is on `:root` where Mantine writes them; `:root:root` outranks its later-rendered
|
|
169
|
+
* `<style>` at equal source order. Both rules are gated to the same width — the header and the bar
|
|
170
|
+
* both exist ONLY below `sm` in this landscape-compact form; a wide landscape phone/tablet has no
|
|
171
|
+
* bar (and keeps `--app-shell-footer-offset` 0) and reads its header at the ordinary, un-clamped
|
|
172
|
+
* height, not this mobile row. Labels hidden: `app-mobile-nav.module.css`.
|
|
173
|
+
*/
|
|
174
|
+
@media (max-height: 480px) and (orientation: landscape) and (max-width: 52.49375em) {
|
|
175
|
+
:global(:root:root) {
|
|
176
|
+
--app-shell-header-height: clamp(36px, 9dvh, 40px);
|
|
177
|
+
--app-shell-header-offset: clamp(36px, 9dvh, 40px);
|
|
178
|
+
--app-shell-footer-height: clamp(
|
|
179
|
+
calc(var(--vx-space-mobile-nav-row-height) + 1px),
|
|
180
|
+
9.4dvh,
|
|
181
|
+
56px
|
|
182
|
+
);
|
|
183
|
+
--app-shell-footer-offset: clamp(
|
|
184
|
+
calc(var(--vx-space-mobile-nav-row-height) + 1px),
|
|
185
|
+
9.4dvh,
|
|
186
|
+
56px
|
|
187
|
+
);
|
|
137
188
|
}
|
|
138
189
|
}
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
* The popover's SECTION RHYTHM — what replaced the single `Menu.Divider`, and it is SPACE, not a
|
|
196
196
|
* line.
|
|
197
197
|
*
|
|
198
|
-
* The Drawer's bulk was never its row height (
|
|
198
|
+
* The Drawer's bulk was never its row height (44px is the WCAG 2.5.5 AAA-enhanced floor pinned by
|
|
199
199
|
* `theme/density-relations.test.ts`); it was the stack rhythm around the rows — a 12px section
|
|
200
200
|
* gap, 18px of Drawer padding and a header row. A popover with one hairline through the middle and
|
|
201
201
|
* no rhythm at all is the other extreme, so each group opens its own band instead.
|
|
@@ -218,8 +218,8 @@
|
|
|
218
218
|
* `max-height` above answers, and the cheaper half. MEASURED per heading at 8/8: 8px margin + 8px
|
|
219
219
|
* padding-top + 2px padding-bottom + a 17px line = a 35px box, and there are FIVE of them in the
|
|
220
220
|
* shipped More surface — 175px, 28% of the whole dropdown spent on five words. 6/4 takes each
|
|
221
|
-
* banded heading to 29px and hands 24px back to the rows. It does NOT touch the row height (
|
|
222
|
-
* is the WCAG 2.5.5 floor named at the top of this comment) and it does not add the line the
|
|
221
|
+
* banded heading to 29px and hands 24px back to the rows. It does NOT touch the row height (44px
|
|
222
|
+
* is the WCAG 2.5.5 AAA-enhanced floor named at the top of this comment) and it does not add the line the
|
|
223
223
|
* paragraph above rules out: this is the same space that argument asked to carry the boundary,
|
|
224
224
|
* spent tighter.
|
|
225
225
|
*/
|
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
* longer fights that — it used to force the dense desktop row's padding back OPEN with
|
|
332
332
|
* `!important`, which is exactly how a "44px touch row" and a "29px sidebar row" ended up as two
|
|
333
333
|
* unrelated geometries for the same list. What is left here is only what the theme does not supply:
|
|
334
|
-
* the touch-target `min-height` floor (`--vx-space-mobile-nav-row-height`,
|
|
334
|
+
* the touch-target `min-height` floor (`--vx-space-mobile-nav-row-height`, 44px — WCAG 2.5.5 AAA-enhanced).
|
|
335
335
|
*/
|
|
336
336
|
.row {
|
|
337
337
|
display: flex;
|
|
@@ -403,3 +403,15 @@
|
|
|
403
403
|
transition: none;
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
|
+
|
|
407
|
+
/* Landscape phone: the bar's compact row (`app-main.module.css` sets its height) is icons only —
|
|
408
|
+
* the label is visually hidden, not removed, so each tab keeps its accessible name. */
|
|
409
|
+
@media (max-height: 480px) and (orientation: landscape) {
|
|
410
|
+
.label {
|
|
411
|
+
position: absolute;
|
|
412
|
+
width: 1px;
|
|
413
|
+
height: 1px;
|
|
414
|
+
overflow: hidden;
|
|
415
|
+
clip-path: inset(50%);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -30,10 +30,8 @@
|
|
|
30
30
|
* notch on the side the sidebar touches. `max()` is a FLOOR — the inset only grows the padding
|
|
31
31
|
* past its normal value, never shrinks it. Right inset is `app-main.module.css`'s to draw; the
|
|
32
32
|
* PWA's top inset needs no rule here (the status bar uses the opaque `default` style). */
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
padding-left: max(var(--vx-space-row-inset-x), env(safe-area-inset-left));
|
|
36
|
-
}
|
|
33
|
+
:global(:root[data-basalt-host]:not([data-basalt-host='web'])) .root {
|
|
34
|
+
padding-left: max(var(--vx-space-row-inset-x), env(safe-area-inset-left));
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
.link {
|
|
@@ -634,12 +632,12 @@
|
|
|
634
632
|
}
|
|
635
633
|
|
|
636
634
|
/*
|
|
637
|
-
* Icon-rail (collapsed) — desktop only. Gated behind min-width: sm (
|
|
635
|
+
* Icon-rail (collapsed) — desktop only. Gated behind min-width: sm (52.5em) so the sidebar doesn't
|
|
638
636
|
* exist at all below that breakpoint (see `MobileNav`) regardless of the persisted collapse flag.
|
|
639
637
|
* Hide the section labels and nav labels; center the remaining icons. The brand's own rail form
|
|
640
638
|
* lives in `app-brand.module.css` now, keyed off the same `data-collapsed` attribute.
|
|
641
639
|
*/
|
|
642
|
-
@media (min-width:
|
|
640
|
+
@media (min-width: 52.5em) {
|
|
643
641
|
.root[data-collapsed] .searchSlot {
|
|
644
642
|
padding-block: var(--vx-space-sidebar-region-gap);
|
|
645
643
|
display: flex;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aside docking (`docs/DESIGN-CORE.md` § Layout, elevation, shapes) — the pure decision `ShellFrame`
|
|
3
|
+
* sizes `AppShell.Aside` from. An open aside pushes main only in `expanded` while main keeps
|
|
4
|
+
* `MAIN_MIN_WIDTH` beside the navbar; otherwise the region reserves just its rail and an open panel
|
|
5
|
+
* overlays main. Unclaimed, the region is zero-wide.
|
|
6
|
+
*/
|
|
7
|
+
import { toEm } from '../tokens/size-classes'
|
|
8
|
+
import type { SizeClass } from './use-size-class'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main keeps at least this much width, or an open aside overlays it instead of docking. Not
|
|
12
|
+
* derived from a token: it is the readable floor for a page body, independent of density.
|
|
13
|
+
*/
|
|
14
|
+
const MAIN_MIN_WIDTH = 720
|
|
15
|
+
|
|
16
|
+
export type AsideDockingInput = {
|
|
17
|
+
claimed: boolean
|
|
18
|
+
folded: boolean
|
|
19
|
+
sizeClass: SizeClass
|
|
20
|
+
/** The `asideDockQuery` media query's result. */
|
|
21
|
+
roomToDock: boolean
|
|
22
|
+
asideWidth: number
|
|
23
|
+
asideRailWidth: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AsideDocking = {
|
|
27
|
+
/** An open aside pushes main rather than overlaying it. */
|
|
28
|
+
docks: boolean
|
|
29
|
+
/** An open aside renders over main (reserving only its rail). */
|
|
30
|
+
overlay: boolean
|
|
31
|
+
/** The width `AppShell` reserves for the region. */
|
|
32
|
+
width: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The `min-width` query that holds while navbar + open aside + `MAIN_MIN_WIDTH` fit side by side. */
|
|
36
|
+
export function asideDockQuery(input: { navbarWidth: number; asideWidth: number }): string {
|
|
37
|
+
return `(min-width: ${toEm(input.navbarWidth + input.asideWidth + MAIN_MIN_WIDTH)})`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The room-to-dock query's SSR/no-`matchMedia` fallback — an `'expanded'` `sizeClassHint` means the
|
|
42
|
+
* server already believes there is room, matching `useSizeClass`'s own hint-seeded guard
|
|
43
|
+
* (`use-size-class.ts`), so the first client paint doesn't fold a default-open aside for one commit.
|
|
44
|
+
*/
|
|
45
|
+
export function roomToDockServerFallback(sizeClassHint: SizeClass | undefined): boolean {
|
|
46
|
+
return sizeClassHint === 'expanded'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function resolveAsideDocking(input: AsideDockingInput): AsideDocking {
|
|
50
|
+
const docks = input.sizeClass === 'expanded' && input.roomToDock
|
|
51
|
+
const open = input.claimed && !input.folded
|
|
52
|
+
const reserved = open && docks ? input.asideWidth : input.asideRailWidth
|
|
53
|
+
return { docks, overlay: open && !docks, width: input.claimed ? reserved : 0 }
|
|
54
|
+
}
|
package/src/shell/index.tsx
CHANGED
|
@@ -15,14 +15,17 @@
|
|
|
15
15
|
* `item.onClick`, `item.badge`/`item.count` and supplies `item.Anchor` — its router `<Link>`,
|
|
16
16
|
* which basalt HOSTS rather than delegating rendering to. The breadcrumb is derived from the
|
|
17
17
|
* active item across `sections`, not from a router hook. Collapse is persisted via basalt's own
|
|
18
|
-
* `
|
|
18
|
+
* `createPersistedStore` (`../state/persisted`, `createPersistedState` plus an `isSet` flag) keyed
|
|
19
|
+
* by `storageKey` — see `collapseStore`. While nothing is persisted the size class picks the
|
|
20
|
+
* default (rail in `medium`); any stored value, `false` included, wins.
|
|
19
21
|
*/
|
|
20
|
-
import { AppShell, Box } from '@mantine/core'
|
|
21
|
-
import { Fragment, useCallback, useEffect, useMemo } from 'react'
|
|
22
|
-
import type { MouseEvent, ReactNode } from 'react'
|
|
22
|
+
import { AppShell, Box, rem } from '@mantine/core'
|
|
23
|
+
import { Fragment, useCallback, useContext, useEffect, useMemo } from 'react'
|
|
24
|
+
import type { CSSProperties, MouseEvent, ReactNode } from 'react'
|
|
23
25
|
import { registerSidebarToggle } from '../commands/shell-bridge'
|
|
24
26
|
import { cx } from '../common/props'
|
|
25
27
|
import type { BasaltProps } from '../common/props'
|
|
28
|
+
import { useMediaQuery } from '../common/use-media-query'
|
|
26
29
|
import { assertRequiredProps } from '../common/validate'
|
|
27
30
|
import { AppSidebar } from './app-sidebar'
|
|
28
31
|
import { AppBrand } from './app-brand'
|
|
@@ -30,7 +33,8 @@ import { MobileNav, accountRowCount } from './app-mobile-nav'
|
|
|
30
33
|
import { blockRowCount, projectMobileNav } from './mobile-nav-model'
|
|
31
34
|
import { AppBreadcrumbs } from './app-breadcrumbs'
|
|
32
35
|
import { PageBarBandOutlet, PageBarOutlet, PageBarProvider, usePageKebabClaimed } from './page-bar'
|
|
33
|
-
import { AsideOutlet, AsideProvider, useAsideRegion } from './page-aside'
|
|
36
|
+
import { AsideDocksContext, AsideOutlet, AsideProvider, useAsideRegion } from './page-aside'
|
|
37
|
+
import { asideDockQuery, resolveAsideDocking, roomToDockServerFallback } from './aside-docking'
|
|
34
38
|
import { OverflowMenu, globalActionAsBarAction, globalActionMobile } from '../controls/actions'
|
|
35
39
|
import type { GlobalAction } from '../controls/actions'
|
|
36
40
|
import type { AccountMenuItem, BasaltAccountProps } from './account-types'
|
|
@@ -43,7 +47,8 @@ import type {
|
|
|
43
47
|
SidebarSection,
|
|
44
48
|
} from './nav-types'
|
|
45
49
|
import { CtlSlot, useBasaltSpacing } from '../theme'
|
|
46
|
-
import {
|
|
50
|
+
import { createPersistedStore } from '../state/persisted'
|
|
51
|
+
import { SizeClassHintContext, useSizeClass } from './use-size-class'
|
|
47
52
|
import brandClasses from './app-brand.module.css'
|
|
48
53
|
import headerClasses from './app-header.module.css'
|
|
49
54
|
import mainClasses from './app-main.module.css'
|
|
@@ -80,6 +85,7 @@ export { AppBreadcrumbs, type AppBreadcrumbsProps } from './app-breadcrumbs'
|
|
|
80
85
|
export { PageBar, type PageBarProps, type PageBarSlot } from './page-bar'
|
|
81
86
|
export { PageAside, type PageAsideProps, type PageAsideSlot } from './page-aside'
|
|
82
87
|
export { useBreakpoint, type BreakpointName } from './use-breakpoint'
|
|
88
|
+
export { useSizeClass, type SizeClass } from './use-size-class'
|
|
83
89
|
export { PageTitle, type PageTitleProps, type PageTitleSlot } from './page-title'
|
|
84
90
|
|
|
85
91
|
/**
|
|
@@ -268,7 +274,9 @@ function findActiveWithParent(
|
|
|
268
274
|
const COLLAPSE_VERSION = 1
|
|
269
275
|
|
|
270
276
|
/**
|
|
271
|
-
* One `
|
|
277
|
+
* One `createPersistedStore` store per `storageKey`, memoized at module scope. Its third tuple
|
|
278
|
+
* element, `isSet`, is what lets an unset key take the size-class default while an explicit
|
|
279
|
+
* write of `false` still pins the navbar open.
|
|
272
280
|
*
|
|
273
281
|
* Through 1.20.0 the shell persisted collapse with `@mantine/hooks`' `useLocalStorage` while
|
|
274
282
|
* `createPersistedState` was the documented house API — and this component's own docstring told
|
|
@@ -276,17 +284,20 @@ const COLLAPSE_VERSION = 1
|
|
|
276
284
|
* `localStorage` call was COMPLIANCE with the shipped component rather than drift. A framework
|
|
277
285
|
* cannot ship a persistence rule its own shell breaks; this is the shell coming into line.
|
|
278
286
|
*
|
|
279
|
-
* The memo is required, not an optimization: `
|
|
287
|
+
* The memo is required, not an optimization: `createPersistedStore` is a per-key module FACTORY and
|
|
280
288
|
* `storageKey` is a runtime prop, so calling it during render would allocate a fresh store (and a
|
|
281
289
|
* fresh `useSyncExternalStore` subscription) on every commit. Swapping keys mid-life stays safe —
|
|
282
|
-
* every store's hook calls
|
|
290
|
+
* every store's hook calls the same two `useSyncExternalStore`s (value, `isSet`), so the hook count
|
|
291
|
+
* never moves.
|
|
283
292
|
*/
|
|
284
|
-
|
|
293
|
+
type CollapseStore = () => readonly [boolean, (next: boolean) => void, boolean]
|
|
285
294
|
|
|
286
|
-
|
|
295
|
+
const collapseStores = new Map<string, CollapseStore>()
|
|
296
|
+
|
|
297
|
+
function collapseStore(key: string): CollapseStore {
|
|
287
298
|
const cached = collapseStores.get(key)
|
|
288
299
|
if (cached) return cached
|
|
289
|
-
const store =
|
|
300
|
+
const store = createPersistedStore<boolean>({ key, version: COLLAPSE_VERSION, initial: false })
|
|
290
301
|
collapseStores.set(key, store)
|
|
291
302
|
return store
|
|
292
303
|
}
|
|
@@ -355,11 +366,46 @@ function ShellFrame({
|
|
|
355
366
|
// Mantine's own scale, which is where the 13px came from.
|
|
356
367
|
const inset = { base: step.appShellInsetMobile, sm: step.appShellInset }
|
|
357
368
|
const aside = useAsideRegion()
|
|
358
|
-
const
|
|
369
|
+
const sizeClass = useSizeClass()
|
|
370
|
+
const [storedCollapsed, setStoredCollapsed, collapseIsSet] = collapseStore(storageKey)()
|
|
359
371
|
// Controlled/uncontrolled seam (item 19): an explicit `collapsed` prop overrides the internal
|
|
360
|
-
// localStorage-persisted state entirely — the consumer becomes the source of truth.
|
|
372
|
+
// localStorage-persisted state entirely — the consumer becomes the source of truth. Otherwise the
|
|
373
|
+
// size class picks the default (rail in `medium`) ONLY while the user has never toggled; an
|
|
374
|
+
// explicit write — `false` included — always wins.
|
|
361
375
|
const isCollapseControlled = collapsedProp !== undefined
|
|
362
|
-
const
|
|
376
|
+
const uncontrolledCollapsed = collapseIsSet ? storedCollapsed : sizeClass === 'medium'
|
|
377
|
+
const collapsed = isCollapseControlled ? collapsedProp : uncontrolledCollapsed
|
|
378
|
+
// Aside docking (`aside-docking.ts`), provided through `AsideDocksContext` in the SAME render so
|
|
379
|
+
// `PageAside` defaults its fold from the right value on its first pass.
|
|
380
|
+
const navbarWidth = collapsed ? step.appShellNavbarRailWidth : step.appShellNavbarWidth
|
|
381
|
+
const sizeClassHint = useContext(SizeClassHintContext)
|
|
382
|
+
const roomToDock = useMediaQuery(
|
|
383
|
+
asideDockQuery({ navbarWidth, asideWidth: step.appShellAsideWidth }),
|
|
384
|
+
roomToDockServerFallback(sizeClassHint),
|
|
385
|
+
)
|
|
386
|
+
const docking = resolveAsideDocking({
|
|
387
|
+
claimed: aside.claimed,
|
|
388
|
+
folded: aside.folded,
|
|
389
|
+
sizeClass,
|
|
390
|
+
roomToDock,
|
|
391
|
+
asideWidth: step.appShellAsideWidth,
|
|
392
|
+
asideRailWidth: step.appShellAsideRailWidth,
|
|
393
|
+
})
|
|
394
|
+
const asideOverlay = docking.overlay
|
|
395
|
+
// Keyboard escape hatch for the overlay form only — the docked form never blocks content, so
|
|
396
|
+
// there is nothing to escape out of. `requestClose` is a stable callback from `AsideProvider`
|
|
397
|
+
// (destructured, not read as `aside.requestClose`, so the effect deps name it directly rather
|
|
398
|
+
// than the whole `aside` object — a fresh literal every render); `PageAside` folds itself in
|
|
399
|
+
// response (see its own `closeSignal` effect).
|
|
400
|
+
const { requestClose } = aside
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (!asideOverlay) return
|
|
403
|
+
const onKeyDown = (event: KeyboardEvent): void => {
|
|
404
|
+
if (event.key === 'Escape') requestClose()
|
|
405
|
+
}
|
|
406
|
+
window.addEventListener('keydown', onKeyDown)
|
|
407
|
+
return () => window.removeEventListener('keydown', onKeyDown)
|
|
408
|
+
}, [asideOverlay, requestClose])
|
|
363
409
|
const toggleCollapse = useCallback(() => {
|
|
364
410
|
const next = !collapsed
|
|
365
411
|
if (!isCollapseControlled) setStoredCollapsed(next)
|
|
@@ -392,126 +438,129 @@ function ShellFrame({
|
|
|
392
438
|
)
|
|
393
439
|
|
|
394
440
|
return (
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
base: step.appShellNavbarWidth,
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
<div className={headerClasses.lead}>
|
|
454
|
-
<AppBreadcrumbs {...activeCrumb} />
|
|
441
|
+
<AsideDocksContext.Provider value={docking.docks}>
|
|
442
|
+
<AppShell
|
|
443
|
+
h="100dvh"
|
|
444
|
+
// Mantine's DEFAULT layout, not `alt`: the header spans the full viewport width on top and
|
|
445
|
+
// the navbar/aside start underneath it at `top: var(--app-shell-header-offset)`. `alt` did
|
|
446
|
+
// the opposite — a full-height navbar with the header inset beside it — which put the top
|
|
447
|
+
// bar to the RIGHT of the sidebar instead of above it.
|
|
448
|
+
header={{
|
|
449
|
+
// ONE height at every width (law C14): nothing reserves a second mobile row any more.
|
|
450
|
+
height: step.appShellHeaderHeight,
|
|
451
|
+
}}
|
|
452
|
+
navbar={{
|
|
453
|
+
width: { base: step.appShellNavbarWidth, sm: navbarWidth },
|
|
454
|
+
breakpoint: 'sm',
|
|
455
|
+
// No mobile sidebar drawer, ever — the bottom bar is the entire mobile nav.
|
|
456
|
+
collapsed: { mobile: true },
|
|
457
|
+
}}
|
|
458
|
+
// The aside region (`docs/ASIDE-SPEC.md` §0). It is DECLARED here on every route and costs
|
|
459
|
+
// nothing until a page mounts a `PageAside` to claim it: unclaimed it is zero-wide and
|
|
460
|
+
// `collapsed.desktop`, so `--app-shell-aside-offset` stays 0 and the main column is
|
|
461
|
+
// full-width (law C14 — an empty home renders nothing). There is no `BasaltShellProps`
|
|
462
|
+
// prop for it on purpose; the ROUTE decides, the same way it decides its page bar.
|
|
463
|
+
aside={{
|
|
464
|
+
// An overlay reserves the rail only — `<AppShell.Aside>` below widens itself over main.
|
|
465
|
+
width: docking.width,
|
|
466
|
+
breakpoint: 'sm',
|
|
467
|
+
// Below `sm` there is no region at all — `PageAside` renders its content in the page
|
|
468
|
+
// flow instead, one node, no responsive twin (law C9).
|
|
469
|
+
collapsed: { desktop: !aside.claimed, mobile: true },
|
|
470
|
+
}}
|
|
471
|
+
// A plain number, NOT a `calc(... + env(safe-area-inset-bottom))` string: Mantine's own
|
|
472
|
+
// `.footer` rule already adds the inset to both the height and the padding (§2.7).
|
|
473
|
+
footer={{ height: { base: step.mobileNavBarHeight, sm: 0 } }}
|
|
474
|
+
padding={inset}
|
|
475
|
+
// `mainClasses.shell` is not decoration — see its rule for why the root has to establish a
|
|
476
|
+
// block formatting context now that Main is offset with margins.
|
|
477
|
+
className={cx(mainClasses.shell, className)}
|
|
478
|
+
{...(style !== undefined && { style })}
|
|
479
|
+
>
|
|
480
|
+
{/* Region seams (docs/DESIGN-SPEC.md §5, §8 #12): Mantine's `[data-with-border]` painted in
|
|
481
|
+
* `--vx-divider` by the theme's `AppShell.extend({ vars })`. No shell module draws a region
|
|
482
|
+
* edge; never opt a section back out of its border here. */}
|
|
483
|
+
<AppShell.Header px={inset}>
|
|
484
|
+
<div className={headerClasses.bar}>
|
|
485
|
+
{/* The header's LEADING ZONE — exactly `--app-shell-navbar-offset` wide, so its trailing
|
|
486
|
+
* edge lands on the sidebar|main seam and the breadcrumb after it starts on Main's own
|
|
487
|
+
* content edge. The brand lives here rather than in the sidebar because with a full-width
|
|
488
|
+
* header a sidebar brand row painted as a SECOND 48px band under the header seam; see
|
|
489
|
+
* `app-brand.tsx`. `data-collapsed` is the rail's one signal — the zone narrows with the
|
|
490
|
+
* navbar and shows the toggle alone, exactly as the sidebar's own row did. */}
|
|
491
|
+
<div className={brandClasses.zone} data-collapsed={collapsed || undefined}>
|
|
492
|
+
<AppBrand brand={brand} collapsed={collapsed} onToggleCollapse={toggleCollapse} />
|
|
493
|
+
</div>
|
|
494
|
+
<div className={headerClasses.lead}>
|
|
495
|
+
<AppBreadcrumbs {...activeCrumb} />
|
|
496
|
+
</div>
|
|
497
|
+
<PageBarOutlet className={headerClasses.pageBar} />
|
|
498
|
+
<HeaderGlobalActions actions={globalActions ?? []} />
|
|
455
499
|
</div>
|
|
456
|
-
|
|
457
|
-
<HeaderGlobalActions actions={globalActions ?? []} />
|
|
458
|
-
</div>
|
|
459
|
-
</AppShell.Header>
|
|
500
|
+
</AppShell.Header>
|
|
460
501
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
502
|
+
<AppShell.Navbar p={0}>
|
|
503
|
+
<AppSidebar
|
|
504
|
+
brand={brand}
|
|
505
|
+
sections={sections}
|
|
506
|
+
collapsed={collapsed}
|
|
507
|
+
onToggleCollapse={toggleCollapse}
|
|
508
|
+
{...(sidebarBlocks !== undefined && { blocks: sidebarBlocks })}
|
|
509
|
+
{...(settingsMenuItems !== undefined && { settingsMenuItems })}
|
|
510
|
+
{...(settingsMenu !== undefined && { settingsMenu })}
|
|
511
|
+
{...(account !== undefined && { account })}
|
|
512
|
+
{...(search !== undefined && { search })}
|
|
513
|
+
/>
|
|
514
|
+
</AppShell.Navbar>
|
|
474
515
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
516
|
+
{/* The PAGE-BAR BAND — a shell-owned outlet between the header and the scrollport that
|
|
517
|
+
* `PageBar` row 2 portals into, the same mechanism row 1 uses one region up. It is written
|
|
518
|
+
* BEFORE Main because the two are the AppShell root's only in-flow children (every other
|
|
519
|
+
* region is `position: fixed`), so DOM order is column order: band, then whatever height is
|
|
520
|
+
* left. Empty, it is a zero-height, seam-less box (law C14). */}
|
|
521
|
+
<PageBarBandOutlet className={mainClasses.band} />
|
|
481
522
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
523
|
+
{/* MAIN IS THE SCROLLPORT (`app-main.module.css`), not the document: the scrollbar belongs
|
|
524
|
+
* on the content's own right edge, inside the aside, and every sticky offset in the package
|
|
525
|
+
* resolves against this box rather than the window. `data-basalt-scrollport` is the public
|
|
526
|
+
* handle — `scrollParentOf` (`../common`) and `MobileNav`'s scroll-to-top both resolve it —
|
|
527
|
+
* and `data-scroll-restoration-id` is the attribute TanStack Router's scroll restoration
|
|
528
|
+
* reads (`@tanstack/router-core`'s `scrollRestorationIdAttribute`), so a router-driven app
|
|
529
|
+
* restores THIS element's scrollTop instead of the window's, which no longer moves. */}
|
|
530
|
+
<AppShell.Main
|
|
531
|
+
className={mainClasses.main}
|
|
532
|
+
data-basalt-scrollport
|
|
533
|
+
data-scroll-restoration-id="basalt-main"
|
|
534
|
+
>
|
|
535
|
+
{children}
|
|
536
|
+
</AppShell.Main>
|
|
496
537
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
538
|
+
{/* The outlet is bare; the region's leading seam is the AppShell's own. An unclaimed region
|
|
539
|
+
* is zero-wide but NOT display-none — Mantine's collapsed aside keeps its border-box, so a
|
|
540
|
+
* seam would still paint as a 1px ghost at the viewport's right edge (measured on
|
|
541
|
+
* `/dashboard`). The border follows the claim, not the section (C14). */}
|
|
542
|
+
<AppShell.Aside
|
|
543
|
+
p={0}
|
|
544
|
+
withBorder={aside.claimed}
|
|
545
|
+
{...(asideOverlay && {
|
|
546
|
+
className: mainClasses.asideOverlay,
|
|
547
|
+
style: { '--app-shell-aside-width': rem(step.appShellAsideWidth) } as CSSProperties,
|
|
548
|
+
})}
|
|
549
|
+
>
|
|
550
|
+
<AsideOutlet className={asideClasses.outlet} />
|
|
551
|
+
</AppShell.Aside>
|
|
504
552
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
553
|
+
<AppShell.Footer hiddenFrom="sm" p={0}>
|
|
554
|
+
<MobileNav
|
|
555
|
+
model={model}
|
|
556
|
+
config={mobileNav}
|
|
557
|
+
{...(account !== undefined && { account })}
|
|
558
|
+
{...(settingsMenuItems !== undefined && { settingsMenuItems })}
|
|
559
|
+
{...(sidebarBlocks !== undefined && { blocks: sidebarBlocks })}
|
|
560
|
+
/>
|
|
561
|
+
</AppShell.Footer>
|
|
562
|
+
</AppShell>
|
|
563
|
+
</AsideDocksContext.Provider>
|
|
515
564
|
)
|
|
516
565
|
}
|
|
517
566
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* padding), so the 560px cap uses 73% of it; on the smallest supported viewport (320x568) the
|
|
20
20
|
* headroom is ~496px and 72dvh is 409px — the cap is binding on every supported phone, and the row
|
|
21
21
|
* count never is. The old arithmetic here ("6 x 44px + 8px padding against 415px of headroom") was
|
|
22
|
-
* stale in almost every number: rows are
|
|
22
|
+
* stale in almost every number: rows are 44px (`--vx-space-mobile-nav-row-height`), the dropdown's
|
|
23
23
|
* own padding is `2 * --vx-space-stack-xs` = 8px, and the headroom figures above are the measured
|
|
24
24
|
* ones.
|
|
25
25
|
*
|