basalt-ui 1.17.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 +14 -0
- package/agent/rules/basalt-charts.md +12 -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/agent/skills/basalt-design/SKILL.md +2 -2
- package/dist/charts/kinds/DualPanel.d.ts +7 -0
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +10 -3
- package/dist/charts/primitives/CartesianChart.d.ts +17 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +5 -2
- package/dist/charts/primitives/ChartTooltip.d.ts +6 -1
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +2 -1
- package/dist/charts/series.d.ts +5 -0
- package/dist/charts/series.d.ts.map +1 -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/charts/kinds/DualPanel.test.tsx +81 -1
- package/src/charts/kinds/DualPanel.tsx +26 -4
- package/src/charts/primitives/CartesianChart.test.tsx +173 -1
- package/src/charts/primitives/CartesianChart.tsx +29 -5
- package/src/charts/primitives/ChartTooltip.test.tsx +22 -1
- package/src/charts/primitives/ChartTooltip.tsx +7 -1
- package/src/charts/series.ts +5 -0
- 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/dist/shell/index.d.ts
CHANGED
|
@@ -1,45 +1,22 @@
|
|
|
1
1
|
import type { MouseEvent, ReactNode } from 'react';
|
|
2
|
-
import type { NavLinkRenderer } from './app-sidebar.js';
|
|
3
|
-
import type { BreadcrumbLinkRenderer } from './app-breadcrumbs.js';
|
|
4
2
|
import type { BasaltAccountProps } from './account-types.js';
|
|
5
3
|
import type { SidebarSearchConfig } from './sidebar-search.js';
|
|
6
|
-
|
|
4
|
+
import type { MobileNavConfig, SidebarSection } from '../nav/types.js';
|
|
5
|
+
export { AppSidebar, type AppSidebarProps } from './app-sidebar.js';
|
|
6
|
+
export { NavCountBadge } from './nav-count-badge.js';
|
|
7
7
|
export { SidebarSearch, type SidebarSearchConfig } from './sidebar-search.js';
|
|
8
8
|
export { SidebarAccount } from './app-sidebar-account.js';
|
|
9
9
|
export type { AccountBadgeTone, BasaltIdentity, BasaltRole, BasaltPlan, AccountMenuItem, BasaltAccountState, BasaltAccountActions, BasaltAccountProps, } from './account-types.js';
|
|
10
|
-
export { MobileNav, type
|
|
11
|
-
export {
|
|
10
|
+
export { MobileNav, type MobileNavProps } from './app-mobile-nav.js';
|
|
11
|
+
export { projectMobileNav, type ProjectMobileNavOptions, MOBILE_MAX_TABS_DEFAULT, MOBILE_MENU_MAX_DEFAULT, MOBILE_MORE_KEY, } from './mobile-nav-model.js';
|
|
12
|
+
export { AppBreadcrumbs } from './app-breadcrumbs.js';
|
|
12
13
|
export { PageHeaderProvider, PageActions, PageActionsOutlet } from './page-header.js';
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** Whether this destination appears in the mobile bottom-nav. */
|
|
20
|
-
mobile?: boolean;
|
|
21
|
-
icon: ReactNode;
|
|
22
|
-
href?: string;
|
|
23
|
-
active?: boolean;
|
|
24
|
-
disabled?: boolean;
|
|
25
|
-
onClick?: (e: MouseEvent) => void;
|
|
26
|
-
badge?: ReactNode;
|
|
27
|
-
/** Nested sub-navigation items — surfaced in a hover popover and inline when active. */
|
|
28
|
-
children?: SidebarItem[];
|
|
29
|
-
};
|
|
30
|
-
/** A labelled group of sidebar items. Grounded verbatim in argo's `SidebarSection`. */
|
|
31
|
-
export type SidebarSection = {
|
|
32
|
-
label: string;
|
|
33
|
-
items: SidebarItem[];
|
|
34
|
-
/** Group icon, used by the mobile tabs. */
|
|
35
|
-
icon?: ReactNode;
|
|
36
|
-
/** Desktop: render a clickable group header that collapses its items. */
|
|
37
|
-
collapsible?: boolean;
|
|
38
|
-
/** Initial collapsed state when `collapsible`. */
|
|
39
|
-
defaultCollapsed?: boolean;
|
|
40
|
-
/** `false` excludes the section from the primary mobile group tabs. */
|
|
41
|
-
mobileTab?: boolean;
|
|
42
|
-
};
|
|
14
|
+
/**
|
|
15
|
+
* The shared nav vocabulary lives in `src/nav/types.ts` so the headless router bridge can `import
|
|
16
|
+
* type` it without reaching into the Mantine layer. Re-exported here so `SidebarItem` /
|
|
17
|
+
* `SidebarSection` keep resolving straight from `basalt-ui`, exactly as before.
|
|
18
|
+
*/
|
|
19
|
+
export type { NavAnchor, NavAnchorProps, NavMobilePlacement, NavSectionMobile, SidebarItem, SidebarSection, MobileNavConfig, MobileNavGroup, MobileNavModel, MobileNavSlot, MobileNavSurface, } from '../nav/types.js';
|
|
43
20
|
/** Brand identity shown in the sidebar header (logo + name). */
|
|
44
21
|
export type BrandConfig = {
|
|
45
22
|
/** Display name (e.g. "Argo"). */
|
|
@@ -61,25 +38,21 @@ export type SettingsMenuItem = {
|
|
|
61
38
|
export type BasaltShellProps = {
|
|
62
39
|
/** Brand identity for the sidebar header. */
|
|
63
40
|
brand: BrandConfig;
|
|
64
|
-
/** Grouped nav sections rendered
|
|
41
|
+
/** Grouped nav sections — the ONE definition, rendered as the sidebar and projected to the bar. */
|
|
65
42
|
sections: SidebarSection[];
|
|
66
43
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* seam — no router primitive is imported by the shell.
|
|
44
|
+
* Mobile bar tuning. Every field optional — the defaults ARE the design, and a nav with nothing
|
|
45
|
+
* configured still produces a working bar (see `projectMobileNav`'s zero-config fallback).
|
|
70
46
|
*/
|
|
71
|
-
|
|
47
|
+
mobileNav?: MobileNavConfig;
|
|
72
48
|
/** Persistent, shell-owned top-bar slot (timer, refresh, notifications). */
|
|
73
49
|
globalActions?: ReactNode;
|
|
74
|
-
/** Extra content appended to the sidebar footer, beside the settings menu (mobile close, etc.). */
|
|
75
|
-
sidebarFooterExtra?: ReactNode;
|
|
76
50
|
/**
|
|
77
|
-
* Arbitrary content appended after `sections` inside the sidebar's nav scroll
|
|
78
|
-
* filter panel, a project list, …)
|
|
79
|
-
*
|
|
80
|
-
* `
|
|
81
|
-
*
|
|
82
|
-
* reaches both.
|
|
51
|
+
* DESKTOP ONLY. Arbitrary content appended after `sections` inside the sidebar's nav scroll
|
|
52
|
+
* region (a tree, a filter panel, a project list, …), for anything a set of `SidebarItem`s can't
|
|
53
|
+
* express. Hidden on the collapsed desktop rail; the sidebar itself no longer exists below the
|
|
54
|
+
* `sm` breakpoint, so this slot has no mobile representation — put mobile-reachable extras in
|
|
55
|
+
* `mobileNav.moreExtra` instead.
|
|
83
56
|
*/
|
|
84
57
|
sidebarNavExtra?: ReactNode;
|
|
85
58
|
/** Entries appended to the sidebar settings menu. */
|
|
@@ -109,24 +82,8 @@ export type BasaltShellProps = {
|
|
|
109
82
|
* a no-op when omitted.
|
|
110
83
|
*/
|
|
111
84
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
112
|
-
/**
|
|
113
|
-
* Optional router link renderer for the breadcrumb parent segment. When provided, the parent
|
|
114
|
-
* breadcrumb label is rendered through this callback (e.g. a TanStack `<Link>`) instead of a
|
|
115
|
-
* plain `<a href>`, enabling client-side navigation.
|
|
116
|
-
*/
|
|
117
|
-
renderBreadcrumbLink?: BreadcrumbLinkRenderer | undefined;
|
|
118
85
|
/** Page content. */
|
|
119
86
|
children?: ReactNode;
|
|
120
87
|
};
|
|
121
|
-
|
|
122
|
-
* Sidebar nav count badge (docs/DESIGN-SPEC.md §5): mono 10.5px, ink-8% bg, radius 5, height 16,
|
|
123
|
-
* padding 0 5px, muted text; `marginLeft: auto` pins it to the row end on any render path.
|
|
124
|
-
* `styles` (inline) rather than a token color prop, since none of Mantine's variant/color
|
|
125
|
-
* combinations land on the ink-tint idiom. Returns `null` for a zero/empty count so the badge slot
|
|
126
|
-
* stays clean ("ink earns its color", DESIGN.md).
|
|
127
|
-
*/
|
|
128
|
-
export declare function NavCountBadge({ count }: {
|
|
129
|
-
count: number;
|
|
130
|
-
}): import("react").JSX.Element | null;
|
|
131
|
-
export declare function BasaltShell({ brand, sections, renderNavLink, globalActions, sidebarFooterExtra, sidebarNavExtra, settingsMenuItems, storageKey, collapsed: collapsedProp, onCollapsedChange, renderBreadcrumbLink, account, search, children, }: BasaltShellProps): import("react").JSX.Element;
|
|
88
|
+
export declare function BasaltShell({ brand, sections, mobileNav, globalActions, sidebarNavExtra, settingsMenuItems, storageKey, collapsed: collapsedProp, onCollapsedChange, account, search, children, }: BasaltShellProps): import("react").JSX.Element;
|
|
132
89
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMlD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,KAAK,EAAE,eAAe,EAA0B,cAAc,EAAE,MAAM,cAAc,CAAA;AAK3F,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,GAChB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAElF;;;;GAIG;AACH,YAAY,EACV,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,cAAc,CAAA;AAErB,gEAAgE;AAChE,MAAM,MAAM,WAAW,GAAG;IACxB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,6DAA6D;AAC7D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6CAA6C;IAC7C,KAAK,EAAE,WAAW,CAAA;IAClB,mGAAmG;IACnG,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,4EAA4E;IAC5E,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,SAAS,CAAA;IAC3B,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B,kGAAkG;IAClG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,oBAAoB;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAgDD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,SAAS,EACT,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,UAAuC,EACvC,SAAS,EAAE,aAAa,EACxB,iBAAiB,EACjB,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE,gBAAgB,+BAgGlB"}
|
package/dist/shell/index.js
CHANGED
|
@@ -1,44 +1,28 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { AppShell
|
|
3
|
-
import {
|
|
2
|
+
import { AppShell } from "@mantine/core";
|
|
3
|
+
import { useLocalStorage } from "@mantine/hooks";
|
|
4
|
+
import { useMemo } from "react";
|
|
4
5
|
import { AppSidebar } from "./app-sidebar.js";
|
|
5
|
-
import { MobileNav } from "./app-mobile-nav.js";
|
|
6
|
+
import { MobileNav, accountRowCount } from "./app-mobile-nav.js";
|
|
7
|
+
import { projectMobileNav } from "./mobile-nav-model.js";
|
|
6
8
|
import { AppBreadcrumbs } from "./app-breadcrumbs.js";
|
|
7
9
|
import { PageActionsOutlet, PageHeaderProvider } from "./page-header.js";
|
|
8
|
-
import { VX } from "../tokens/index.js";
|
|
9
10
|
import { useBasaltSpacing } from "../theme/index.js";
|
|
10
11
|
import headerClasses from "./app-header.module.css";
|
|
12
|
+
import mobileNavClasses from "./app-mobile-nav.module.css";
|
|
11
13
|
import { AppSidebar as AppSidebar2 } from "./app-sidebar.js";
|
|
14
|
+
import { NavCountBadge } from "./nav-count-badge.js";
|
|
12
15
|
import { SidebarSearch } from "./sidebar-search.js";
|
|
13
16
|
import { SidebarAccount } from "./app-sidebar-account.js";
|
|
17
|
+
import { MobileNav as MobileNav2 } from "./app-mobile-nav.js";
|
|
14
18
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
projectMobileNav as projectMobileNav2,
|
|
20
|
+
MOBILE_MAX_TABS_DEFAULT,
|
|
21
|
+
MOBILE_MENU_MAX_DEFAULT,
|
|
22
|
+
MOBILE_MORE_KEY
|
|
23
|
+
} from "./mobile-nav-model.js";
|
|
17
24
|
import { AppBreadcrumbs as AppBreadcrumbs2 } from "./app-breadcrumbs.js";
|
|
18
25
|
import { PageHeaderProvider as PageHeaderProvider2, PageActions, PageActionsOutlet as PageActionsOutlet2 } from "./page-header.js";
|
|
19
|
-
function NavCountBadge({ count }) {
|
|
20
|
-
if (!count) return null;
|
|
21
|
-
return /* @__PURE__ */ jsx(
|
|
22
|
-
Badge,
|
|
23
|
-
{
|
|
24
|
-
size: "sm",
|
|
25
|
-
styles: {
|
|
26
|
-
root: {
|
|
27
|
-
backgroundColor: "color-mix(in srgb, var(--vx-ink) 8%, transparent)",
|
|
28
|
-
color: "var(--vx-muted)",
|
|
29
|
-
fontFamily: "var(--basalt-font-mono)",
|
|
30
|
-
fontSize: VX.text.micro,
|
|
31
|
-
fontWeight: 500,
|
|
32
|
-
height: 16,
|
|
33
|
-
padding: "0 5px",
|
|
34
|
-
marginLeft: "auto",
|
|
35
|
-
borderRadius: "var(--vx-radius-tight)"
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
children: count
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
26
|
function findActiveCrumb(sections) {
|
|
43
27
|
for (const section of sections) {
|
|
44
28
|
const found = findActiveWithParent(section.items);
|
|
@@ -46,67 +30,38 @@ function findActiveCrumb(sections) {
|
|
|
46
30
|
return {
|
|
47
31
|
section: section.label,
|
|
48
32
|
parent: found.parent,
|
|
33
|
+
parentAnchor: found.parentAnchor,
|
|
49
34
|
parentHref: found.parentHref,
|
|
50
35
|
page: found.page
|
|
51
36
|
};
|
|
52
37
|
}
|
|
53
38
|
return void 0;
|
|
54
39
|
}
|
|
55
|
-
function findActiveWithParent(items, parentLabel, parentHref) {
|
|
40
|
+
function findActiveWithParent(items, parentLabel, parentAnchor, parentHref) {
|
|
56
41
|
for (const item of items) {
|
|
57
42
|
if (item.children) {
|
|
58
|
-
const found = findActiveWithParent(item.children, item.label, item.href);
|
|
43
|
+
const found = findActiveWithParent(item.children, item.label, item.Anchor, item.href);
|
|
59
44
|
if (found) return found;
|
|
60
45
|
}
|
|
61
|
-
if (item.active) return { parent: parentLabel, parentHref, page: item.label };
|
|
46
|
+
if (item.active) return { parent: parentLabel, parentAnchor, parentHref, page: item.label };
|
|
62
47
|
}
|
|
63
48
|
return void 0;
|
|
64
49
|
}
|
|
65
|
-
function toMobileSections(sections, closeMobile) {
|
|
66
|
-
return sections.filter((s) => s.mobileTab !== false).map((s) => ({
|
|
67
|
-
key: s.label,
|
|
68
|
-
label: s.label,
|
|
69
|
-
icon: s.icon,
|
|
70
|
-
active: s.items.some((i) => i.active),
|
|
71
|
-
items: flattenSidebarItems(s.items).map(
|
|
72
|
-
(i) => ({
|
|
73
|
-
key: i.key,
|
|
74
|
-
label: i.label,
|
|
75
|
-
icon: i.icon,
|
|
76
|
-
...i.href !== void 0 && { href: i.href },
|
|
77
|
-
...i.active !== void 0 && { active: i.active },
|
|
78
|
-
onClick: (e) => {
|
|
79
|
-
i.onClick?.(e);
|
|
80
|
-
closeMobile();
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
)
|
|
84
|
-
}));
|
|
85
|
-
}
|
|
86
|
-
function flattenSidebarItems(items) {
|
|
87
|
-
return items.flatMap((item) => [
|
|
88
|
-
item,
|
|
89
|
-
...item.children ? flattenSidebarItems(item.children) : []
|
|
90
|
-
]);
|
|
91
|
-
}
|
|
92
50
|
function BasaltShell({
|
|
93
51
|
brand,
|
|
94
52
|
sections,
|
|
95
|
-
|
|
53
|
+
mobileNav,
|
|
96
54
|
globalActions,
|
|
97
|
-
sidebarFooterExtra,
|
|
98
55
|
sidebarNavExtra,
|
|
99
56
|
settingsMenuItems,
|
|
100
57
|
storageKey = "basalt-sidebar-collapsed",
|
|
101
58
|
collapsed: collapsedProp,
|
|
102
59
|
onCollapsedChange,
|
|
103
|
-
renderBreadcrumbLink,
|
|
104
60
|
account,
|
|
105
61
|
search,
|
|
106
62
|
children
|
|
107
63
|
}) {
|
|
108
64
|
const { step } = useBasaltSpacing();
|
|
109
|
-
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure();
|
|
110
65
|
const [storedCollapsed, setStoredCollapsed] = useLocalStorage({
|
|
111
66
|
key: storageKey,
|
|
112
67
|
defaultValue: false,
|
|
@@ -120,7 +75,11 @@ function BasaltShell({
|
|
|
120
75
|
onCollapsedChange?.(next);
|
|
121
76
|
};
|
|
122
77
|
const activeCrumb = findActiveCrumb(sections);
|
|
123
|
-
const
|
|
78
|
+
const extraMoreRows = accountRowCount(account) + (settingsMenuItems?.length ?? 0) + (mobileNav?.moreExtra ? 1 : 0);
|
|
79
|
+
const model = useMemo(
|
|
80
|
+
() => projectMobileNav(sections, { config: mobileNav, extraMoreRows }),
|
|
81
|
+
[sections, mobileNav, extraMoreRows]
|
|
82
|
+
);
|
|
124
83
|
return /* @__PURE__ */ jsx(PageHeaderProvider, { children: /* @__PURE__ */ jsxs(
|
|
125
84
|
AppShell,
|
|
126
85
|
{
|
|
@@ -135,13 +94,14 @@ function BasaltShell({
|
|
|
135
94
|
sm: collapsed ? step.appShellNavbarRailWidth : step.appShellNavbarWidth
|
|
136
95
|
},
|
|
137
96
|
breakpoint: "sm",
|
|
138
|
-
|
|
97
|
+
// No mobile sidebar drawer, ever — the bottom bar is the entire mobile nav.
|
|
98
|
+
collapsed: { mobile: true }
|
|
139
99
|
},
|
|
140
|
-
footer: { height: { base:
|
|
100
|
+
footer: { height: { base: step.mobileNavBarHeight, sm: 0 } },
|
|
141
101
|
padding: "sm",
|
|
142
102
|
children: [
|
|
143
103
|
/* @__PURE__ */ jsx(AppShell.Header, { px: "md", withBorder: false, children: /* @__PURE__ */ jsxs("div", { className: headerClasses.bar, children: [
|
|
144
|
-
/* @__PURE__ */ jsx("div", { className: headerClasses.lead, children: /* @__PURE__ */ jsx(AppBreadcrumbs, { ...activeCrumb
|
|
104
|
+
/* @__PURE__ */ jsx("div", { className: headerClasses.lead, children: /* @__PURE__ */ jsx(AppBreadcrumbs, { ...activeCrumb }) }),
|
|
145
105
|
/* @__PURE__ */ jsx(PageActionsOutlet, { className: headerClasses.pageActions }),
|
|
146
106
|
globalActions && /* @__PURE__ */ jsx("div", { className: headerClasses.global, children: globalActions })
|
|
147
107
|
] }) }),
|
|
@@ -152,22 +112,20 @@ function BasaltShell({
|
|
|
152
112
|
sections,
|
|
153
113
|
collapsed,
|
|
154
114
|
onToggleCollapse: toggleCollapse,
|
|
155
|
-
onClose: closeMobile,
|
|
156
|
-
footerExtra: sidebarFooterExtra,
|
|
157
115
|
navExtra: sidebarNavExtra,
|
|
158
|
-
...renderNavLink !== void 0 && { renderNavLink },
|
|
159
116
|
...settingsMenuItems !== void 0 && { settingsMenuItems },
|
|
160
117
|
...account !== void 0 && { account },
|
|
161
118
|
...search !== void 0 && { search }
|
|
162
119
|
}
|
|
163
120
|
) }),
|
|
164
|
-
/* @__PURE__ */ jsx(AppShell.Main, { children }),
|
|
165
|
-
/* @__PURE__ */ jsx(AppShell.Footer, { hiddenFrom: "sm", p: 0, children: /* @__PURE__ */ jsx(
|
|
121
|
+
/* @__PURE__ */ jsx(AppShell.Main, { className: mobileNavClasses.mainSafeArea, children }),
|
|
122
|
+
/* @__PURE__ */ jsx(AppShell.Footer, { hiddenFrom: "sm", p: 0, withBorder: false, children: /* @__PURE__ */ jsx(
|
|
166
123
|
MobileNav,
|
|
167
124
|
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
...
|
|
125
|
+
model,
|
|
126
|
+
config: mobileNav,
|
|
127
|
+
...account !== void 0 && { account },
|
|
128
|
+
...settingsMenuItems !== void 0 && { settingsMenuItems }
|
|
171
129
|
}
|
|
172
130
|
) })
|
|
173
131
|
]
|
|
@@ -178,11 +136,15 @@ export {
|
|
|
178
136
|
AppBreadcrumbs2 as AppBreadcrumbs,
|
|
179
137
|
AppSidebar2 as AppSidebar,
|
|
180
138
|
BasaltShell,
|
|
139
|
+
MOBILE_MAX_TABS_DEFAULT,
|
|
140
|
+
MOBILE_MENU_MAX_DEFAULT,
|
|
141
|
+
MOBILE_MORE_KEY,
|
|
181
142
|
MobileNav2 as MobileNav,
|
|
182
143
|
NavCountBadge,
|
|
183
144
|
PageActions,
|
|
184
145
|
PageActionsOutlet2 as PageActionsOutlet,
|
|
185
146
|
PageHeaderProvider2 as PageHeaderProvider,
|
|
186
147
|
SidebarAccount,
|
|
187
|
-
SidebarSearch
|
|
148
|
+
SidebarSearch,
|
|
149
|
+
projectMobileNav2 as projectMobileNav
|
|
188
150
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mobile-bar projection — `SidebarSection[]` + `MobileNavConfig` → `MobileNavModel`.
|
|
3
|
+
*
|
|
4
|
+
* PURE BY CONTRACT: no React, no Mantine, no DOM, no JSX. Everything the bottom bar decides —
|
|
5
|
+
* which destinations get a slot, which slot is a plain navigation link and which raises a surface,
|
|
6
|
+
* whether that surface is a menu or a sheet, what falls into the overflow "More" slot — is decided
|
|
7
|
+
* here and unit-testable with no renderer. `app-mobile-nav.tsx` only paints the result.
|
|
8
|
+
*
|
|
9
|
+
* The law it implements is the interaction model, not a heuristic: a slot is a DESTINATION, so a
|
|
10
|
+
* tap navigates with no overlay to dismiss. An overlay exists only where a slot genuinely holds
|
|
11
|
+
* more than one destination, and its surface is INFERRED from how many rows it holds rather than
|
|
12
|
+
* configured — six rows is the arithmetic ceiling for a menu that pops out of a 56px bar without
|
|
13
|
+
* ever rendering below the fold (6 x 44px + 8px padding = 272px against 415px of headroom on the
|
|
14
|
+
* smallest supported viewport), so past that the surface becomes a bottom sheet.
|
|
15
|
+
*/
|
|
16
|
+
import type { MobileNavConfig, MobileNavModel, SidebarSection } from '../nav/types.js';
|
|
17
|
+
/** Hard cap on bar slots, INCLUDING More. Five 72px slots is the widest a 360px viewport holds. */
|
|
18
|
+
export declare const MOBILE_MAX_TABS_DEFAULT = 5;
|
|
19
|
+
/**
|
|
20
|
+
* Rows a slot may hold before it becomes a sheet instead of a menu. Arithmetic, not taste — see
|
|
21
|
+
* the module doc: six rows is the last count a bottom-anchored, non-flipping menu fits above the
|
|
22
|
+
* bar on the smallest supported viewport.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MOBILE_MENU_MAX_DEFAULT = 6;
|
|
25
|
+
/** The overflow slot's reserved key. A slot like any other — it just exists only when needed. */
|
|
26
|
+
export declare const MOBILE_MORE_KEY = "__more";
|
|
27
|
+
export type ProjectMobileNavOptions = {
|
|
28
|
+
config?: MobileNavConfig | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Extra rows the More surface will hold — account + settings + `moreExtra`. Drives both
|
|
31
|
+
* `needsMore` and the menu-vs-sheet threshold. The model cannot see those rows (they are
|
|
32
|
+
* Mantine-rendered chrome, not `SidebarItem`s), so the shell counts them and passes the total.
|
|
33
|
+
*/
|
|
34
|
+
extraMoreRows?: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Projects `sections` onto the bar. Implements §2.3's thirteen rules in order, and is the ONLY
|
|
38
|
+
* place slot selection happens — `MobileNav` receives a finished model and paints it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function projectMobileNav(sections: SidebarSection[], opts?: ProjectMobileNavOptions): MobileNavModel;
|
|
41
|
+
//# sourceMappingURL=mobile-nav-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-nav-model.d.ts","sourceRoot":"","sources":["../../src/shell/mobile-nav-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAId,cAAc,EACf,MAAM,cAAc,CAAA;AAErB,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,iGAAiG;AACjG,eAAO,MAAM,eAAe,WAAW,CAAA;AAEvC,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAoQD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EAAE,EAC1B,IAAI,CAAC,EAAE,uBAAuB,GAC7B,cAAc,CA2EhB"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const MOBILE_MAX_TABS_DEFAULT = 5;
|
|
2
|
+
const MOBILE_MENU_MAX_DEFAULT = 6;
|
|
3
|
+
const MOBILE_MORE_KEY = "__more";
|
|
4
|
+
const DEV = process.env["NODE_ENV"] !== "production";
|
|
5
|
+
function placementOf(item, sectionHidden) {
|
|
6
|
+
if (sectionHidden) return "hidden";
|
|
7
|
+
const mobile = item.mobile;
|
|
8
|
+
if (mobile === void 0) return "more";
|
|
9
|
+
if (mobile === true) return "tab";
|
|
10
|
+
if (mobile === false) return "hidden";
|
|
11
|
+
return mobile;
|
|
12
|
+
}
|
|
13
|
+
function isSectionHidden(section) {
|
|
14
|
+
return section.mobile === false;
|
|
15
|
+
}
|
|
16
|
+
function hasActiveDestination(item) {
|
|
17
|
+
if (item.active) return true;
|
|
18
|
+
return item.children?.some(hasActiveDestination) ?? false;
|
|
19
|
+
}
|
|
20
|
+
function countRows(items) {
|
|
21
|
+
return items.reduce((total, item) => total + 1 + countRows(item.children ?? []), 0);
|
|
22
|
+
}
|
|
23
|
+
function collectKeys(items, out) {
|
|
24
|
+
for (const item of items) {
|
|
25
|
+
out.add(item.key);
|
|
26
|
+
collectKeys(item.children ?? [], out);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function flattenItems(items, out) {
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
out.push(item);
|
|
32
|
+
flattenItems(item.children ?? [], out);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function pruneTree(items, keep) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
const children = pruneTree(item.children ?? [], keep);
|
|
39
|
+
if (!keep(item)) {
|
|
40
|
+
out.push(...children);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const next = { ...item };
|
|
44
|
+
if (children.length > 0) next.children = children;
|
|
45
|
+
else delete next.children;
|
|
46
|
+
out.push(next);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function itemCandidate(item) {
|
|
51
|
+
return {
|
|
52
|
+
key: item.key,
|
|
53
|
+
covered: /* @__PURE__ */ new Set([item.key]),
|
|
54
|
+
toSlot: () => ({
|
|
55
|
+
kind: "link",
|
|
56
|
+
key: item.key,
|
|
57
|
+
label: item.label,
|
|
58
|
+
short: item.short ?? item.label,
|
|
59
|
+
icon: item.icon,
|
|
60
|
+
active: hasActiveDestination(item),
|
|
61
|
+
item
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function sectionCandidate(section, menuMax) {
|
|
66
|
+
const mobile = section.mobile === false || section.mobile === void 0 ? void 0 : section.mobile;
|
|
67
|
+
const items = pruneTree(section.items, (item) => placementOf(item, false) !== "hidden");
|
|
68
|
+
const covered = /* @__PURE__ */ new Set();
|
|
69
|
+
collectKeys(items, covered);
|
|
70
|
+
const label = mobile?.label ?? section.label;
|
|
71
|
+
const rows = countRows(items);
|
|
72
|
+
const only = items[0];
|
|
73
|
+
return {
|
|
74
|
+
key: section.label,
|
|
75
|
+
covered,
|
|
76
|
+
toSlot: () => {
|
|
77
|
+
if (rows === 0) return void 0;
|
|
78
|
+
if (rows === 1 && only !== void 0 && !only.disabled) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "link",
|
|
81
|
+
key: only.key,
|
|
82
|
+
label,
|
|
83
|
+
short: label,
|
|
84
|
+
// The section's own identity wins when it declares one — the consumer configured the
|
|
85
|
+
// SLOT — and falls back to the single destination's icon when it does not.
|
|
86
|
+
icon: mobile?.icon ?? section.icon ?? only.icon,
|
|
87
|
+
active: hasActiveDestination(only),
|
|
88
|
+
item: only
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
kind: rows <= menuMax ? "menu" : "sheet",
|
|
93
|
+
key: section.label,
|
|
94
|
+
label,
|
|
95
|
+
short: label,
|
|
96
|
+
icon: mobile?.icon ?? section.icon,
|
|
97
|
+
active: items.some(hasActiveDestination),
|
|
98
|
+
// One section, so the rows need no group heading — the tab itself names them.
|
|
99
|
+
groups: [{ key: section.label, items }],
|
|
100
|
+
isMore: false
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function buildCandidates(sections, config, maxTabs, menuMax) {
|
|
106
|
+
const flat = [];
|
|
107
|
+
for (const section of sections) {
|
|
108
|
+
if (isSectionHidden(section)) continue;
|
|
109
|
+
flattenItems(section.items, flat);
|
|
110
|
+
}
|
|
111
|
+
const tabbable = (item) => {
|
|
112
|
+
if (!item.disabled) return true;
|
|
113
|
+
if (DEV) {
|
|
114
|
+
console.warn(
|
|
115
|
+
`[basalt] MobileNav: "${item.key}" asks for a bar slot but is disabled \u2014 a disabled destination never becomes a tab. It still renders, disabled, in the More surface.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
};
|
|
120
|
+
if (config?.tabs !== void 0) {
|
|
121
|
+
const candidates2 = [];
|
|
122
|
+
for (const id of config.tabs) {
|
|
123
|
+
const item = flat.find((candidate) => candidate.key === id);
|
|
124
|
+
if (item !== void 0) {
|
|
125
|
+
if (tabbable(item)) candidates2.push(itemCandidate(item));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const section = sections.find((s) => s.label === id && !isSectionHidden(s));
|
|
129
|
+
if (section !== void 0) {
|
|
130
|
+
candidates2.push(sectionCandidate(section, menuMax));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (DEV) {
|
|
134
|
+
console.warn(
|
|
135
|
+
`[basalt] MobileNav: mobileNav.tabs names "${id}", which is neither a destination key nor a visible section label \u2014 ignoring it.`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return candidates2;
|
|
140
|
+
}
|
|
141
|
+
const candidates = [];
|
|
142
|
+
for (const section of sections) {
|
|
143
|
+
if (isSectionHidden(section)) continue;
|
|
144
|
+
if (section.mobile !== void 0 && section.mobile !== false && section.mobile.tab === true) {
|
|
145
|
+
candidates.push(sectionCandidate(section, menuMax));
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const items = [];
|
|
149
|
+
flattenItems(section.items, items);
|
|
150
|
+
for (const item of items) {
|
|
151
|
+
if (placementOf(item, false) !== "tab") continue;
|
|
152
|
+
if (tabbable(item)) candidates.push(itemCandidate(item));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (candidates.length > 0) return candidates;
|
|
156
|
+
const fallback = [];
|
|
157
|
+
for (const section of sections) {
|
|
158
|
+
if (isSectionHidden(section)) continue;
|
|
159
|
+
for (const item of section.items) {
|
|
160
|
+
if (fallback.length >= maxTabs - 1) return fallback;
|
|
161
|
+
if (item.disabled) continue;
|
|
162
|
+
if (placementOf(item, false) === "hidden") continue;
|
|
163
|
+
fallback.push(itemCandidate(item));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return fallback;
|
|
167
|
+
}
|
|
168
|
+
function buildOverflow(sections, covered) {
|
|
169
|
+
const groups = [];
|
|
170
|
+
for (const section of sections) {
|
|
171
|
+
const hidden = isSectionHidden(section);
|
|
172
|
+
if (hidden) continue;
|
|
173
|
+
const items = pruneTree(
|
|
174
|
+
section.items,
|
|
175
|
+
(item) => placementOf(item, hidden) !== "hidden" && !covered.has(item.key)
|
|
176
|
+
);
|
|
177
|
+
if (items.length === 0) continue;
|
|
178
|
+
groups.push({ key: section.label, label: section.label, items });
|
|
179
|
+
}
|
|
180
|
+
return groups;
|
|
181
|
+
}
|
|
182
|
+
function projectMobileNav(sections, opts) {
|
|
183
|
+
const config = opts?.config;
|
|
184
|
+
const maxTabs = Math.max(1, config?.maxTabs ?? MOBILE_MAX_TABS_DEFAULT);
|
|
185
|
+
const menuMax = Math.max(1, config?.menuMax ?? MOBILE_MENU_MAX_DEFAULT);
|
|
186
|
+
const extraMoreRows = opts?.extraMoreRows ?? 0;
|
|
187
|
+
const candidates = buildCandidates(sections, config, maxTabs, menuMax);
|
|
188
|
+
const coveredByAll = /* @__PURE__ */ new Set();
|
|
189
|
+
for (const candidate of candidates) for (const key of candidate.covered) coveredByAll.add(key);
|
|
190
|
+
const overflowIfNoneDropped = buildOverflow(sections, coveredByAll);
|
|
191
|
+
const needsMore = countRows(overflowIfNoneDropped.flatMap((group) => group.items)) > 0 || extraMoreRows > 0 || candidates.length > maxTabs;
|
|
192
|
+
const limit = needsMore ? maxTabs - 1 : maxTabs;
|
|
193
|
+
const kept = candidates.slice(0, limit);
|
|
194
|
+
const dropped = candidates.slice(limit);
|
|
195
|
+
if (DEV && dropped.length > 0) {
|
|
196
|
+
console.warn(
|
|
197
|
+
`[basalt] MobileNav: ${candidates.length} slot candidates for ${limit} available slot(s) \u2014 the last ${dropped.length} moved into the More surface: ${dropped.map((candidate) => `"${candidate.key}"`).join(", ")}. Raise mobileNav.maxTabs or drop a mobile: 'tab'.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const covered = /* @__PURE__ */ new Set();
|
|
201
|
+
for (const candidate of kept) for (const key of candidate.covered) covered.add(key);
|
|
202
|
+
const slots = [];
|
|
203
|
+
for (const candidate of kept) {
|
|
204
|
+
const slot = candidate.toSlot();
|
|
205
|
+
if (slot !== void 0) slots.push(slot);
|
|
206
|
+
}
|
|
207
|
+
if (!needsMore) return { slots };
|
|
208
|
+
const groups = buildOverflow(sections, covered);
|
|
209
|
+
const overflowItems = groups.flatMap((group) => group.items);
|
|
210
|
+
const rows = countRows(overflowItems) + extraMoreRows;
|
|
211
|
+
if (rows === 0) return { slots };
|
|
212
|
+
const label = config?.moreLabel ?? "More";
|
|
213
|
+
const only = overflowItems[0];
|
|
214
|
+
if (rows === 1 && extraMoreRows === 0 && only !== void 0 && !only.disabled) {
|
|
215
|
+
slots.push({
|
|
216
|
+
kind: "link",
|
|
217
|
+
key: only.key,
|
|
218
|
+
label: only.label,
|
|
219
|
+
short: only.short ?? only.label,
|
|
220
|
+
icon: only.icon,
|
|
221
|
+
active: hasActiveDestination(only),
|
|
222
|
+
item: only
|
|
223
|
+
});
|
|
224
|
+
return { slots };
|
|
225
|
+
}
|
|
226
|
+
slots.push({
|
|
227
|
+
kind: rows <= menuMax ? "menu" : "sheet",
|
|
228
|
+
key: MOBILE_MORE_KEY,
|
|
229
|
+
label,
|
|
230
|
+
short: label,
|
|
231
|
+
// No icon: the More glyph is chrome the renderer owns, and this module holds no JSX.
|
|
232
|
+
icon: void 0,
|
|
233
|
+
active: overflowItems.some(hasActiveDestination),
|
|
234
|
+
groups,
|
|
235
|
+
isMore: true
|
|
236
|
+
});
|
|
237
|
+
return { slots };
|
|
238
|
+
}
|
|
239
|
+
export {
|
|
240
|
+
MOBILE_MAX_TABS_DEFAULT,
|
|
241
|
+
MOBILE_MENU_MAX_DEFAULT,
|
|
242
|
+
MOBILE_MORE_KEY,
|
|
243
|
+
projectMobileNav
|
|
244
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-nav-model.test.d.ts","sourceRoot":"","sources":["../../src/shell/mobile-nav-model.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar nav count badge (docs/DESIGN-SPEC.md §5): mono 10.5px, ink-8% bg, radius 5, height 16,
|
|
3
|
+
* padding 0 5px, muted text; `marginLeft: auto` pins it to the row end on any render path.
|
|
4
|
+
* `styles` (inline) rather than a token color prop, since none of Mantine's variant/color
|
|
5
|
+
* combinations land on the ink-tint idiom. Returns `null` for a zero/empty count so the badge slot
|
|
6
|
+
* stays clean ("ink earns its color", DESIGN.md).
|
|
7
|
+
*/
|
|
8
|
+
export declare function NavCountBadge({ count }: {
|
|
9
|
+
count: number;
|
|
10
|
+
}): import("react").JSX.Element | null;
|
|
11
|
+
//# sourceMappingURL=nav-count-badge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-count-badge.d.ts","sourceRoot":"","sources":["../../src/shell/nav-count-badge.tsx"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,sCAsBzD"}
|