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.
Files changed (112) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/agent/rules/basalt-charts.md +12 -0
  4. package/agent/rules/basalt-mantine.md +71 -6
  5. package/agent/rules/basalt-router.md +129 -30
  6. package/agent/rules/basalt-tokens.md +2 -1
  7. package/agent/skills/basalt-design/SKILL.md +2 -2
  8. package/dist/charts/kinds/DualPanel.d.ts +7 -0
  9. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  10. package/dist/charts/kinds/DualPanel.js +10 -3
  11. package/dist/charts/primitives/CartesianChart.d.ts +17 -0
  12. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  13. package/dist/charts/primitives/CartesianChart.js +5 -2
  14. package/dist/charts/primitives/ChartTooltip.d.ts +6 -1
  15. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  16. package/dist/charts/primitives/ChartTooltip.js +2 -1
  17. package/dist/charts/series.d.ts +5 -0
  18. package/dist/charts/series.d.ts.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +8 -0
  22. package/dist/nav/types.d.ts +110 -0
  23. package/dist/nav/types.d.ts.map +1 -0
  24. package/dist/nav/types.js +0 -0
  25. package/dist/router-tanstack/index.d.ts +9 -2
  26. package/dist/router-tanstack/index.d.ts.map +1 -1
  27. package/dist/router-tanstack/index.js +7 -0
  28. package/dist/router-tanstack/nav.d.ts +163 -0
  29. package/dist/router-tanstack/nav.d.ts.map +1 -0
  30. package/dist/router-tanstack/nav.js +38 -0
  31. package/dist/router-tanstack/nav.test.d.ts +2 -0
  32. package/dist/router-tanstack/nav.test.d.ts.map +1 -0
  33. package/dist/router-tanstack/search-param-store.d.ts +19 -9
  34. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  35. package/dist/router-tanstack/use-nav.d.ts +30 -0
  36. package/dist/router-tanstack/use-nav.d.ts.map +1 -0
  37. package/dist/router-tanstack/use-nav.js +71 -0
  38. package/dist/shell/app-breadcrumbs.d.ts +7 -9
  39. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  40. package/dist/shell/app-breadcrumbs.js +8 -4
  41. package/dist/shell/app-mobile-nav.d.ts +23 -24
  42. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  43. package/dist/shell/app-mobile-nav.js +290 -68
  44. package/dist/shell/app-mobile-nav.module.css +166 -11
  45. package/dist/shell/app-mobile-nav.module.css.d.ts +11 -2
  46. package/dist/shell/app-mobile-nav.test.d.ts +2 -0
  47. package/dist/shell/app-mobile-nav.test.d.ts.map +1 -0
  48. package/dist/shell/app-sidebar.d.ts +3 -28
  49. package/dist/shell/app-sidebar.d.ts.map +1 -1
  50. package/dist/shell/app-sidebar.js +40 -174
  51. package/dist/shell/app-sidebar.module.css +3 -2
  52. package/dist/shell/app-sidebar.module.css.d.ts +0 -2
  53. package/dist/shell/index.d.ts +22 -65
  54. package/dist/shell/index.d.ts.map +1 -1
  55. package/dist/shell/index.js +38 -76
  56. package/dist/shell/mobile-nav-model.d.ts +41 -0
  57. package/dist/shell/mobile-nav-model.d.ts.map +1 -0
  58. package/dist/shell/mobile-nav-model.js +244 -0
  59. package/dist/shell/mobile-nav-model.test.d.ts +2 -0
  60. package/dist/shell/mobile-nav-model.test.d.ts.map +1 -0
  61. package/dist/shell/nav-count-badge.d.ts +11 -0
  62. package/dist/shell/nav-count-badge.d.ts.map +1 -0
  63. package/dist/shell/nav-count-badge.js +29 -0
  64. package/dist/surfaces.d.ts +1 -1
  65. package/dist/surfaces.js +1 -1
  66. package/dist/theme/index.d.ts.map +1 -1
  67. package/dist/theme/index.js +8 -7
  68. package/dist/theme/nav-link.module.css +4 -2
  69. package/dist/tokens/index.d.ts.map +1 -1
  70. package/dist/tokens/index.js +2 -0
  71. package/dist/tokens/palette.d.ts +15 -0
  72. package/dist/tokens/palette.d.ts.map +1 -1
  73. package/dist/tokens/palette.js +13 -0
  74. package/dist/tokens.css +2 -0
  75. package/llms.txt +3 -3
  76. package/package.json +1 -1
  77. package/src/charts/kinds/DualPanel.test.tsx +81 -1
  78. package/src/charts/kinds/DualPanel.tsx +26 -4
  79. package/src/charts/primitives/CartesianChart.test.tsx +173 -1
  80. package/src/charts/primitives/CartesianChart.tsx +29 -5
  81. package/src/charts/primitives/ChartTooltip.test.tsx +22 -1
  82. package/src/charts/primitives/ChartTooltip.tsx +7 -1
  83. package/src/charts/series.ts +5 -0
  84. package/src/index.ts +15 -5
  85. package/src/nav/types.ts +115 -0
  86. package/src/router-tanstack/index.ts +21 -2
  87. package/src/router-tanstack/nav.test.ts +111 -0
  88. package/src/router-tanstack/nav.ts +205 -0
  89. package/src/router-tanstack/search-param-store.ts +19 -9
  90. package/src/router-tanstack/use-nav.ts +146 -0
  91. package/src/shell/app-breadcrumbs.tsx +21 -19
  92. package/src/shell/app-mobile-nav.module.css +166 -11
  93. package/src/shell/app-mobile-nav.module.css.d.ts +11 -2
  94. package/src/shell/app-mobile-nav.test.tsx +369 -0
  95. package/src/shell/app-mobile-nav.tsx +436 -102
  96. package/src/shell/app-sidebar.module.css +3 -2
  97. package/src/shell/app-sidebar.module.css.d.ts +0 -2
  98. package/src/shell/app-sidebar.test.tsx +6 -10
  99. package/src/shell/app-sidebar.tsx +49 -206
  100. package/src/shell/index.test.tsx +62 -7
  101. package/src/shell/index.tsx +105 -164
  102. package/src/shell/mobile-nav-model.test.ts +296 -0
  103. package/src/shell/mobile-nav-model.ts +389 -0
  104. package/src/shell/nav-count-badge.tsx +39 -0
  105. package/src/surfaces.ts +1 -1
  106. package/src/theme/density-relations.test.ts +19 -1
  107. package/src/theme/index.ts +8 -7
  108. package/src/theme/nav-link.module.css +4 -2
  109. package/src/theme/spacing.test.ts +11 -0
  110. package/src/tokens/density.test.ts +2 -0
  111. package/src/tokens/index.ts +8 -5
  112. package/src/tokens/palette.ts +16 -0
@@ -13,8 +13,8 @@ const currentStyle = {
13
13
  function AppBreadcrumbs({
14
14
  section,
15
15
  parent,
16
+ parentAnchor,
16
17
  parentHref,
17
- renderBreadcrumbLink,
18
18
  page
19
19
  }) {
20
20
  if (!page) return null;
@@ -23,11 +23,15 @@ function AppBreadcrumbs({
23
23
  /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: section }),
24
24
  /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
25
25
  ] }),
26
- parent && parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
27
- renderBreadcrumbLink ? renderBreadcrumbLink(parentHref, parent) : /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", href: parentHref, truncate: true, children: parent }),
26
+ parent && parentAnchor && /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", component: parentAnchor, truncate: true, children: parent }),
28
28
  /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
29
29
  ] }),
30
- parent && !parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
30
+ parent && !parentAnchor && parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", href: parentHref, truncate: true, children: parent }),
32
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
33
+ ] }),
34
+ parent && !parentAnchor && !parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
31
35
  /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: parent }),
32
36
  /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
33
37
  ] }),
@@ -1,26 +1,25 @@
1
- import type { MouseEvent, ReactNode } from 'react';
2
- export type MobileNavItem = {
3
- key: string;
4
- label: string;
5
- icon: ReactNode;
6
- href?: string;
7
- active?: boolean;
8
- onClick?: (e: MouseEvent) => void;
1
+ import type { ReactElement } from 'react';
2
+ import type { MobileNavConfig, MobileNavModel } from '../nav/types.js';
3
+ import type { BasaltAccountProps } from './account-types.js';
4
+ import type { SettingsMenuItem } from './index.js';
5
+ export type MobileNavProps = {
6
+ /** The projection from `projectMobileNav` — `BasaltShell` builds it, memoized. */
7
+ model: MobileNavModel;
8
+ config?: MobileNavConfig | undefined;
9
+ /** Rendered as FLAT ROWS in the More surface, never by mounting `SidebarAccount` (which opens
10
+ * its own `Menu` — a menu inside a menu). This is what makes the mobile sidebar drawer
11
+ * deletable: everything it used to hold is reachable from More. */
12
+ account?: BasaltAccountProps | undefined;
13
+ settingsMenuItems?: SettingsMenuItem[] | undefined;
9
14
  };
10
- export type MobileNavSection = {
11
- key: string;
12
- label: string;
13
- icon: ReactNode;
14
- active: boolean;
15
- items: MobileNavItem[];
16
- };
17
- /** Renders a single sheet row. The consumer's router `Link` lives here; `active` is precomputed. */
18
- export type MobileNavLinkRenderer = (item: MobileNavItem, opts: {
19
- active: boolean;
20
- }) => ReactNode;
21
- export declare function MobileNav({ sections, onOpenMore, renderNavLink, }: {
22
- sections: MobileNavSection[];
23
- onOpenMore: () => void;
24
- renderNavLink?: MobileNavLinkRenderer;
25
- }): import("react").JSX.Element;
15
+ /**
16
+ * How many rows `account` contributes to the More surface — DELEGATED to `accountRows` rather than
17
+ * re-derived, because `BasaltShell` needs this number BEFORE the projection runs (it feeds
18
+ * `extraMoreRows`, which picks `menu` vs `sheet` against `menuMax`) while only this module knows
19
+ * how many rows an account actually expands into. Two independent counts is how a "1 row" account
20
+ * shipped a 9-row menu into headroom sized for 6, and how a `loading` account — which renders NO
21
+ * rows — still conjured a More slot that opened empty.
22
+ */
23
+ export declare function accountRowCount(account: BasaltAccountProps | undefined): number;
24
+ export declare function MobileNav({ model, config, account, settingsMenuItems, }: MobileNavProps): ReactElement;
26
25
  //# sourceMappingURL=app-mobile-nav.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGlD,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,SAAS,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB,CAAA;AAED,oGAAoG;AACpG,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,KAAK,SAAS,CAAA;AAwBjG,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,UAAU,EACV,aAAa,GACd,EAAE;IACD,QAAQ,EAAE,gBAAgB,EAAE,CAAA;IAC5B,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,aAAa,CAAC,EAAE,qBAAqB,CAAA;CACtC,+BA+EA"}
1
+ {"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,YAAY,EAAa,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGf,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAiB/C,MAAM,MAAM,cAAc,GAAG;IAC3B,kFAAkF;IAClF,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;wEAEoE;IACpE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACxC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;CACnD,CAAA;AAkFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAE/E;AAsFD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,OAAO,EACP,iBAAiB,GAClB,EAAE,cAAc,GAAG,YAAY,CAsO/B"}
@@ -1,7 +1,11 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Box, Drawer, NavLink, Stack, Text, UnstyledButton } from "@mantine/core";
3
- import { useState } from "react";
2
+ import { Drawer, Menu, NavLink, ScrollArea, Stack, Text, UnstyledButton } from "@mantine/core";
3
+ import { useReducedMotion } from "@mantine/hooks";
4
+ import { Fragment as Fragment2, useState } from "react";
5
+ import { NavCountBadge } from "./nav-count-badge.js";
6
+ import { useBasaltSpacing } from "../theme/index.js";
4
7
  import classes from "./app-mobile-nav.module.css";
8
+ const MENU_MIDDLEWARES = { flip: false, shift: { padding: 8 } };
5
9
  function IconMore() {
6
10
  return /* @__PURE__ */ jsxs(
7
11
  "svg",
@@ -24,83 +28,301 @@ function IconMore() {
24
28
  }
25
29
  );
26
30
  }
31
+ function accountRows(account) {
32
+ if (!account || account.state.status === "loading") return [];
33
+ const actions = account.actions;
34
+ if (account.state.status === "unauthenticated") {
35
+ return [{ key: "basalt-account-sign-in", label: "Sign in", onClick: actions?.onSignIn }];
36
+ }
37
+ const rows2 = [];
38
+ if (actions?.onManageAccount) {
39
+ rows2.push({
40
+ key: "basalt-account-manage",
41
+ label: "Account settings",
42
+ onClick: actions.onManageAccount
43
+ });
44
+ }
45
+ if (actions?.onManageBilling) {
46
+ rows2.push({
47
+ key: "basalt-account-billing",
48
+ label: "Billing & payment",
49
+ onClick: actions.onManageBilling
50
+ });
51
+ }
52
+ if (account.state.plan?.isFree && actions?.onUpgrade) {
53
+ rows2.push({ key: "basalt-account-upgrade", label: "Upgrade", onClick: actions.onUpgrade });
54
+ }
55
+ for (const item of actions?.extraMenuItems ?? []) {
56
+ rows2.push({
57
+ key: item.key,
58
+ label: item.label,
59
+ icon: item.icon,
60
+ ...item.danger !== void 0 && { danger: item.danger },
61
+ onClick: item.onClick
62
+ });
63
+ }
64
+ if (actions?.onSignOut) {
65
+ const onSignOut = actions.onSignOut;
66
+ rows2.push({
67
+ key: "basalt-account-sign-out",
68
+ label: "Sign out",
69
+ danger: true,
70
+ onClick: () => void onSignOut()
71
+ });
72
+ }
73
+ return rows2;
74
+ }
75
+ function accountRowCount(account) {
76
+ return accountRows(account).length;
77
+ }
78
+ function settingsRows(items) {
79
+ return (items ?? []).map((item) => ({
80
+ key: item.key,
81
+ label: item.label,
82
+ icon: item.icon,
83
+ onClick: item.onClick
84
+ }));
85
+ }
86
+ const menuRow = (item, depth) => {
87
+ const onClick = (event) => {
88
+ if (item.disabled) {
89
+ event.preventDefault();
90
+ return;
91
+ }
92
+ item.onClick?.(event);
93
+ };
94
+ const shared = {
95
+ className: depth > 0 ? `${classes.menuItem} ${classes.menuItemNested}` : classes.menuItem,
96
+ leftSection: item.icon,
97
+ disabled: Boolean(item.disabled),
98
+ // Mantine emits `data-disabled` (a STYLING hook) and nothing else, so the ARIA state is ours
99
+ // to set — a disabled destination still renders (§2.3 rule 11), so it has to announce itself.
100
+ "aria-disabled": item.disabled || void 0,
101
+ "aria-current": item.active ? "page" : void 0,
102
+ rightSection: item.count ? /* @__PURE__ */ jsx(NavCountBadge, { count: item.count }) : void 0,
103
+ onClick
104
+ };
105
+ const Anchor = item.Anchor;
106
+ if (Anchor && !item.disabled) {
107
+ return /* @__PURE__ */ jsx(Menu.Item, { component: Anchor, ...shared, children: item.label }, item.key);
108
+ }
109
+ return /* @__PURE__ */ jsx(
110
+ Menu.Item,
111
+ {
112
+ component: "a",
113
+ ...item.href !== void 0 && !item.disabled && { href: item.href },
114
+ ...shared,
115
+ children: item.label
116
+ },
117
+ item.key
118
+ );
119
+ };
120
+ const rows = (items, depth, render) => items.flatMap((item) => [render(item, depth), ...rows(item.children ?? [], depth + 1, render)]);
121
+ const menuActionRow = (row) => /* @__PURE__ */ jsx(
122
+ Menu.Item,
123
+ {
124
+ className: classes.menuItem,
125
+ leftSection: row.icon,
126
+ ...row.danger ? { color: "red" } : {},
127
+ onClick: (event) => row.onClick?.(event),
128
+ children: row.label
129
+ },
130
+ row.key
131
+ );
132
+ const sheetGroupLabel = (group) => group.label ? /* @__PURE__ */ jsx(Text, { component: "div", className: classes.menuLabel, children: group.label }) : null;
27
133
  function MobileNav({
28
- sections,
29
- onOpenMore,
30
- renderNavLink
134
+ model,
135
+ config,
136
+ account,
137
+ settingsMenuItems
31
138
  }) {
32
- const [openSection, setOpenSection] = useState(null);
33
- return /* @__PURE__ */ jsxs(Fragment, { children: [
34
- /* @__PURE__ */ jsxs("nav", { className: classes.bar, children: [
35
- sections.map((section) => /* @__PURE__ */ jsxs(
36
- UnstyledButton,
37
- {
38
- onClick: () => setOpenSection(section),
39
- className: classes.tab,
40
- "data-active": section.active || void 0,
41
- "aria-current": section.active ? "page" : void 0,
42
- "aria-label": section.label,
43
- children: [
44
- section.icon,
45
- /* @__PURE__ */ jsx(Text, { className: classes.label, children: section.label })
46
- ]
47
- },
48
- section.key
49
- )),
50
- /* @__PURE__ */ jsxs(UnstyledButton, { onClick: onOpenMore, className: classes.tab, "aria-label": "More", children: [
51
- /* @__PURE__ */ jsx(IconMore, {}),
52
- /* @__PURE__ */ jsx(Text, { className: classes.label, children: "More" })
53
- ] })
139
+ const [openKey, setOpenKey] = useState(null);
140
+ const reduceMotion = useReducedMotion();
141
+ const { step } = useBasaltSpacing();
142
+ const close = () => setOpenKey(null);
143
+ const scrollToTop = () => {
144
+ const target = config?.getScrollElement?.() ?? document.scrollingElement ?? document.documentElement;
145
+ target.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" });
146
+ };
147
+ const extraRows = [...accountRows(account), ...settingsRows(settingsMenuItems)];
148
+ const hasCount = (slot) => slot.kind === "link" ? Boolean(slot.item.count) : slot.groups.some((g) => groupHasCount(g.items));
149
+ const tabInner = (slot) => /* @__PURE__ */ jsxs(Fragment, { children: [
150
+ /* @__PURE__ */ jsxs("span", { className: classes.tabIcon, children: [
151
+ slot.icon ?? (slot.kind !== "link" && slot.isMore ? /* @__PURE__ */ jsx(IconMore, {}) : null),
152
+ hasCount(slot) ? /* @__PURE__ */ jsx("span", { className: classes.tabDot, "aria-hidden": true }) : null
54
153
  ] }),
55
- /* @__PURE__ */ jsx(
154
+ /* @__PURE__ */ jsx(Text, { className: classes.label, children: slot.short })
155
+ ] });
156
+ const surfaceTab = (slot, onClick) => /* @__PURE__ */ jsx(
157
+ UnstyledButton,
158
+ {
159
+ className: classes.tab,
160
+ "data-active": slot.active || void 0,
161
+ "aria-current": slot.active ? "page" : void 0,
162
+ "aria-label": slot.label,
163
+ ...onClick ? { onClick } : {},
164
+ children: tabInner(slot)
165
+ }
166
+ );
167
+ const linkTab = (slot) => {
168
+ const onClick = (e) => {
169
+ if (slot.active) {
170
+ e.preventDefault();
171
+ scrollToTop();
172
+ }
173
+ slot.item.onClick?.(e);
174
+ };
175
+ const shared = {
176
+ className: classes.tab,
177
+ "data-active": slot.active || void 0,
178
+ "aria-current": slot.active ? "page" : void 0,
179
+ "aria-label": slot.label,
180
+ onClick
181
+ };
182
+ const Anchor = slot.item.Anchor;
183
+ if (Anchor) {
184
+ return /* @__PURE__ */ jsx(UnstyledButton, { component: Anchor, ...shared, children: tabInner(slot) }, slot.key);
185
+ }
186
+ return /* @__PURE__ */ jsx(
187
+ UnstyledButton,
188
+ {
189
+ component: "a",
190
+ ...slot.item.href !== void 0 && { href: slot.item.href },
191
+ ...shared,
192
+ children: tabInner(slot)
193
+ },
194
+ slot.key
195
+ );
196
+ };
197
+ const sheetRow = (item, depth) => {
198
+ const onClick = (e) => {
199
+ item.onClick?.(e);
200
+ close();
201
+ };
202
+ const shared = {
203
+ classNames: {
204
+ root: depth > 0 ? `${classes.row} ${classes.rowNested}` : classes.row
205
+ },
206
+ label: item.label,
207
+ leftSection: item.icon,
208
+ rightSection: item.count ? /* @__PURE__ */ jsx(NavCountBadge, { count: item.count }) : void 0,
209
+ active: Boolean(item.active),
210
+ disabled: Boolean(item.disabled),
211
+ // Same as the menu row above: `NavLink`'s `disabled` is a `mod`, i.e. `data-disabled` only.
212
+ "aria-disabled": item.disabled || void 0,
213
+ "aria-current": item.active ? "page" : void 0,
214
+ onClick
215
+ };
216
+ const Anchor = item.Anchor;
217
+ if (Anchor && !item.disabled) {
218
+ return /* @__PURE__ */ jsx(NavLink, { component: Anchor, ...shared }, item.key);
219
+ }
220
+ return /* @__PURE__ */ jsx(
221
+ NavLink,
222
+ {
223
+ component: "a",
224
+ ...item.href !== void 0 && !item.disabled && { href: item.href },
225
+ ...shared
226
+ },
227
+ item.key
228
+ );
229
+ };
230
+ const sheetActionRow = (row) => /* @__PURE__ */ jsx(
231
+ NavLink,
232
+ {
233
+ classNames: { root: classes.row },
234
+ label: row.label,
235
+ leftSection: row.icon,
236
+ ...row.danger ? { color: "red" } : {},
237
+ onClick: (event) => {
238
+ row.onClick?.(event);
239
+ close();
240
+ }
241
+ },
242
+ row.key
243
+ );
244
+ const menuSlot = (slot) => /* @__PURE__ */ jsxs(
245
+ Menu,
246
+ {
247
+ opened: openKey === slot.key,
248
+ onChange: (opened) => setOpenKey(opened ? slot.key : null),
249
+ position: slot.isMore ? "top-end" : "top",
250
+ offset: 8,
251
+ width: step.mobileNavMenuWidth,
252
+ withinPortal: true,
253
+ zIndex: 400,
254
+ middlewares: MENU_MIDDLEWARES,
255
+ trapFocus: true,
256
+ returnFocus: true,
257
+ closeOnItemClick: true,
258
+ closeOnClickOutside: true,
259
+ closeOnEscape: true,
260
+ menuItemTabIndex: 0,
261
+ transitionProps: {
262
+ transition: slot.isMore ? "pop-bottom-right" : "pop",
263
+ duration: reduceMotion ? 0 : 140,
264
+ exitDuration: reduceMotion ? 0 : 100
265
+ },
266
+ classNames: { dropdown: classes.menuDropdown, label: classes.menuLabel },
267
+ children: [
268
+ /* @__PURE__ */ jsx(Menu.Target, { children: surfaceTab(slot) }),
269
+ /* @__PURE__ */ jsxs(Menu.Dropdown, { children: [
270
+ slot.groups.flatMap((group) => [
271
+ ...group.label ? [/* @__PURE__ */ jsx(Menu.Label, { children: group.label }, `${group.key}-label`)] : [],
272
+ ...rows(group.items, 0, menuRow)
273
+ ]),
274
+ slot.isMore && extraRows.length > 0 && slotHasRows(slot) ? /* @__PURE__ */ jsx(Menu.Divider, {}) : null,
275
+ slot.isMore ? extraRows.map(menuActionRow) : null,
276
+ slot.isMore ? config?.moreExtra : null
277
+ ] })
278
+ ]
279
+ },
280
+ slot.key
281
+ );
282
+ const sheetSlot = (slot) => /* @__PURE__ */ jsxs(Fragment2, { children: [
283
+ surfaceTab(slot, () => setOpenKey(slot.key)),
284
+ /* @__PURE__ */ jsxs(
56
285
  Drawer,
57
286
  {
58
- opened: openSection !== null,
59
- onClose: () => setOpenSection(null),
287
+ opened: openKey === slot.key,
288
+ onClose: close,
60
289
  position: "bottom",
61
290
  size: "auto",
62
291
  padding: "md",
63
- title: openSection?.label,
292
+ title: slot.label,
64
293
  classNames: { content: classes.sheet, title: classes.sheetTitle },
65
- children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: openSection?.items.map((item) => {
66
- const handleClick = (e) => {
67
- item.onClick?.(e);
68
- setOpenSection(null);
69
- };
70
- const active = Boolean(item.active);
71
- if (renderNavLink) {
72
- return /* @__PURE__ */ jsx(Box, { onClick: () => setOpenSection(null), children: renderNavLink(item, { active }) }, item.key);
73
- }
74
- return item.href ? /* @__PURE__ */ jsx(
75
- NavLink,
76
- {
77
- classNames: { root: classes.row },
78
- component: "a",
79
- href: item.href,
80
- label: item.label,
81
- leftSection: item.icon,
82
- active,
83
- onClick: handleClick
84
- },
85
- item.key
86
- ) : /* @__PURE__ */ jsxs(
87
- UnstyledButton,
88
- {
89
- onClick: handleClick,
90
- className: classes.row,
91
- "data-active": active || void 0,
92
- children: [
93
- item.icon,
94
- /* @__PURE__ */ jsx(Text, { className: classes.rowLabel, children: item.label })
95
- ]
96
- },
97
- item.key
98
- );
99
- }) })
294
+ transitionProps: {
295
+ transition: "slide-up",
296
+ duration: reduceMotion ? 0 : 220,
297
+ timingFunction: "cubic-bezier(0.32, 0.72, 0, 1)"
298
+ },
299
+ children: [
300
+ /* @__PURE__ */ jsx("div", { className: classes.grabber, "aria-hidden": true }),
301
+ /* @__PURE__ */ jsx(ScrollArea.Autosize, { mah: "62dvh", type: "scroll", children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
302
+ slot.groups.map((group) => /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
303
+ sheetGroupLabel(group),
304
+ rows(group.items, 0, sheetRow)
305
+ ] }, group.key)),
306
+ slot.isMore && extraRows.length > 0 ? /* @__PURE__ */ jsx(Stack, { gap: 2, children: extraRows.map(sheetActionRow) }) : null,
307
+ slot.isMore ? config?.moreExtra : null
308
+ ] }) })
309
+ ]
100
310
  }
101
311
  )
102
- ] });
312
+ ] }, slot.key);
313
+ return /* @__PURE__ */ jsx("nav", { className: classes.bar, "aria-label": "Primary", children: model.slots.map((slot) => {
314
+ if (slot.kind === "link") return linkTab(slot);
315
+ if (slot.kind === "menu") return menuSlot(slot);
316
+ return sheetSlot(slot);
317
+ }) });
318
+ }
319
+ function slotHasRows(slot) {
320
+ return slot.groups.some((group) => group.items.length > 0);
321
+ }
322
+ function groupHasCount(items) {
323
+ return items.some((item) => Boolean(item.count) || groupHasCount(item.children ?? []));
103
324
  }
104
325
  export {
105
- MobileNav
326
+ MobileNav,
327
+ accountRowCount
106
328
  };