basalt-ui 1.18.0 → 1.19.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 +7 -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 +290 -68
- package/dist/shell/app-mobile-nav.module.css +166 -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 +166 -11
- package/src/shell/app-mobile-nav.module.css.d.ts +11 -2
- package/src/shell/app-mobile-nav.test.tsx +369 -0
- package/src/shell/app-mobile-nav.tsx +436 -102
- 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
package/AGENTS.md
CHANGED
|
@@ -15,7 +15,7 @@ Toolchain: Bun runtime, oxlint + oxfmt, conventional commits (empty scope), `mas
|
|
|
15
15
|
| `basalt-ui/vite` | mantine-coupled | basaltViteConfig(opts) — Vite preset for basalt-ui consumer apps; basaltAppPlugin(opts) — PWA head, manifest, and icon metadata derived from the token palette |
|
|
16
16
|
| `basalt-ui/guard` | headless | checkSource, GUARD_RULES, Finding types — the headless theme-guard core |
|
|
17
17
|
| `basalt-ui/query` | headless | createBasaltQueryClient, transport-agnostic unwrap, lazy BasaltQueryDevtools |
|
|
18
|
-
| `basalt-ui/router-tanstack` | headless | TanStack Router bridge: useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)
|
|
18
|
+
| `basalt-ui/router-tanstack` | headless | 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) |
|
|
19
19
|
| `basalt-ui/forms` | mantine-coupled | Mantine form adapter: useBasaltForm, field, FormErrorSummary, useFormDraft (Standard Schema) |
|
|
20
20
|
| `basalt-ui/notifications` | mantine-coupled | Mantine notifications: notify helpers, typed registry, persisted history, NotificationBell, NotificationCenter |
|
|
21
21
|
| `basalt-ui/commands` | mantine-coupled | typed command bus + overlay controller, toSpotlightActions, ShortcutsHelp, BasaltOverlays |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.19.0](https://github.com/jkrumm/basalt-ui/compare/v1.18.0...v1.19.0) (2026-08-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* make the mobile bar navigate, driven by one typed nav definition ([1ebba92](https://github.com/jkrumm/basalt-ui/commit/1ebba9267267ff26e988c876f17839b05d61689d))
|
|
7
|
+
|
|
1
8
|
# [1.18.0](https://github.com/jkrumm/basalt-ui/compare/v1.17.0...v1.18.0) (2026-08-19)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -60,8 +60,9 @@ Restraint is the #1 lever — neutrals carry the surface, the accent only points
|
|
|
60
60
|
reads as a button sitting in the nav rather than as the current location. Inactive = muted text,
|
|
61
61
|
faint icon, hover ink-6%; an active row hovers one step further to ink-13%. This is baked into
|
|
62
62
|
the theme's NavLink defaults (`--nl-*` vars) and holds for _every_ render path, including a
|
|
63
|
-
consumer's router `<Link>`
|
|
64
|
-
left border; active child = accent text, weight 600.
|
|
63
|
+
consumer's router `<Link>` hosted through `SidebarItem.Anchor`. Child items indent with a 1px
|
|
64
|
+
`divider` left border; active child = accent text, weight 600. The MOBILE bar is the one
|
|
65
|
+
deliberate exception to the accent-icon half of this rule — see "Mobile bar" below.
|
|
65
66
|
- **Buttons**: the primary action = filled accent (`variant="filled"`), **exactly one per view**.
|
|
66
67
|
Every other/secondary action = `variant="default"` (neutral). Do **not** use a colored
|
|
67
68
|
`variant="light"` for routine actions.
|
|
@@ -280,8 +281,8 @@ shadow-surfaces.test.ts` mechanically enumerates every site that applies either
|
|
|
280
281
|
`BasaltShell` is the canonical single-mount. When a fully custom layout is needed, the shell
|
|
281
282
|
sub-components are available individually from `basalt-ui`:
|
|
282
283
|
|
|
283
|
-
- `AppSidebar` — desktop sidebar (sections, collapse, brand,
|
|
284
|
-
- `MobileNav` — bottom
|
|
284
|
+
- `AppSidebar` — desktop sidebar (sections, collapse, brand, settings/account extras)
|
|
285
|
+
- `MobileNav` — the bottom tab bar (takes a `MobileNavModel` from `projectMobileNav`)
|
|
285
286
|
- `AppBreadcrumbs` — breadcrumb bar (reads from `useRouterBreadcrumbs` or a manual trail)
|
|
286
287
|
- `PageHeaderProvider` + `PageActions` + `PageActionsOutlet` — portal-based page action slots
|
|
287
288
|
- `NavCountBadge` — count badge for sidebar nav items
|
|
@@ -294,13 +295,14 @@ import {
|
|
|
294
295
|
PageHeaderProvider,
|
|
295
296
|
PageActionsOutlet,
|
|
296
297
|
NavCountBadge,
|
|
298
|
+
projectMobileNav,
|
|
297
299
|
} from 'basalt-ui'
|
|
298
300
|
|
|
299
301
|
function CustomShell({ children }: { children: React.ReactNode }) {
|
|
300
302
|
return (
|
|
301
303
|
<PageHeaderProvider>
|
|
302
|
-
<AppSidebar sections={sections} brand={brand}
|
|
303
|
-
<MobileNav
|
|
304
|
+
<AppSidebar sections={sections} brand={brand} />
|
|
305
|
+
<MobileNav model={projectMobileNav(sections)} />
|
|
304
306
|
<main>
|
|
305
307
|
<AppBreadcrumbs />
|
|
306
308
|
<PageActionsOutlet />
|
|
@@ -314,6 +316,69 @@ function CustomShell({ children }: { children: React.ReactNode }) {
|
|
|
314
316
|
Prefer `BasaltShell` unless the layout genuinely diverges — it already composes all of the above
|
|
315
317
|
with the correct wiring (collapse persistence, mobile breakpoint, breadcrumb integration).
|
|
316
318
|
|
|
319
|
+
## Mobile bar
|
|
320
|
+
|
|
321
|
+
The bottom bar is a **tab bar, not a menu**: a slot is a DESTINATION, so a tap navigates instantly
|
|
322
|
+
with nothing to dismiss. There is no full-height mobile sidebar drawer — everything it used to hold
|
|
323
|
+
(nav, account, settings, theme switcher) is reachable from the trailing More slot instead.
|
|
324
|
+
|
|
325
|
+
`BasaltShell` builds the whole thing from the same `SidebarSection[]` the desktop sidebar renders:
|
|
326
|
+
**`projectMobileNav`** projects those sections onto the bar and **`MobileNav`** paints the result.
|
|
327
|
+
There is no second nav model to maintain, and in the common case no configuration at all.
|
|
328
|
+
|
|
329
|
+
**Surface inference — the part you do not configure.** For any slot holding `n` rows:
|
|
330
|
+
|
|
331
|
+
| `n` | surface |
|
|
332
|
+
| ----------------------- | ------------------------------------------------------------------------- |
|
|
333
|
+
| 0 | the slot is dropped |
|
|
334
|
+
| 1 | a plain navigation link — a group of one IS a destination |
|
|
335
|
+
| ≤ `menuMax` (default 6) | a Mantine `Menu` that pops upward out of the tab, content-sized, no scrim |
|
|
336
|
+
| > `menuMax` | a bottom `Drawer` sheet with a grabber and an internal `ScrollArea` |
|
|
337
|
+
|
|
338
|
+
Six is arithmetic, not taste: 6 × 44px + 8px padding = 272px against 415px of headroom on the
|
|
339
|
+
smallest supported viewport, which is why the menu is configured `flip: false` and can never render
|
|
340
|
+
below the fold. Move a destination in or out of the More slot and the surface changes itself.
|
|
341
|
+
|
|
342
|
+
**Placement** is one field per destination — `SidebarItem`'s `mobile`: `'tab'` gives it a slot of
|
|
343
|
+
its own, `'more'` (the default) makes it an overflow row, `'hidden'` drops it from mobile entirely.
|
|
344
|
+
A whole section claims one slot with `mobile: { tab: true }` (its destinations then open per the
|
|
345
|
+
table above) or leaves mobile with `mobile: false`. Configure nothing and the first four non-disabled
|
|
346
|
+
top-level destinations take slots — a nav that works before anyone has thought about mobile.
|
|
347
|
+
|
|
348
|
+
**`MobileNavConfig`** (the `mobileNav` prop) is the escape hatch, not the interface: `tabs` for an
|
|
349
|
+
explicit slot order by key when the bar order must differ from the sidebar's, `maxTabs` (5),
|
|
350
|
+
`menuMax` (6), `moreLabel`, `moreExtra`, and `getScrollElement` for re-tap-to-top when the page
|
|
351
|
+
scrolls inside a container rather than the document.
|
|
352
|
+
|
|
353
|
+
Design rules specific to the bar, each one load-bearing:
|
|
354
|
+
|
|
355
|
+
- **The active indicator is a neutral pill behind the ICON only** — `--vx-ink` at 10% plus
|
|
356
|
+
`--vx-radius-ctrl`, the Material-3 idiom. Never the identity blue, and never a full-tab fill: a
|
|
357
|
+
filled tab reads as chrome instead of as a tab bar. This is the exception to "active nav = accent
|
|
358
|
+
icon" in the palette rules above; the desktop sidebar keeps that rule unchanged.
|
|
359
|
+
- **A count renders as an 8px accent dot** on the slot icon, not a number — a count glyph is
|
|
360
|
+
unreadable at 56px. `SidebarItem.count` drives it; `SidebarItem.badge` (a rich node) stays
|
|
361
|
+
desktop-only.
|
|
362
|
+
- **Press feedback is the icon, not the hit box**: `.tab:active` scales the icon to 0.9 over 140ms.
|
|
363
|
+
`touch-action: manipulation` + `-webkit-tap-highlight-color: transparent` kill the 300ms
|
|
364
|
+
double-tap delay and the blue flash.
|
|
365
|
+
- **Every `:hover` rule sits behind `@media (hover: hover)`.** Ungated, a tapped row stays lit until
|
|
366
|
+
something else is tapped.
|
|
367
|
+
- **Re-tapping the ACTIVE slot scrolls to top** instead of pushing a redundant history entry.
|
|
368
|
+
- **Never add `padding-bottom: env(safe-area-inset-bottom)` to the bar.** Mantine's own
|
|
369
|
+
`AppShell.Footer` rule already grows the footer box by the inset and pads its content; adding it
|
|
370
|
+
again double-counts. The one real gap is `AppShell.Main`, whose padding is computed from an offset
|
|
371
|
+
var that excludes the inset — the shell closes that itself.
|
|
372
|
+
- **Reduced motion** is read explicitly via `useReducedMotion` (Mantine's
|
|
373
|
+
`respectReducedMotion` defaults to `false` and basalt does not change it) and zeroes both the menu
|
|
374
|
+
and sheet transitions.
|
|
375
|
+
|
|
376
|
+
Accessibility, all handled by the shell: the bar is `<nav aria-label="Primary">`, the active slot
|
|
377
|
+
carries `aria-current="page"`, every slot carries `aria-label` (the visible text is the short
|
|
378
|
+
label), menus get `menuItemTabIndex={0}` for the WAI-ARIA disclosure pattern, and the row height
|
|
379
|
+
floors at 44px with the bar at 48px regardless of the `density` level. **Do not hand-set
|
|
380
|
+
`aria-haspopup` / `aria-expanded`** on a `Menu.Target` child — Mantine clones them on.
|
|
381
|
+
|
|
317
382
|
## Icons
|
|
318
383
|
|
|
319
384
|
basalt-ui takes icons as `ReactNode` (no icon dependency shipped). Use a single icon library across the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: basalt-ui
|
|
3
|
-
description: TanStack Router conventions for basalt-ui apps — file-based routes, typed search params,
|
|
3
|
+
description: TanStack Router conventions for basalt-ui apps — file-based routes, typed search params, loader/query coupling, and the one typed nav definition that drives both the desktop sidebar and the mobile bar. Advisory (ships an optional ./router-tanstack adapter).
|
|
4
4
|
paths:
|
|
5
5
|
- 'src/routes/**'
|
|
6
6
|
- 'apps/**/src/routes/**'
|
|
@@ -8,13 +8,14 @@ paths:
|
|
|
8
8
|
|
|
9
9
|
# Basalt Router — TanStack Router Conventions
|
|
10
10
|
|
|
11
|
-
basalt-ui is **router-agnostic** —
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
consistency; nothing in the framework
|
|
11
|
+
basalt-ui is **router-agnostic** — the shell renders every pixel of nav chrome and hosts the
|
|
12
|
+
consumer's router component through one seam, `SidebarItem.Anchor` (a **`NavAnchor`**). With
|
|
13
|
+
TanStack Router you never write that seam by hand: **`defineNav`** declares the whole navigation
|
|
14
|
+
once and **`useNav`** resolves it against the live router, returning the two props `BasaltShell`
|
|
15
|
+
needs. Breadcrumbs come from **`useRouterBreadcrumbs`**, ad-hoc active checks from
|
|
16
|
+
**`useBasaltNav`**. This rule is the recommended opinion layer when you use TanStack Router (the
|
|
17
|
+
basalt-ui default). It is **advisory** — adopt it for consistency; nothing in the framework
|
|
18
|
+
enforces it.
|
|
18
19
|
|
|
19
20
|
## Adding a page
|
|
20
21
|
|
|
@@ -23,7 +24,7 @@ consistency; nothing in the framework enforces it.
|
|
|
23
24
|
3. Add `validateSearch` with a Zod schema parsed via a function — not the schema passed directly.
|
|
24
25
|
4. Define `loaderDeps` to forward search params to the loader (required for search-param-driven queries).
|
|
25
26
|
5. Add a `loader` that calls `ensureQueryData` for each query the page needs.
|
|
26
|
-
6. Register the page in your `
|
|
27
|
+
6. Register the page in your `defineNav` definition (see "One typed nav definition" below).
|
|
27
28
|
|
|
28
29
|
## Route structure
|
|
29
30
|
|
|
@@ -146,28 +147,126 @@ function AppBreadcrumbs() {
|
|
|
146
147
|
}
|
|
147
148
|
```
|
|
148
149
|
|
|
149
|
-
**
|
|
150
|
-
|
|
150
|
+
**One typed nav definition** — `defineNav` / `navGroup` / `navTarget` / `useNav`.
|
|
151
|
+
|
|
152
|
+
The whole navigation — desktop sidebar and mobile bar — is declared once in a **leaf** module
|
|
153
|
+
(`src/lib/nav.tsx`), which imports `@tanstack/react-router` and `basalt-ui/router-tanstack` and
|
|
154
|
+
**never** `routeTree.gen` or `__root.tsx`, so command palettes and redirects can import it without
|
|
155
|
+
closing a cycle.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { linkOptions } from '@tanstack/react-router'
|
|
159
|
+
import { defineNav, navGroup } from 'basalt-ui/router-tanstack'
|
|
160
|
+
|
|
161
|
+
const ICON = 18
|
|
162
|
+
|
|
163
|
+
export const NAV = defineNav({
|
|
164
|
+
groups: [
|
|
165
|
+
navGroup({ id: 'health', label: 'Health', icon: <IconHeartbeat size={ICON} /> }, [
|
|
166
|
+
{
|
|
167
|
+
id: 'garmin',
|
|
168
|
+
label: 'Garmin Health',
|
|
169
|
+
short: 'Garmin', // bar/menu label — keep it ≤ 10 chars
|
|
170
|
+
mobile: 'tab', // give this destination its own slot on the mobile bar
|
|
171
|
+
icon: <IconHeartbeat size={ICON} />,
|
|
172
|
+
link: linkOptions({ to: '/garmin-health', search: { window: '30d' } }),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'reading',
|
|
176
|
+
label: 'Reading',
|
|
177
|
+
icon: <IconBook size={ICON} />,
|
|
178
|
+
link: linkOptions({ to: '/reading' }),
|
|
179
|
+
children: [
|
|
180
|
+
{
|
|
181
|
+
id: 'reading-stats',
|
|
182
|
+
label: 'Stats',
|
|
183
|
+
icon: <IconChartBar size={ICON} />,
|
|
184
|
+
link: linkOptions({ to: '/reading/stats' }),
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
]),
|
|
189
|
+
],
|
|
190
|
+
})
|
|
191
|
+
```
|
|
151
192
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
193
|
+
Rules that are not stylistic:
|
|
194
|
+
|
|
195
|
+
- **Route options live under `link:`, wrapped in TanStack's own `linkOptions`** — never spread flat
|
|
196
|
+
onto the item. `to` / `search` / `params` get the router's full validation there, while `id`,
|
|
197
|
+
`label`, `short`, `icon`, `mobile`, `disabled` and `exact` ride outside it. A flat shape
|
|
198
|
+
(`{ id, label, to }`) validates `to` by ASSIGNABILITY, which does no excess-property checking, so
|
|
199
|
+
a typo'd metadata key would compile silently. A top-level `to:` is a compile error by design.
|
|
200
|
+
- **`search` may be a thunk** — `search: () => ({ date: format(new Date(), 'yyyy-MM-dd') })`
|
|
201
|
+
compiles without `as const` and re-evaluates at click time, so "today" never goes stale in a
|
|
202
|
+
long-lived tab.
|
|
203
|
+
- **Nesting is `children`**, and nested destinations are indexed by id just like top-level ones.
|
|
204
|
+
- **`NavItemMeta`**'s `mobile` field places the destination on the bar: `'tab'` (own slot),
|
|
205
|
+
`'more'` (the default — reachable through the More overlay) or `'hidden'`. A group claims a slot
|
|
206
|
+
of its own with `mobile: { tab: true }`, and leaves mobile entirely with `mobile: false`.
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
// __root.tsx — four lines replace a hand-written sections array, two render callbacks,
|
|
210
|
+
// a NAV_TARGETS table and one useMatchRoute call per destination.
|
|
211
|
+
import { useNav } from 'basalt-ui/router-tanstack'
|
|
212
|
+
import { NAV } from '../lib/nav'
|
|
213
|
+
|
|
214
|
+
const nav = useNav(NAV, { badges: { calendar: unread } }) // keys autocomplete; a typo is an error
|
|
215
|
+
return (
|
|
216
|
+
<BasaltShell brand={brand} {...nav}>
|
|
217
|
+
{children}
|
|
218
|
+
</BasaltShell>
|
|
169
219
|
)
|
|
170
220
|
```
|
|
171
221
|
|
|
172
|
-
|
|
173
|
-
|
|
222
|
+
**`useNav`** returns `{ sections, mobileNav }` — both are `BasaltShell` props, so spread it. It
|
|
223
|
+
resolves `active` per destination through `useMatchRoute` (prefix match by default, exact when the
|
|
224
|
+
item sets `exact: true`, and always exact for `'/'`), and builds each destination's **`NavAnchor`**
|
|
225
|
+
so `preload="intent"`, middle-click and back/forward keep working. `badges` values that are
|
|
226
|
+
`number` become `SidebarItem.count` (a desktop **`NavCountBadge`** plus an accent dot on the mobile
|
|
227
|
+
slot); anything else becomes `SidebarItem.badge`, which is desktop-only because a count glyph is
|
|
228
|
+
unreadable in a 56px tab.
|
|
229
|
+
|
|
230
|
+
**`navTarget`** returns one destination's link options, typed per id — spread it into `<Link>`,
|
|
231
|
+
`router.navigate()` or `redirect()` instead of restating the route and its default search:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
throw redirect(navTarget(NAV, 'garmin')) // exactly { to: '/garmin-health', search: { window: '30d' } }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**`flattenNav`** walks the definition depth-first (parent, then children), tagging each destination
|
|
238
|
+
with `groupId` / `groupLabel` — the leaf every other surface reads, e.g. projecting the whole nav
|
|
239
|
+
into Spotlight commands in one `.map()`.
|
|
240
|
+
|
|
241
|
+
### The prerequisite that makes all of this real
|
|
242
|
+
|
|
243
|
+
**Without the `Register` module augmentation, `defineNav` validates NOTHING and reports zero
|
|
244
|
+
errors.** `RegisteredRouter` falls back to `AnyRouter` when the consumer has not written:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
declare module '@tanstack/react-router' {
|
|
248
|
+
interface Register {
|
|
249
|
+
router: typeof router
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
With that missing, every `to` widens to `string`, a nonexistent route path compiles, a missing
|
|
255
|
+
required `search` compiles, and the API looks like it is working while catching nothing. This is
|
|
256
|
+
the single highest-value thing to verify when adopting the config: change one `to` to a garbage
|
|
257
|
+
path and confirm you get a compile error listing the real route paths. If you don't, the
|
|
258
|
+
augmentation is missing or `tsr generate` has not run.
|
|
259
|
+
|
|
260
|
+
Second thing to know before you migrate: TypeScript cannot catch a destination that FORGETS a
|
|
261
|
+
default `search` when the route validates with `validateSearch: (raw: Record<string, unknown>) => T`
|
|
262
|
+
— that gives TanStack an input type where no key is required. Diff an old hand-written target table
|
|
263
|
+
against the new definition by hand, key by key.
|
|
264
|
+
|
|
265
|
+
Error messages here are the router's own and they are large: one wrong `to` prints the whole
|
|
266
|
+
route-path union plus a `RouterCore<…>` blob, because TanStack's `Constrain` falls back to the
|
|
267
|
+
whole constraint. That is not misuse — read the union for the intended path and fix the string.
|
|
268
|
+
**Do not reach for `as never` to make it quiet**; the checking is the entire point of the config.
|
|
269
|
+
|
|
270
|
+
Navigation itself (`.navigate()`, `<Link to={…}>`) stays the consumer's typed concern — the adapter
|
|
271
|
+
still exposes no `navigate()` helper, so TanStack's route-tree types remain authoritative. What
|
|
272
|
+
`navTarget` hands back is the ARGUMENT to those calls, never the call.
|
|
@@ -67,7 +67,8 @@ Retuning a fill OUTSIDE the band breaks its label. `theme/contrast.test.ts` fail
|
|
|
67
67
|
or genuine multi-series separation. A count badge is a signal → it may carry blue; a nav active-state
|
|
68
68
|
is UI chrome → it stays neutral (`NavCountBadge` already encodes this). Active nav renders a
|
|
69
69
|
**neutral surface fill** + plain text + a weight bump, never the accent — baked into the theme's
|
|
70
|
-
NavLink `--nl-*` defaults across every render path (including a router `<Link>`
|
|
70
|
+
NavLink `--nl-*` defaults across every render path (including a router `<Link>` hosted through
|
|
71
|
+
`SidebarItem.Anchor`).
|
|
71
72
|
- **Accent restraint, mechanically.** The accent lands on exactly the single primary CTA per view
|
|
72
73
|
(`variant="filled"`), focus rings, links, and small status pops — nowhere else. Routine/secondary
|
|
73
74
|
buttons use `variant="default"` (neutral); don't reach for a colored `variant="light"` (it reads
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { createBasaltTheme, baseTheme, cssVariablesResolver } from './theme/inde
|
|
|
10
10
|
export type { BasaltFontsConfig, CreateBasaltThemeOptions } from './theme/index.js';
|
|
11
11
|
export { ThemeToggle, type ThemeToggleProps } from './theme-toggle/index.js';
|
|
12
12
|
export { MOTION_DURATION, MOTION_SPRING, MOTION_EASE_STANDARD } from './motion/index.js';
|
|
13
|
-
export { BasaltShell, NavCountBadge, AppSidebar, SidebarSearch, type SidebarSearchConfig, SidebarAccount, MobileNav,
|
|
13
|
+
export { BasaltShell, NavCountBadge, AppSidebar, SidebarSearch, type SidebarSearchConfig, SidebarAccount, MobileNav, type MobileNavProps, projectMobileNav, type ProjectMobileNavOptions, MOBILE_MAX_TABS_DEFAULT, MOBILE_MENU_MAX_DEFAULT, MOBILE_MORE_KEY, AppBreadcrumbs, PageHeaderProvider, PageActions, PageActionsOutlet, type SidebarSection, type SidebarItem, type NavAnchor, type NavAnchorProps, type NavMobilePlacement, type NavSectionMobile, type MobileNavConfig, type MobileNavGroup, type MobileNavModel, type MobileNavSlot, type MobileNavSurface, type BasaltShellProps, type BrandConfig, type SettingsMenuItem, type AppSidebarProps, type AccountBadgeTone, type BasaltIdentity, type BasaltRole, type BasaltPlan, type AccountMenuItem, type BasaltAccountState, type BasaltAccountActions, type BasaltAccountProps, } from './shell/index.js';
|
|
14
14
|
export { type BasaltRegister, type Slot, type Series, type SeriesKey, type AsyncState, assertNever, type StandardSchemaV1, } from './register.js';
|
|
15
15
|
export { createPersistedState, type PersistedStateOptions, useOnlineStatus, readPersistedValue, } from './state.js';
|
|
16
16
|
export { DeltaBadge, type DeltaBadgeProps, StatCard, type StatCardProps, type StatCardTone, EmptyState, type EmptyStateProps, SettingsSection, type SettingsSectionProps, SettingsRow, type SettingsRowProps, DangerZone, type DangerZoneProps, } from './dashboard/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC5E,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EACL,WAAW,EACX,aAAa,EACb,UAAU,EACV,aAAa,EACb,KAAK,mBAAmB,EACxB,cAAc,EACd,SAAS,EACT,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
SidebarSearch,
|
|
13
13
|
SidebarAccount,
|
|
14
14
|
MobileNav,
|
|
15
|
+
projectMobileNav,
|
|
16
|
+
MOBILE_MAX_TABS_DEFAULT,
|
|
17
|
+
MOBILE_MENU_MAX_DEFAULT,
|
|
18
|
+
MOBILE_MORE_KEY,
|
|
15
19
|
AppBreadcrumbs,
|
|
16
20
|
PageHeaderProvider,
|
|
17
21
|
PageActions,
|
|
@@ -64,6 +68,9 @@ export {
|
|
|
64
68
|
DangerZone,
|
|
65
69
|
DeltaBadge,
|
|
66
70
|
EmptyState,
|
|
71
|
+
MOBILE_MAX_TABS_DEFAULT,
|
|
72
|
+
MOBILE_MENU_MAX_DEFAULT,
|
|
73
|
+
MOBILE_MORE_KEY,
|
|
67
74
|
MOTION_DURATION,
|
|
68
75
|
MOTION_EASE_STANDARD,
|
|
69
76
|
MOTION_SPRING,
|
|
@@ -93,6 +100,7 @@ export {
|
|
|
93
100
|
createThreadsStore,
|
|
94
101
|
cssVariablesResolver,
|
|
95
102
|
heuristicOutcome,
|
|
103
|
+
projectMobileNav,
|
|
96
104
|
readPersistedValue,
|
|
97
105
|
threadPartRenderers,
|
|
98
106
|
threadsStoreAdapterContract,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared nav vocabulary. TYPE-ONLY BY CONTRACT — never add a value export here.
|
|
3
|
+
* `src/shell/**` (Mantine) and `src/router-tanstack/**` (headless) both `import type` from it,
|
|
4
|
+
* which is erased by tsup, so `dist/router-tanstack/index.js` gains no edge into `shell/` and
|
|
5
|
+
* `scripts/check-dist-layering.mjs` stays green with no config change.
|
|
6
|
+
*/
|
|
7
|
+
import type { MouseEvent, ReactNode } from 'react';
|
|
8
|
+
/** Where a destination appears on the mobile bar. `true` ≡ `'tab'`, `false` ≡ `'hidden'`. */
|
|
9
|
+
export type NavMobilePlacement = 'tab' | 'more' | 'hidden';
|
|
10
|
+
/**
|
|
11
|
+
* Props basalt hands a nav anchor. Deliberately a PLAIN FUNCTION TYPE, not `ComponentType`
|
|
12
|
+
* or `ElementType`: both are unions, and a union defeats Mantine's polymorphic `component`
|
|
13
|
+
* inference (`<NavLink component={…}>` then rejects `label`). VERIFIED both ways.
|
|
14
|
+
*/
|
|
15
|
+
export type NavAnchorProps = {
|
|
16
|
+
className?: string;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
onClick?: (e: MouseEvent<HTMLElement>) => void;
|
|
19
|
+
'aria-current'?: 'page' | undefined;
|
|
20
|
+
'aria-label'?: string;
|
|
21
|
+
};
|
|
22
|
+
export type NavAnchor = (props: NavAnchorProps) => ReactNode;
|
|
23
|
+
/** A single nav destination. */
|
|
24
|
+
export type SidebarItem = {
|
|
25
|
+
key: string;
|
|
26
|
+
label: string;
|
|
27
|
+
/** Bar/menu label. Falls back to `label`. Keep ≤ 10 chars — a 5-slot bar is ~72px wide. */
|
|
28
|
+
short?: string;
|
|
29
|
+
/** Mobile placement. `true` ≡ `'tab'`, `false` ≡ `'hidden'`. @default 'more' */
|
|
30
|
+
mobile?: boolean | NavMobilePlacement;
|
|
31
|
+
icon: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* The router seam. basalt renders every pixel of chrome (desktop row, 56px slot, 44px sheet
|
|
34
|
+
* row) and only hosts this component. When absent the row falls back to `<a href>` + `onClick`.
|
|
35
|
+
*/
|
|
36
|
+
Anchor?: NavAnchor;
|
|
37
|
+
/** Plain href — the no-router fallback, and the breadcrumb parent target. */
|
|
38
|
+
href?: string;
|
|
39
|
+
active?: boolean;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
onClick?: (e: MouseEvent) => void;
|
|
42
|
+
/** Rich desktop badge (e.g. `NavCountBadge`). Desktop only. */
|
|
43
|
+
badge?: ReactNode;
|
|
44
|
+
/** Unread count. Renders `NavCountBadge` on desktop when `badge` is absent, and an accent
|
|
45
|
+
* dot on the mobile slot icon. `0` renders nothing. */
|
|
46
|
+
count?: number;
|
|
47
|
+
children?: SidebarItem[];
|
|
48
|
+
};
|
|
49
|
+
/** Per-section mobile configuration. `false` hides the whole section from mobile. */
|
|
50
|
+
export type NavSectionMobile = {
|
|
51
|
+
/** Give this SECTION a bar slot; its destinations open per §2.2 cardinality inference. */
|
|
52
|
+
tab?: true;
|
|
53
|
+
/** Slot label override; falls back to `SidebarSection.label`. */
|
|
54
|
+
label?: string;
|
|
55
|
+
/** Slot icon override; falls back to `SidebarSection.icon`. */
|
|
56
|
+
icon?: ReactNode;
|
|
57
|
+
};
|
|
58
|
+
export type SidebarSection = {
|
|
59
|
+
label: string;
|
|
60
|
+
items: SidebarItem[];
|
|
61
|
+
icon?: ReactNode;
|
|
62
|
+
collapsible?: boolean;
|
|
63
|
+
defaultCollapsed?: boolean;
|
|
64
|
+
/** Mobile behaviour for this section. Omit for the default (items place themselves). */
|
|
65
|
+
mobile?: false | NavSectionMobile;
|
|
66
|
+
};
|
|
67
|
+
export type MobileNavSurface = 'link' | 'menu' | 'sheet';
|
|
68
|
+
/** One row inside a `menu`/`sheet` slot, grouped under its source section. */
|
|
69
|
+
export type MobileNavGroup = {
|
|
70
|
+
key: string;
|
|
71
|
+
label?: string;
|
|
72
|
+
items: SidebarItem[];
|
|
73
|
+
};
|
|
74
|
+
export type MobileNavSlot = {
|
|
75
|
+
kind: 'link';
|
|
76
|
+
key: string;
|
|
77
|
+
label: string;
|
|
78
|
+
short: string;
|
|
79
|
+
icon: ReactNode;
|
|
80
|
+
active: boolean;
|
|
81
|
+
item: SidebarItem;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'menu' | 'sheet';
|
|
84
|
+
key: string;
|
|
85
|
+
label: string;
|
|
86
|
+
short: string;
|
|
87
|
+
icon: ReactNode;
|
|
88
|
+
active: boolean;
|
|
89
|
+
groups: MobileNavGroup[];
|
|
90
|
+
isMore: boolean;
|
|
91
|
+
};
|
|
92
|
+
/** Pure projection of `sections` onto the bar. Unit-testable with no DOM. */
|
|
93
|
+
export type MobileNavModel = {
|
|
94
|
+
slots: MobileNavSlot[];
|
|
95
|
+
};
|
|
96
|
+
export type MobileNavConfig = {
|
|
97
|
+
/** Explicit slot order by item/section key. Overrides every `item.mobile` placement. */
|
|
98
|
+
tabs?: readonly string[];
|
|
99
|
+
/** Hard cap on bar slots, INCLUDING More. @default 5 */
|
|
100
|
+
maxTabs?: number;
|
|
101
|
+
/** Rows a slot may hold before it becomes a sheet instead of a menu. @default 6 */
|
|
102
|
+
menuMax?: number;
|
|
103
|
+
/** @default 'More' */
|
|
104
|
+
moreLabel?: string;
|
|
105
|
+
/** Extra content pinned to the bottom of the More surface. */
|
|
106
|
+
moreExtra?: ReactNode;
|
|
107
|
+
/** Scroll container for active-slot re-tap. @default document.scrollingElement */
|
|
108
|
+
getScrollElement?: () => HTMLElement | null;
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/nav/types.ts"],"names":[],"mappings":"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"}
|
|
File without changes
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ./router-tanstack — headless TanStack Router bridge for the app shell.
|
|
3
3
|
* Optional peer: @tanstack/react-router.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Carries the typed navigation definition (`defineNav`/`navGroup`/`navTarget`/`flattenNav`) and
|
|
5
|
+
* `useNav`, which resolves it into `BasaltShell`'s `sections` + `mobileNav` props; plus the
|
|
6
|
+
* StaticDataRouteOption augmentation (title/icon/navSection) for router-derived breadcrumbs and
|
|
7
|
+
* the search-param stores — no Mantine, no JSX.
|
|
6
8
|
*/
|
|
7
9
|
export { createSearchParamStore, type SearchParamStore, type SearchParamStoreOptions, } from './search-param-store.js';
|
|
8
10
|
export { createMultiSearchParamStore, type MultiSearchParamStore, type MultiSearchParamStoreOptions, } from './multi-search-param-store.js';
|
|
11
|
+
export { defineNav, navGroup, navTarget, flattenNav } from './nav.js';
|
|
12
|
+
export type { NavItemMeta, NavGroupMeta, AnyNavItem, AnyNavGroup, AnyNavLink, FlatNavItem, NavConfig, NavItemId, NavGroupId, NavTabId, NavIndex, } from './nav.js';
|
|
13
|
+
export { useNav } from './use-nav.js';
|
|
14
|
+
export type { UseNavOptions, NavBinding } from './use-nav.js';
|
|
15
|
+
export type { NavAnchor, NavAnchorProps, NavMobilePlacement } from '../nav/types.js';
|
|
9
16
|
import type { ReactNode } from 'react';
|
|
10
17
|
declare module '@tanstack/react-router' {
|
|
11
18
|
interface StaticDataRouteOption {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router-tanstack/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router-tanstack/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,2BAA2B,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,4BAA4B,GAClC,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAClE,YAAY,EACV,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,QAAQ,EACR,QAAQ,GACT,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAClC,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC1D,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,qBAAqB;QAC7B,0EAA0E;QAC1E,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,oEAAoE;QACpE,IAAI,CAAC,EAAE,SAAS,CAAA;QAChB,wDAAwD;QACxD,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB;CACF;AAID,mFAAmF;AACnF,MAAM,MAAM,SAAS,GAAG;IACtB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAA;CAChE,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAUxC;AAID,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,IAAI,gBAAgB,EAAE,CASzD"}
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createMultiSearchParamStore
|
|
6
6
|
} from "./multi-search-param-store.js";
|
|
7
|
+
import { defineNav, navGroup, navTarget, flattenNav } from "./nav.js";
|
|
8
|
+
import { useNav } from "./use-nav.js";
|
|
7
9
|
import { useLocation, useMatches } from "@tanstack/react-router";
|
|
8
10
|
function useBasaltNav() {
|
|
9
11
|
const currentPath = useLocation({ select: (l) => l.pathname });
|
|
@@ -24,6 +26,11 @@ function useRouterBreadcrumbs() {
|
|
|
24
26
|
export {
|
|
25
27
|
createMultiSearchParamStore,
|
|
26
28
|
createSearchParamStore,
|
|
29
|
+
defineNav,
|
|
30
|
+
flattenNav,
|
|
31
|
+
navGroup,
|
|
32
|
+
navTarget,
|
|
27
33
|
useBasaltNav,
|
|
34
|
+
useNav,
|
|
28
35
|
useRouterBreadcrumbs
|
|
29
36
|
};
|