basalt-ui 1.18.0 → 1.19.1
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 +14 -0
- package/agent/rules/basalt-mantine.md +71 -6
- package/agent/rules/basalt-router.md +129 -30
- package/agent/rules/basalt-tokens.md +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/nav/types.d.ts +110 -0
- package/dist/nav/types.d.ts.map +1 -0
- package/dist/nav/types.js +0 -0
- package/dist/router-tanstack/index.d.ts +9 -2
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/index.js +7 -0
- package/dist/router-tanstack/nav.d.ts +163 -0
- package/dist/router-tanstack/nav.d.ts.map +1 -0
- package/dist/router-tanstack/nav.js +38 -0
- package/dist/router-tanstack/nav.test.d.ts +2 -0
- package/dist/router-tanstack/nav.test.d.ts.map +1 -0
- package/dist/router-tanstack/search-param-store.d.ts +19 -9
- package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.d.ts +30 -0
- package/dist/router-tanstack/use-nav.d.ts.map +1 -0
- package/dist/router-tanstack/use-nav.js +71 -0
- package/dist/shell/app-breadcrumbs.d.ts +7 -9
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.js +8 -4
- package/dist/shell/app-mobile-nav.d.ts +23 -24
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +291 -69
- package/dist/shell/app-mobile-nav.module.css +191 -11
- package/dist/shell/app-mobile-nav.module.css.d.ts +11 -2
- package/dist/shell/app-mobile-nav.test.d.ts +2 -0
- package/dist/shell/app-mobile-nav.test.d.ts.map +1 -0
- package/dist/shell/app-sidebar.d.ts +3 -28
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +40 -174
- package/dist/shell/app-sidebar.module.css +3 -2
- package/dist/shell/app-sidebar.module.css.d.ts +0 -2
- package/dist/shell/index.d.ts +22 -65
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +38 -76
- package/dist/shell/mobile-nav-model.d.ts +41 -0
- package/dist/shell/mobile-nav-model.d.ts.map +1 -0
- package/dist/shell/mobile-nav-model.js +244 -0
- package/dist/shell/mobile-nav-model.test.d.ts +2 -0
- package/dist/shell/mobile-nav-model.test.d.ts.map +1 -0
- package/dist/shell/nav-count-badge.d.ts +11 -0
- package/dist/shell/nav-count-badge.d.ts.map +1 -0
- package/dist/shell/nav-count-badge.js +29 -0
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +8 -7
- package/dist/theme/nav-link.module.css +4 -2
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/palette.d.ts +15 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +13 -0
- package/dist/tokens.css +2 -0
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/index.ts +15 -5
- package/src/nav/types.ts +115 -0
- package/src/router-tanstack/index.ts +21 -2
- package/src/router-tanstack/nav.test.ts +111 -0
- package/src/router-tanstack/nav.ts +205 -0
- package/src/router-tanstack/search-param-store.ts +19 -9
- package/src/router-tanstack/use-nav.ts +146 -0
- package/src/shell/app-breadcrumbs.tsx +21 -19
- package/src/shell/app-mobile-nav.module.css +191 -11
- package/src/shell/app-mobile-nav.module.css.d.ts +11 -2
- package/src/shell/app-mobile-nav.test.tsx +403 -0
- package/src/shell/app-mobile-nav.tsx +449 -104
- package/src/shell/app-sidebar.module.css +3 -2
- package/src/shell/app-sidebar.module.css.d.ts +0 -2
- package/src/shell/app-sidebar.test.tsx +6 -10
- package/src/shell/app-sidebar.tsx +49 -206
- package/src/shell/index.test.tsx +62 -7
- package/src/shell/index.tsx +105 -164
- package/src/shell/mobile-nav-model.test.ts +296 -0
- package/src/shell/mobile-nav-model.ts +389 -0
- package/src/shell/nav-count-badge.tsx +39 -0
- package/src/surfaces.ts +1 -1
- package/src/theme/density-relations.test.ts +19 -1
- package/src/theme/index.ts +8 -7
- package/src/theme/nav-link.module.css +4 -2
- package/src/theme/spacing.test.ts +11 -0
- package/src/tokens/density.test.ts +2 -0
- package/src/tokens/index.ts +8 -5
- package/src/tokens/palette.ts +16 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `NavCountBadge` — the sidebar/menu unread-count chip. Its own module rather than a member of the
|
|
3
|
+
* shell barrel so `app-mobile-nav.tsx` and `app-sidebar.tsx` can render it without importing the
|
|
4
|
+
* barrel that imports THEM (an ESM value cycle). `shell/index.tsx` re-exports it, so the published
|
|
5
|
+
* surface is unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import { Badge } from '@mantine/core'
|
|
8
|
+
import { VX } from '../tokens'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sidebar nav count badge (docs/DESIGN-SPEC.md §5): mono 10.5px, ink-8% bg, radius 5, height 16,
|
|
12
|
+
* padding 0 5px, muted text; `marginLeft: auto` pins it to the row end on any render path.
|
|
13
|
+
* `styles` (inline) rather than a token color prop, since none of Mantine's variant/color
|
|
14
|
+
* combinations land on the ink-tint idiom. Returns `null` for a zero/empty count so the badge slot
|
|
15
|
+
* stays clean ("ink earns its color", DESIGN.md).
|
|
16
|
+
*/
|
|
17
|
+
export function NavCountBadge({ count }: { count: number }) {
|
|
18
|
+
if (!count) return null
|
|
19
|
+
return (
|
|
20
|
+
<Badge
|
|
21
|
+
size="sm"
|
|
22
|
+
styles={{
|
|
23
|
+
root: {
|
|
24
|
+
backgroundColor: 'color-mix(in srgb, var(--vx-ink) 8%, transparent)',
|
|
25
|
+
color: 'var(--vx-muted)',
|
|
26
|
+
fontFamily: 'var(--basalt-font-mono)',
|
|
27
|
+
fontSize: VX.text.micro,
|
|
28
|
+
fontWeight: 500,
|
|
29
|
+
height: 16,
|
|
30
|
+
padding: '0 5px',
|
|
31
|
+
marginLeft: 'auto',
|
|
32
|
+
borderRadius: 'var(--vx-radius-tight)',
|
|
33
|
+
},
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{count}
|
|
37
|
+
</Badge>
|
|
38
|
+
)
|
|
39
|
+
}
|
package/src/surfaces.ts
CHANGED
|
@@ -288,7 +288,7 @@ export const SURFACES = {
|
|
|
288
288
|
skill: ['basalt-app'],
|
|
289
289
|
guardKinds: [],
|
|
290
290
|
description:
|
|
291
|
-
'TanStack Router bridge: useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)',
|
|
291
|
+
'TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)',
|
|
292
292
|
optionalPeers: ['@tanstack/react-router'],
|
|
293
293
|
globs: {
|
|
294
294
|
shipped: [],
|
|
@@ -161,12 +161,28 @@ describe('Fix 4 — every interactive target clears the WCAG 2.5.8 24px floor at
|
|
|
161
161
|
}
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
-
test('the mobile header actions row (appHeaderMobileActionsHeight
|
|
164
|
+
test('the mobile header actions row (appHeaderMobileActionsHeight)', () => {
|
|
165
165
|
for (const level of ALL_LEVELS) {
|
|
166
166
|
expect(deriveSpacing(level).step.appHeaderMobileActionsHeight).toBeGreaterThanOrEqual(24)
|
|
167
167
|
}
|
|
168
168
|
})
|
|
169
169
|
|
|
170
|
+
// The mobile bar's own two targets clear a HIGHER bar than WCAG 2.5.8's 24px: a thumb on a phone
|
|
171
|
+
// is not a mouse pointer, so `deriveSpacing` floors them at Apple HIG 44pt / WCAG 2.5.5 AAA
|
|
172
|
+
// instead. Without those floors the 1 + 0.1*level multiplier takes 56 -> 39 and 44 -> 31 at level
|
|
173
|
+
// -3 — both under even the 24px floor the rest of this block asserts.
|
|
174
|
+
test('the mobile nav bar height never drops below its own 48px floor', () => {
|
|
175
|
+
for (const level of ALL_LEVELS) {
|
|
176
|
+
expect(deriveSpacing(level).step.mobileNavBarHeight).toBeGreaterThanOrEqual(48)
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('a mobile nav menu/sheet row never drops below its own 44px floor', () => {
|
|
181
|
+
for (const level of ALL_LEVELS) {
|
|
182
|
+
expect(deriveSpacing(level).step.mobileNavRowHeight).toBeGreaterThanOrEqual(44)
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
170
186
|
test('the resolved control/input height (size="md" Button/ActionIcon/Input) clears the floor too', () => {
|
|
171
187
|
for (const level of ALL_LEVELS) {
|
|
172
188
|
const { anchors } = deriveSpacing(level)
|
|
@@ -212,6 +228,8 @@ function oldLawFlatten(level: number): Record<string, number> {
|
|
|
212
228
|
step[k] = scaleSpace(v, level, multiplier)
|
|
213
229
|
}
|
|
214
230
|
step['sidebarSearchTriggerHeight'] = Math.max(24, step['sidebarSearchTriggerHeight']!)
|
|
231
|
+
step['mobileNavBarHeight'] = Math.max(48, step['mobileNavBarHeight']!)
|
|
232
|
+
step['mobileNavRowHeight'] = Math.max(44, step['mobileNavRowHeight']!)
|
|
215
233
|
step['stickyHeaderClearance'] = step['appShellHeaderHeight']! + anchors['stackMd']!
|
|
216
234
|
step['stickyHeaderClearanceMobile'] = step['appShellHeaderMobileHeight']! + anchors['stackMd']!
|
|
217
235
|
return { ...anchors, ...scale, ...step }
|
package/src/theme/index.ts
CHANGED
|
@@ -549,10 +549,10 @@ function buildTheme(data: PaletteData, options: BuildThemeOptions = {}): Mantine
|
|
|
549
549
|
// panel-filled row read as a button sitting in the nav rather than as the current location.
|
|
550
550
|
// Selection is carried by the tint + ink text + weight 600 + the accent icon instead. Forced
|
|
551
551
|
// here at the THEME level via NavLink's `--nl-*` vars so it holds for every render path —
|
|
552
|
-
// including a consumer's router `<Link>`
|
|
553
|
-
// module; the active ICON is accent-colored via `nav-link.module.css`
|
|
554
|
-
// `[data-position='left']` leftSection, which the flat `vars`/`styles` API
|
|
555
|
-
// conditionally on `[data-active]`).
|
|
552
|
+
// including a consumer's router `<Link>` hosted through `SidebarItem.Anchor`, which never
|
|
553
|
+
// sees the shell CSS module; the active ICON is accent-colored via `nav-link.module.css`
|
|
554
|
+
// (targets the `[data-position='left']` leftSection, which the flat `vars`/`styles` API
|
|
555
|
+
// can't express conditionally on `[data-active]`).
|
|
556
556
|
//
|
|
557
557
|
// `--nl-hover` (ink-13%) only ever reaches an ACTIVE row in practice — the inactive hover is
|
|
558
558
|
// set at ink-6% by higher-specificity `:not([data-active])` rules in `nav-link.module.css` /
|
|
@@ -568,9 +568,10 @@ function buildTheme(data: PaletteData, options: BuildThemeOptions = {}): Mantine
|
|
|
568
568
|
children: {},
|
|
569
569
|
}),
|
|
570
570
|
// Dense, rounded nav rows — applied at the THEME level so they survive EVERY render path,
|
|
571
|
-
// including a consumer's router `<Link>`
|
|
572
|
-
// module (the module's `.link` only reaches the shell's own fallback
|
|
573
|
-
// reasoning as the `--nl-*` fill above: layout is single-sourced here,
|
|
571
|
+
// including a consumer's router `<Link>` hosted through `SidebarItem.Anchor`, which never
|
|
572
|
+
// sees the shell CSS module (the module's `.link` only reaches the shell's own fallback
|
|
573
|
+
// `<NavLink>`). Same reasoning as the `--nl-*` fill above: layout is single-sourced here,
|
|
574
|
+
// not in two places.
|
|
574
575
|
// The active-weight/shadow/icon-accent state selectors can't live in `styles` (flat inline
|
|
575
576
|
// props only) — they're in nav-link.module.css, wired via `classNames` so they reach the
|
|
576
577
|
// same every-render-path scope.
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* tint rather than as a button, so the row never competes with the real filled controls around it.
|
|
6
6
|
*
|
|
7
7
|
* Applied at the THEME level via `classNames` so it reaches EVERY NavLink render path — including a
|
|
8
|
-
* consumer's router `<Link>` mounted through `
|
|
8
|
+
* consumer's router `<Link>` mounted through `SidebarItem.Anchor`, which a shell-scoped CSS
|
|
9
|
+
* module never
|
|
9
10
|
* sees. Mantine's `styles` prop only takes flat inline properties (a nested `&[data-active]` there
|
|
10
11
|
* throws a React warning), so the state selectors live here. React Router's NavLink marks the
|
|
11
12
|
* active item via `aria-current` rather than the `active` prop's `data-active`, so both states are
|
|
@@ -42,7 +43,8 @@
|
|
|
42
43
|
|
|
43
44
|
/*
|
|
44
45
|
* Inactive hover — the ink-6% ghost tint + ink text/icon (docs/DESIGN-SPEC.md §5). Lives here (not
|
|
45
|
-
* only in the shell module) so a consumer's `
|
|
46
|
+
* only in the shell module) so a consumer's `SidebarItem.Anchor` path gets the same hover;
|
|
47
|
+
* Mantine's own
|
|
46
48
|
* hover fill uses `:where(scheme) .m_*:hover` (gray-0/dark-6), which this outranks on specificity.
|
|
47
49
|
* Active rows keep their panel fill + `--nl-hover` untouched.
|
|
48
50
|
*/
|
|
@@ -352,6 +352,13 @@ const SPACE_STEP_SWEEP: ReadonlyArray<
|
|
|
352
352
|
['appShellNavbarWidth', 216, null],
|
|
353
353
|
['appShellNavbarRailWidth', 48, null],
|
|
354
354
|
['mobileNavTabGap', 3, 'space-mobile-nav-tab-gap'],
|
|
355
|
+
// JS-number-only, and for the same two reasons the six above are: `shell/index.tsx` hands
|
|
356
|
+
// `mobileNavBarHeight` to AppShell's `footer.height` (a number, not a `var()` string), and
|
|
357
|
+
// `app-mobile-nav.tsx` hands `mobileNavMenuWidth` to `<Menu width={…}>`.
|
|
358
|
+
['mobileNavBarHeight', 56, null],
|
|
359
|
+
['mobileNavIconSize', 24, 'space-mobile-nav-icon-size'],
|
|
360
|
+
['mobileNavRowHeight', 44, 'space-mobile-nav-row-height'],
|
|
361
|
+
['mobileNavMenuWidth', 232, null],
|
|
355
362
|
['agentRailInsetX', 10, 'space-agent-rail-inset-x'],
|
|
356
363
|
['agentPartGapTop', 6, 'space-agent-part-gap-top'],
|
|
357
364
|
['agentCodeInset', 8, 'space-agent-code-inset'],
|
|
@@ -404,6 +411,8 @@ describe('SPACE_STEP CSS-module spacing-sweep one-offs match the shipped identit
|
|
|
404
411
|
'appShellHeaderMobileHeight',
|
|
405
412
|
'appShellNavbarWidth',
|
|
406
413
|
'appShellNavbarRailWidth',
|
|
414
|
+
'mobileNavBarHeight',
|
|
415
|
+
'mobileNavMenuWidth',
|
|
407
416
|
'chartLegendGap',
|
|
408
417
|
'chartMarginTop',
|
|
409
418
|
'chartMarginRight',
|
|
@@ -420,6 +429,8 @@ describe('SPACE_STEP CSS-module spacing-sweep one-offs match the shipped identit
|
|
|
420
429
|
expect(css).not.toContain('--vx-space-app-shell-header-mobile-height')
|
|
421
430
|
expect(css).not.toContain('--vx-space-app-shell-navbar-width')
|
|
422
431
|
expect(css).not.toContain('--vx-space-app-shell-navbar-rail-width')
|
|
432
|
+
expect(css).not.toContain('--vx-space-mobile-nav-bar-height')
|
|
433
|
+
expect(css).not.toContain('--vx-space-mobile-nav-menu-width')
|
|
423
434
|
expect(css).not.toContain('--vx-space-chart-legend-gap')
|
|
424
435
|
expect(css).not.toContain('--vx-space-progress-bar-size')
|
|
425
436
|
expect(css).not.toContain('--vx-space-timeline-bullet')
|
|
@@ -239,6 +239,8 @@ describe('buildDensityCss', () => {
|
|
|
239
239
|
'space-app-shell-header-mobile-height',
|
|
240
240
|
'space-app-shell-navbar-width',
|
|
241
241
|
'space-app-shell-navbar-rail-width',
|
|
242
|
+
'space-mobile-nav-bar-height', // AppShell footer.height prop
|
|
243
|
+
'space-mobile-nav-menu-width', // Menu width prop
|
|
242
244
|
'space-chart-legend-gap', // VX.legendGap / VX.margin / VX.dotR — visx SVG props
|
|
243
245
|
'space-chart-margin-top',
|
|
244
246
|
'space-chart-margin-right',
|
package/src/tokens/index.ts
CHANGED
|
@@ -400,14 +400,15 @@ function frameworkPrimitives(side: Side, data: PaletteData, legacyAliases: boole
|
|
|
400
400
|
* only invite someone to think it does.
|
|
401
401
|
*
|
|
402
402
|
* Deliberately DOES NOT emit a var for every `SpaceValues` number — `space.scale.*` (the generic
|
|
403
|
-
* Mantine spacing scale) and
|
|
403
|
+
* Mantine spacing scale) and seventeen `space.step.*` keys have ZERO `var()` consumers anywhere in
|
|
404
404
|
* the framework or its tests beyond the CSS-emission test itself. Each is read straight off the
|
|
405
405
|
* resolved `SpaceValues` as a JS NUMBER instead, in one of three ways:
|
|
406
406
|
* `{timelineBullet,progressBarSize}` are Mantine `defaultProps` (`theme/index.ts`'s Timeline/
|
|
407
|
-
* Progress); `{sidebarAccountMenuWidth,sidebarSettingsMenuWidth,
|
|
408
|
-
* appShellHeaderMobileHeight,appShellNavbarWidth,appShellNavbarRailWidth
|
|
409
|
-
* component props (Menu `width`, AppShell `header
|
|
410
|
-
* chartMarginRight,
|
|
407
|
+
* Progress); `{sidebarAccountMenuWidth,sidebarSettingsMenuWidth,mobileNavMenuWidth,
|
|
408
|
+
* appShellHeaderHeight,appShellHeaderMobileHeight,appShellNavbarWidth,appShellNavbarRailWidth,
|
|
409
|
+
* mobileNavBarHeight}` are numeric Mantine component props (Menu `width`, AppShell `header`/
|
|
410
|
+
* `navbar`/`footer`); and `{chartLegendGap,chartMarginTop,chartMarginRight,chartMarginRightAxis,
|
|
411
|
+
* chartMarginBottom,chartMarginLeft,chartDotR}` are visx SVG props via
|
|
411
412
|
* `VX.legendGap`/`VX.margin`/`VX.dotR` in this file's own `VX` object. The full list is locked
|
|
412
413
|
* against the `SpaceValues` key set by `tokens/density.test.ts`'s declaration-set test, so adding a
|
|
413
414
|
* one-off silently omits nothing. A `--vx-space-*` declaration for any of them would be dead weight
|
|
@@ -531,6 +532,8 @@ function spaceDecls(space: SpaceValues): string[] {
|
|
|
531
532
|
decl('space-sidebar-child-row-indent', `${space.step.sidebarChildRowIndent}px`),
|
|
532
533
|
decl('space-app-header-mobile-actions-height', `${space.step.appHeaderMobileActionsHeight}px`),
|
|
533
534
|
decl('space-mobile-nav-tab-gap', `${space.step.mobileNavTabGap}px`),
|
|
535
|
+
decl('space-mobile-nav-icon-size', `${space.step.mobileNavIconSize}px`),
|
|
536
|
+
decl('space-mobile-nav-row-height', `${space.step.mobileNavRowHeight}px`),
|
|
534
537
|
decl('space-agent-rail-inset-x', `${space.step.agentRailInsetX}px`),
|
|
535
538
|
decl('space-agent-part-gap-top', `${space.step.agentPartGapTop}px`),
|
|
536
539
|
decl('space-agent-code-inset', `${space.step.agentCodeInset}px`),
|
package/src/tokens/palette.ts
CHANGED
|
@@ -609,6 +609,17 @@ const SPACE_STEP_BASE = {
|
|
|
609
609
|
// ── shell/app-mobile-nav.module.css ───────────────────────────────────────────────────────────
|
|
610
610
|
/** Tab's icon-to-label gap. */
|
|
611
611
|
mobileNavTabGap: 3,
|
|
612
|
+
/** Bar height, EXCLUDING the safe-area inset — Mantine's own `AppShell.footer` rule adds
|
|
613
|
+
* `env(safe-area-inset-bottom)` to it. JS-consumed (AppShell `footer.height`), no `--vx-*` var,
|
|
614
|
+
* same shape as `appShellHeaderHeight`. Floored at 48px in `deriveSpacing`. */
|
|
615
|
+
mobileNavBarHeight: 56,
|
|
616
|
+
/** Slot icon box. */
|
|
617
|
+
mobileNavIconSize: 24,
|
|
618
|
+
/** Menu/sheet row height. Floored at 44px in `deriveSpacing` (Apple HIG 44pt / WCAG 2.5.5 AAA). */
|
|
619
|
+
mobileNavRowHeight: 44,
|
|
620
|
+
/** More/group `<Menu width={…}>`. JS-consumed, no var — same rationale as
|
|
621
|
+
* `sidebarSettingsMenuWidth` directly above. */
|
|
622
|
+
mobileNavMenuWidth: 232,
|
|
612
623
|
|
|
613
624
|
// ── charts (tokens/index.ts's `VX` object — structural, tracks density) ──────────────────────
|
|
614
625
|
// `VX.lineWidth`/`VX.line2Width` are DELIBERATELY absent here — they are stroke WEIGHTS (line
|
|
@@ -927,6 +938,11 @@ export function deriveSpacing(level: number): SpaceValues {
|
|
|
927
938
|
// WCAG 2.5.8 minimum interactive-target size — see this function's doc for why the trigger's
|
|
928
939
|
// density-tracking height alone can't be trusted to stay accessible at a negative level.
|
|
929
940
|
mappedStep.sidebarSearchTriggerHeight = Math.max(24, mappedStep.sidebarSearchTriggerHeight)
|
|
941
|
+
// Touch targets: the 1 + 0.1*level multiplier would take 56 -> 39 and 44 -> 31 at level -3,
|
|
942
|
+
// silently breaking the minimum interactive-target size. Same guardrail, same reason, as the
|
|
943
|
+
// sidebar search trigger's 24px floor directly above.
|
|
944
|
+
mappedStep.mobileNavBarHeight = Math.max(48, mappedStep.mobileNavBarHeight)
|
|
945
|
+
mappedStep.mobileNavRowHeight = Math.max(44, mappedStep.mobileNavRowHeight)
|
|
930
946
|
const step: SpaceValues['step'] = {
|
|
931
947
|
...mappedStep,
|
|
932
948
|
// RESPONSIVE, one per AppShell breakpoint — each DERIVED from its own header, plus
|