laif-ds 1.0.0 → 1.0.2

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.
@@ -1,159 +1,154 @@
1
1
  "use client";
2
- import { jsx as o, jsxs as y, Fragment as T } from "react/jsx-runtime";
3
- import * as F from "react";
4
- import { designTokens as u } from "../design-tokens.js";
5
- import { Icon as K } from "./icon.js";
6
- import { useSidebar as R } from "./sidebar.js";
7
- import { cn as i } from "../../lib/utils.js";
8
- const I = 6, w = /* @__PURE__ */ new Set(), c = (t) => {
9
- w.has(t) || (w.add(t), console.warn(t));
10
- }, $ = (t) => `${t.url} ${t.title}`, k = (t) => !!t.isActive || !!t.subItems?.some((n) => n.isActive), z = "md:hidden shrink-0 overflow-hidden bg-d-card border-d-border rounded-t-lg border-t pb-[env(safe-area-inset-bottom)]", D = "flex items-stretch p-1", m = i(
2
+ import { jsxs as d, jsx as n, Fragment as J } from "react/jsx-runtime";
3
+ import * as Q from "react";
4
+ import { designTokens as U } from "../design-tokens.js";
5
+ import { navTokens as T } from "../nav-tokens.js";
6
+ import { Icon as V } from "./icon.js";
7
+ import { warnOnce as i, someRowBadged as _, normalizeNavBadge as W, NavBadgeOverlay as Y, NavBadgeDot as w, NavBadgeSrLabel as Z } from "./nav-badge.js";
8
+ import { useSidebar as ee } from "./sidebar.js";
9
+ import { cn as r } from "../../lib/utils.js";
10
+ const O = 6, $ = (a) => `${a.url} ${a.title}`, A = (a) => !!a.isActive || !!a.subItems?.some((o) => o.isActive), te = "md:hidden shrink-0 overflow-hidden bg-d-card border-d-border rounded-t-lg border-t pb-[env(safe-area-inset-bottom)]", ae = "flex items-stretch p-1", g = r(
11
11
  // `min-h-11` (44px) written out rather than `sizes.touch`: that token also
12
- // carries `px-4`, and 16px of horizontal padding inside a ~61px cell would
13
- // leave 29px for the label. `min-h-`, never `h-`: a cell must be able to grow.
14
- // `py-1.5` and not `py-2.5`: the bar's breathing room is the `<nav>`'s 4px
15
- // inset, and paying for it twice would put the cell at 54px and the bar at
16
- // 63px — past the column budget for no legibility gained. It stayed at
17
- // `py-1.5` when the icon dropped to 16px, deliberately: `py-2` would have held
18
- // the bar at the 59px it had with a 20px glyph, and a lower bar was the point
19
- // of the smaller glyph, not a side effect of it. 12px of vertical padding
20
- // around 34px of content (16 + 2 + 16) puts the pill at 46px.
12
+ // carries `px-4` and `text-sm`, and 16px of horizontal padding inside a ~61px
13
+ // cell would leave 29px for the label. `min-h-`, never `h-`: a cell must be
14
+ // able to grow. `cursor-pointer` because v4's preflight sets
15
+ // `cursor: default` on `<button>`, so without it the Menu cell would feel
16
+ // inert next to the `<a>` cells.
21
17
  //
22
- // Which leaves this value boxed in from both directions, and tighter than it
23
- // was: upwards by the column budget, downwards by the 44px touch floor, now
24
- // 2px away instead of 6. Trim `py-1.5`, the icon or the label's line-height
25
- // any further and `min-h-11` takes the cell's height over from the padding —
26
- // at which point the class list stops describing what renders, silently.
27
- // Measure the rendered cell, not the padding.
28
- // `cursor-pointer` because the two cell elements must not diverge: v4's
29
- // preflight sets `cursor: default` on `<button>`, so without it the Menu cell
30
- // would feel inert next to the `<a>` cells on a desktop-narrow window.
31
- //
32
- // `flex-1 min-w-0` is the equal-width guarantee, and both halves are load
33
- // bearing. `flex-1` alone is `1 1 0%`, which distributes the row evenly only
34
- // as long as no cell's content forces it wider: a flex item's automatic
35
- // minimum size is its min-content width, so a cell whose contents cannot
36
- // shrink below the fair share silently takes more than it and every other
37
- // cell takes less. Here that floor is 24px — a `shrink-0` 16px icon plus
38
- // `px-1` — and the narrowest cell the budget produces is 52px (six cells at
39
- // 320px), so today the row is even by arithmetic. `min-w-0` makes it even by
40
- // construction instead, at any width and whatever a cell holds, which is what
41
- // the row's rhythm actually depends on. The label shrinks in exchange
42
- // (`w-full truncate`, whose `overflow-hidden` already floors its own
43
- // min-content size at 0) — the label is the right thing to spend.
44
- "flex min-h-11 min-w-0 flex-1 cursor-pointer flex-col items-center justify-center gap-0.5 px-1 py-1.5 text-xs",
45
- // The radius goes on every cell, not only the marked one: it costs nothing
46
- // while the fill is transparent, and it keeps the hover and press surfaces of
47
- // an inactive cell the same shape as the pill they can become.
48
- u.navItem.basePill,
49
- u.focusRing,
50
- // `ring-inset` is still right, for a new reason: the cells now touch each
51
- // other, not the dock's clipped corners, so a 3px outset ring would not be
52
- // shaved — it would spill over the neighbouring cell and read as if two cells
53
- // were focused.
18
+ // `min-w-0` beside `flex-1` is the equal-width guarantee: a flex item's
19
+ // automatic minimum size is its min-content width, so without it a cell whose
20
+ // contents cannot shrink below the fair share takes more than it and every
21
+ // other cell takes less. The label is what gives way instead.
22
+ "flex min-h-11 min-w-0 flex-1 cursor-pointer flex-col items-center justify-center gap-0.5 px-1 py-2.5 text-xs",
23
+ // On every cell, not only the marked one: it keeps the hover and press
24
+ // surfaces of an inactive cell the same shape as the pill they can become.
25
+ T.item.basePill,
26
+ U.focusRing,
27
+ // `ring-inset` because the cells touch each other: an outset ring would spill
28
+ // over the neighbour and read as if two cells were focused.
54
29
  "focus-visible:ring-inset"
55
- ), p = i(
30
+ ), h = r(
56
31
  "text-d-foreground font-medium",
57
32
  "[@media(hover:hover)_and_(pointer:fine)]:hover:bg-d-sidebar-accent [@media(hover:hover)_and_(pointer:fine)]:hover:text-d-sidebar-accent-foreground",
58
33
  "active:bg-d-sidebar-accent active:text-d-sidebar-accent-foreground"
59
- ), v = u.navItem.selectedPill, d = (t, n) => /* @__PURE__ */ y(T, { children: [
60
- /* @__PURE__ */ o(K, { name: t, size: "xs", className: "shrink-0" }),
61
- /* @__PURE__ */ o("span", { className: "w-full truncate text-center", children: n })
34
+ ), M = T.item.selectedPill, P = "relative flex size-5 shrink-0 items-center justify-center", c = (a, o) => /* @__PURE__ */ d("span", { className: P, children: [
35
+ /* @__PURE__ */ n(V, { name: a, size: "sm", className: "shrink-0" }),
36
+ o
37
+ ] }), ne = (a, o) => /* @__PURE__ */ d("span", { className: r(P, "[&>svg]:size-full"), children: [
38
+ a,
39
+ o
40
+ ] }), oe = "-top-1 -right-1.5", S = "absolute -top-1 -right-1", N = (a, o, m) => /* @__PURE__ */ d(J, { children: [
41
+ a,
42
+ /* @__PURE__ */ n("span", { className: "w-full truncate text-center", children: o }),
43
+ m
62
44
  ] });
63
- function J({
64
- items: t,
65
- showMenu: n = !0,
66
- menuLabel: A = "Menu",
67
- ariaLabel: B = "Main navigation",
68
- menuIconName: M = "Menu",
69
- linkComponent: b,
70
- linkProps: S = {},
71
- className: _,
72
- ...j
45
+ function pe({
46
+ items: a,
47
+ showMenu: o = !0,
48
+ menuLabel: m = "Menu",
49
+ ariaLabel: j = "Main navigation",
50
+ menuIcon: p = "Menu",
51
+ menuBadge: D = !1,
52
+ linkComponent: x,
53
+ linkProps: z = {},
54
+ className: E,
55
+ ...R
73
56
  }) {
74
- const { openMobile: f, setOpenMobile: h, mobileNavTarget: L, setMobileNavTarget: g } = R(), s = n ? I - 1 : I, r = t.slice(0, s);
75
- if (t.length > s) {
76
- const e = t.slice(s).map((a) => `"${a.title}"`).join(", ");
77
- c(
78
- `AppBottomNav: ${t.length} items passed, ${s} slots available. Dropped: ${e}.`
57
+ const { openMobile: I, setOpenMobile: B, mobileNavTarget: F, setMobileNavTarget: C } = ee(), u = o ? O - 1 : O, l = a.slice(0, u), v = a.slice(u);
58
+ if (v.length > 0) {
59
+ const e = v.map((t) => `"${t.title}"`).join(", ");
60
+ i(
61
+ `AppBottomNav: ${a.length} items passed, ${u} slots available. Dropped: ${e}.`
79
62
  );
80
63
  }
81
- const N = /* @__PURE__ */ new Set();
82
- for (const e of r) {
83
- const a = $(e);
84
- N.has(a) && c(
85
- `AppBottomNav: duplicate item "${a}" — one of them is ignored.`
86
- ), N.add(a);
64
+ const L = /* @__PURE__ */ new Set();
65
+ for (const e of l) {
66
+ const t = $(e);
67
+ L.has(t) && i(
68
+ `AppBottomNav: duplicate item "${t}" — one of them is ignored.`
69
+ ), L.add(t);
87
70
  }
88
- const l = r.findIndex(k);
89
- r.filter(k).length > 1 && c(
90
- `AppBottomNav: more than one active item — "${r[l].title}" wins.`
71
+ const b = l.findIndex(A);
72
+ l.filter(A).length > 1 && i(
73
+ `AppBottomNav: more than one active item — "${l[b].title}" wins.`
91
74
  );
92
- const x = n && l === -1, P = (e, a) => {
93
- const C = {
94
- className: i(
95
- m,
96
- a ? v : p
75
+ const K = D || _(v), G = (() => {
76
+ const e = K ? /* @__PURE__ */ n(w, { className: S }) : null;
77
+ if (typeof p == "string") return c(p, e);
78
+ const t = p();
79
+ return t == null ? c("Menu", e) : ne(t, e);
80
+ })(), k = (e) => {
81
+ const t = W(e.badge, "compact", e.title), s = /* @__PURE__ */ n(Z, { row: e, badge: t });
82
+ return t?.kind === "text" ? {
83
+ visual: /* @__PURE__ */ n(Y, { text: t.text, className: oe }),
84
+ srLabel: s
85
+ } : t?.kind === "dot" || _(e.subItems) ? { visual: /* @__PURE__ */ n(w, { className: S }), srLabel: s } : { visual: null, srLabel: s };
86
+ }, X = (e, t) => {
87
+ const s = {
88
+ className: r(
89
+ g,
90
+ t ? M : h
97
91
  ),
98
92
  "data-slot": "app-bottom-nav-item",
99
- "data-active": a ? "true" : void 0,
93
+ "data-active": t ? "true" : void 0,
100
94
  // Not set unconditionally: `truncate` does not alter the text node, so
101
- // the full title is already in the accessibility tree and a duplicating
102
- // `aria-label` is dead weight.
95
+ // the full title is already in the accessibility tree.
103
96
  "aria-label": e.ariaLabel,
104
- "aria-current": a ? "page" : void 0
105
- };
106
- return b ? /* @__PURE__ */ o(b, { href: e.url, ...C, ...S, children: d(e.iconName, e.title) }) : /* @__PURE__ */ o("a", { href: e.url, ...C, children: d(e.iconName, e.title) });
107
- }, O = (e, a) => (
108
- // A button is not a page, so no `aria-current` even when the indicator is here.
109
- // No `aria-controls` either — the drill panel's id is Radix-generated and
110
- // unreachable from the bar, and a dangling reference is worse than none —
111
- // and no `aria-haspopup`, which would promise arrow-key menu semantics the
112
- // drill-down does not implement.
113
- /* @__PURE__ */ o(
114
- "button",
115
- {
116
- type: "button",
117
- "data-slot": "app-bottom-nav-item",
118
- "data-active": a ? "true" : void 0,
119
- "aria-label": e.ariaLabel,
120
- "aria-expanded": f && L === e.title,
121
- className: i(
122
- m,
123
- a ? v : p
124
- ),
125
- onClick: () => {
126
- g(e.title), h(!0);
127
- },
128
- children: d(e.iconName, e.title)
129
- }
130
- )
131
- );
132
- return r.length === 0 && !n ? (c(
97
+ "aria-current": t ? "page" : void 0
98
+ }, { visual: f, srLabel: H } = k(e), y = N(
99
+ c(e.iconName, f),
100
+ e.title,
101
+ H
102
+ );
103
+ return x ? /* @__PURE__ */ n(x, { href: e.url, ...s, ...z, children: y }) : /* @__PURE__ */ n("a", { href: e.url, ...s, children: y });
104
+ }, q = (e, t) => {
105
+ const { visual: s, srLabel: f } = k(e);
106
+ return (
107
+ // A button is not a page, so no `aria-current` even when the indicator is
108
+ // here. No `aria-controls` — the drill panel's id is Radix-generated and
109
+ // unreachable from the bar — and no `aria-haspopup`, which would promise
110
+ // arrow-key menu semantics the drill-down does not implement.
111
+ /* @__PURE__ */ n(
112
+ "button",
113
+ {
114
+ type: "button",
115
+ "data-slot": "app-bottom-nav-item",
116
+ "data-active": t ? "true" : void 0,
117
+ "aria-label": e.ariaLabel,
118
+ "aria-expanded": I && F === e.title,
119
+ className: r(
120
+ g,
121
+ t ? M : h
122
+ ),
123
+ onClick: () => {
124
+ C(e.title), B(!0);
125
+ },
126
+ children: N(c(e.iconName, s), e.title, f)
127
+ }
128
+ )
129
+ );
130
+ };
131
+ return l.length === 0 && !o ? (i(
133
132
  "AppBottomNav: no items and showMenu={false} — nothing to render, the bar is omitted."
134
- ), null) : /* @__PURE__ */ o(
133
+ ), null) : /* @__PURE__ */ n(
135
134
  "div",
136
135
  {
137
136
  "data-slot": "app-bottom-nav",
138
- className: i(z, _),
139
- ...j,
140
- children: /* @__PURE__ */ y("nav", { "aria-label": B, className: D, children: [
141
- r.map((e, a) => /* @__PURE__ */ o(F.Fragment, { children: e.subItems?.length ? O(e, a === l) : P(e, a === l) }, $(e))),
142
- n && /* @__PURE__ */ o(
137
+ className: r(te, E),
138
+ ...R,
139
+ children: /* @__PURE__ */ d("nav", { "aria-label": j, className: ae, children: [
140
+ l.map((e, t) => /* @__PURE__ */ n(Q.Fragment, { children: e.subItems?.length ? q(e, t === b) : X(e, t === b) }, $(e))),
141
+ o && /* @__PURE__ */ n(
143
142
  "button",
144
143
  {
145
144
  type: "button",
146
145
  "data-slot": "app-bottom-nav-item",
147
- "data-active": x ? "true" : void 0,
148
- "aria-expanded": f,
149
- className: i(
150
- m,
151
- x ? v : p
152
- ),
146
+ "aria-expanded": I,
147
+ className: r(g, h),
153
148
  onClick: () => {
154
- g(null), h(!0);
149
+ C(null), B(!0);
155
150
  },
156
- children: d(M, A)
151
+ children: N(G, m)
157
152
  }
158
153
  )
159
154
  ] })
@@ -161,5 +156,5 @@ function J({
161
156
  );
162
157
  }
163
158
  export {
164
- J as AppBottomNav
159
+ pe as AppBottomNav
165
160
  };
@@ -1,26 +1,28 @@
1
1
  "use client";
2
- import { jsx as n, jsxs as f, Fragment as L } from "react/jsx-runtime";
3
- import * as l from "react";
4
- import { designTokens as c } from "../design-tokens.js";
2
+ import { jsx as n, jsxs as u, Fragment as L } from "react/jsx-runtime";
3
+ import * as i from "react";
4
+ import { designTokens as h } from "../design-tokens.js";
5
+ import { navTokens as j } from "../nav-tokens.js";
5
6
  import { Icon as R } from "./icon.js";
6
- import { useSidebar as U } from "./sidebar.js";
7
- import { cn as d } from "../../lib/utils.js";
8
- const v = (t, i) => `${t} ${i.url} ${i.title}`, V = (t, i, a) => {
7
+ import { normalizeNavBadge as D, renderInlineNavBadge as G, NavBadgeSrLabel as O, someRowBadged as Y } from "./nav-badge.js";
8
+ import { useSidebar as Z } from "./sidebar.js";
9
+ import { cn as f } from "../../lib/utils.js";
10
+ const v = (t, l) => `${t} ${l.url} ${l.title}`, _ = (t, l, a) => {
9
11
  if (!t) return null;
10
- for (const h of [...i, ...a ?? []]) {
11
- const u = h.items.find(
12
- (b) => b.title === t && b.subItems?.length
12
+ for (const d of [...l, ...a ?? []]) {
13
+ const c = d.items.find(
14
+ (p) => p.title === t && p.subItems?.length
13
15
  );
14
- if (u) return { key: v(h.title, u), item: u };
16
+ if (c) return { key: v(d.title, c), item: c };
15
17
  }
16
18
  return console.warn(
17
19
  `AppSidebarMobile: no navigation item with sub-items titled "${t}" — opening at level 0.`
18
20
  ), null;
19
- }, W = (t) => t.defaultPrevented || t.metaKey || t.ctrlKey || t.shiftKey || t.altKey || t.button !== 0, M = d(
21
+ }, ee = (t) => t.defaultPrevented || t.metaKey || t.ctrlKey || t.shiftKey || t.altKey || t.button !== 0, M = f(
20
22
  "flex w-full items-center gap-3 text-left",
21
- c.sizes.touch,
22
- c.navItem.base,
23
- c.focusRing,
23
+ h.sizes.touch,
24
+ j.item.base,
25
+ h.focusRing,
24
26
  // The card clips its children (`overflow-hidden` is what keeps the rounded
25
27
  // corners on the first and last row), so an outset ring would be cut off.
26
28
  "focus-visible:ring-inset",
@@ -28,134 +30,143 @@ const v = (t, i) => `${t} ${i.url} ${i.title}`, V = (t, i, a) => {
28
30
  // state is the only immediate feedback a finger gets. Not
29
31
  // `interaction.active` — that is the button squash, wrong on a list row.
30
32
  "active:bg-d-accent"
31
- ), $ = (t) => t ? c.navItem.selected : c.interaction.hover, X = "text-d-foreground px-4 pb-2 text-xs font-semibold tracking-wide uppercase", E = d(
33
+ ), T = (t) => t ? j.item.selected : h.interaction.hover, te = "text-d-foreground flex items-center px-4 pb-2 text-xs font-semibold tracking-wide uppercase", $ = f(
32
34
  "divide-d-border border-d-border bg-d-card divide-y overflow-hidden border",
33
- c.radius.lg
34
- ), Y = (t) => {
35
+ h.radius.lg
36
+ ), ne = (t) => {
35
37
  if (t.icon) {
36
- const i = t.icon;
37
- return /* @__PURE__ */ n(i, { className: "size-5 shrink-0" });
38
+ const l = t.icon;
39
+ return /* @__PURE__ */ n(l, { className: "size-5 shrink-0" });
38
40
  }
39
41
  return t.iconName ? /* @__PURE__ */ n(R, { name: t.iconName, size: "sm", className: "shrink-0" }) : null;
40
- }, T = (t) => !!t.iconName || !!t.icon, I = (t, { showIconColumn: i, hasChildren: a }) => /* @__PURE__ */ f(L, { children: [
41
- i && (Y(t) ?? // Empty placeholder, not an initial: on a wide row the label is right
42
- // there, so an initial would only add noise. Desktop icon mode needs
43
- // the fallback because there the icon is the only affordance.
44
- /* @__PURE__ */ n("span", { "aria-hidden": "true", className: "size-5 shrink-0" })),
45
- /* @__PURE__ */ n("span", { className: "flex-1", children: t.title }),
46
- a && /* @__PURE__ */ n(
47
- R,
48
- {
49
- name: "ChevronRight",
50
- size: "sm",
51
- className: "text-d-muted-foreground shrink-0"
52
- }
53
- )
54
- ] });
55
- function se({
42
+ }, E = (t) => !!t.iconName || !!t.icon, y = (t, { showIconColumn: l, hasChildren: a }) => {
43
+ const d = D(t.badge, "full", t.title), c = d ?? (a && Y("subItems" in t ? t.subItems : void 0) ? { kind: "dot" } : null);
44
+ return /* @__PURE__ */ u(L, { children: [
45
+ l && (ne(t) ?? // Empty placeholder, not an initial: on a wide row the label is right
46
+ // there, so an initial would only add noise. Desktop icon mode needs
47
+ // the fallback because there the icon is the only affordance.
48
+ /* @__PURE__ */ n("span", { "aria-hidden": "true", className: "size-5 shrink-0" })),
49
+ /* @__PURE__ */ n("span", { className: "flex-1", children: t.title }),
50
+ /* @__PURE__ */ n(O, { row: t, badge: d }),
51
+ G(c),
52
+ a && /* @__PURE__ */ n(
53
+ R,
54
+ {
55
+ name: "ChevronRight",
56
+ size: "sm",
57
+ className: "text-d-muted-foreground shrink-0"
58
+ }
59
+ )
60
+ ] });
61
+ };
62
+ function de({
56
63
  navigation: t,
57
- navigationFooter: i,
64
+ navigationFooter: l,
58
65
  headerContent: a,
59
- footerContent: h,
60
- linkComponent: u,
61
- linkProps: b = {}
66
+ footerContent: d,
67
+ linkComponent: c,
68
+ linkProps: p = {}
62
69
  }) {
63
- const { openMobile: C, setOpenMobile: j, mobileNavTarget: D } = U(), [r, x] = l.useState(
64
- () => V(D, t, i)
65
- ), [G, N] = l.useState(!1), g = l.useRef(!0), w = (!!r || G) && !(g.current && r);
66
- l.useEffect(() => {
70
+ const { openMobile: B, setOpenMobile: P, mobileNavTarget: q } = Z(), [r, x] = i.useState(
71
+ () => _(q, t, l)
72
+ ), [F, N] = i.useState(!1), g = i.useRef(!0), C = (!!r || F) && !(g.current && r);
73
+ i.useEffect(() => {
67
74
  g.current = !1;
68
- }, []), l.useEffect(() => {
69
- C || (x(null), N(!1));
70
- }, [C]);
71
- const B = l.useRef(null), k = l.useRef(/* @__PURE__ */ new Map()), y = l.useRef(null), O = (e) => (s) => {
75
+ }, []), i.useEffect(() => {
76
+ B || (x(null), N(!1));
77
+ }, [B]);
78
+ const w = i.useRef(null), k = i.useRef(/* @__PURE__ */ new Map()), I = i.useRef(null), H = (e) => (s) => {
72
79
  s ? k.current.set(e, s) : k.current.delete(e);
73
80
  };
74
- l.useLayoutEffect(() => {
81
+ i.useLayoutEffect(() => {
75
82
  if (r) {
76
83
  if (g.current) return;
77
- B.current?.focus();
84
+ w.current?.focus();
78
85
  return;
79
86
  }
80
- const e = y.current;
81
- e && (y.current = null, k.current.get(e)?.focus());
87
+ const e = I.current;
88
+ e && (I.current = null, k.current.get(e)?.focus());
82
89
  }, [r]);
83
- const P = (e) => {
90
+ const J = (e) => {
84
91
  N(!1), x(e);
85
- }, q = () => {
86
- y.current = r?.key ?? null, N(!0), x(null);
87
- }, A = (e, s) => {
88
- const m = d(M, $(e.isActive)), o = (p) => {
89
- W(p) || j(!1);
92
+ }, Q = () => {
93
+ I.current = r?.key ?? null, N(!0), x(null);
94
+ }, S = (e, s) => {
95
+ const b = f(M, T(e.isActive)), o = (m) => {
96
+ ee(m) || P(!1);
90
97
  };
91
- return u ? /* @__PURE__ */ n(
92
- u,
98
+ return c ? /* @__PURE__ */ n(
99
+ c,
93
100
  {
94
101
  href: e.url,
95
- className: m,
102
+ className: b,
96
103
  "aria-current": e.isActive ? "page" : void 0,
97
104
  onClick: o,
98
- ...b,
99
- children: I(e, s)
105
+ ...p,
106
+ children: y(e, s)
100
107
  }
101
108
  ) : /* @__PURE__ */ n(
102
109
  "a",
103
110
  {
104
111
  href: e.url,
105
- className: m,
112
+ className: b,
106
113
  "aria-current": e.isActive ? "page" : void 0,
107
114
  onClick: o,
108
- children: I(e, s)
115
+ children: y(e, s)
109
116
  }
110
117
  );
111
- }, F = (e, s, m) => {
112
- const o = v(e.title, s), p = !!s.subItems?.length, K = { showIconColumn: m, hasChildren: p }, J = s.isActive || !!s.subItems?.some((Q) => Q.isActive);
113
- return p ? /* @__PURE__ */ n(
118
+ }, U = (e, s, b) => {
119
+ const o = v(e.title, s), m = !!s.subItems?.length, K = { showIconColumn: b, hasChildren: m }, W = s.isActive || !!s.subItems?.some((X) => X.isActive);
120
+ return m ? /* @__PURE__ */ n(
114
121
  "button",
115
122
  {
116
- ref: O(o),
123
+ ref: H(o),
117
124
  type: "button",
118
- className: d(M, $(J)),
119
- onClick: () => P({ key: o, item: s }),
120
- children: I(s, K)
125
+ className: f(M, T(W)),
126
+ onClick: () => J({ key: o, item: s }),
127
+ children: y(s, K)
121
128
  }
122
- ) : A(s, K);
123
- }, S = (e, s = !0) => {
124
- const m = e.items.some(T);
125
- return /* @__PURE__ */ f("section", { children: [
126
- s && e.title && /* @__PURE__ */ n("h3", { className: X, children: e.title }),
127
- /* @__PURE__ */ n("ul", { className: E, children: e.items.map((o) => /* @__PURE__ */ n("li", { children: F(e, o, m) }, v(e.title, o))) })
129
+ ) : S(s, K);
130
+ }, A = (e, s = !0) => {
131
+ const b = e.items.some(E), o = D(e.badge, "full", e.title);
132
+ return /* @__PURE__ */ u("section", { children: [
133
+ s && e.title && /* @__PURE__ */ u("h3", { className: te, children: [
134
+ /* @__PURE__ */ n("span", { className: "min-w-0 flex-1", children: e.title }),
135
+ /* @__PURE__ */ n(O, { row: e, badge: o }),
136
+ G(o)
137
+ ] }),
138
+ /* @__PURE__ */ n("ul", { className: $, children: e.items.map((m) => /* @__PURE__ */ n("li", { children: U(e, m, b) }, v(e.title, m))) })
128
139
  ] }, e.title);
129
- }, z = r?.item.subItems ?? [], H = z.some(T);
140
+ }, z = r?.item.subItems ?? [], V = z.some(E);
130
141
  return /* @__PURE__ */ n(
131
142
  "div",
132
143
  {
133
144
  "data-slot": "app-sidebar-mobile",
134
- className: d(
145
+ className: f(
135
146
  "bg-d-muted text-d-foreground flex min-h-0 flex-1 flex-col",
136
147
  // The home indicator overlaps the bottom of the viewport, and
137
148
  // `footerContent` is the last thing in the panel — so the reserve lives
138
149
  // here, not on the scroll area above it.
139
150
  "pb-[max(1rem,env(safe-area-inset-bottom))]"
140
151
  ),
141
- children: /* @__PURE__ */ f("div", { className: "flex min-h-0 w-full flex-1 flex-col sm:mx-auto sm:max-w-md", children: [
142
- r ? /* @__PURE__ */ f(
152
+ children: /* @__PURE__ */ u("div", { className: "flex min-h-0 w-full flex-1 flex-col sm:mx-auto sm:max-w-md", children: [
153
+ r ? /* @__PURE__ */ u(
143
154
  "div",
144
155
  {
145
156
  "data-sidebar": "header",
146
- className: "border-d-border shrink-0 border-b pb-2",
157
+ className: "border-d-border shrink-0 border-b pr-12 pb-2",
147
158
  children: [
148
- /* @__PURE__ */ n("div", { className: "p-2", children: /* @__PURE__ */ f(
159
+ /* @__PURE__ */ n("div", { className: "p-2", children: /* @__PURE__ */ u(
149
160
  "button",
150
161
  {
151
162
  type: "button",
152
163
  "aria-label": "Back to menu",
153
- onClick: q,
154
- className: d(
164
+ onClick: Q,
165
+ className: f(
155
166
  "flex min-h-11 items-center gap-1 pr-3 pl-2",
156
- c.radius.default,
157
- c.focusRing,
158
- c.interaction.hover,
167
+ h.radius.default,
168
+ h.focusRing,
169
+ h.interaction.hover,
159
170
  "active:bg-d-accent"
160
171
  ),
161
172
  children: [
@@ -167,7 +178,7 @@ function se({
167
178
  /* @__PURE__ */ n(
168
179
  "h2",
169
180
  {
170
- ref: B,
181
+ ref: w,
171
182
  tabIndex: -1,
172
183
  className: "px-4 text-base font-semibold outline-none",
173
184
  children: r.item.title
@@ -175,46 +186,50 @@ function se({
175
186
  )
176
187
  ]
177
188
  }
178
- ) : a && // `px-4` so the consumer's header lands on the same grid as the cards.
179
- /* @__PURE__ */ n("div", { "data-sidebar": "header", className: "shrink-0 px-4 py-2", children: a }),
189
+ ) : a && /* @__PURE__ */ n("div", { "data-sidebar": "header", className: "shrink-0 px-12 py-2", children: a }),
180
190
  /* @__PURE__ */ n(
181
191
  "nav",
182
192
  {
183
- className: d(
184
- "min-h-0 flex-1 space-y-6 overflow-y-auto overscroll-contain px-4 pb-4",
193
+ className: f(
194
+ // A column, so the footer block can claim the leftover space with
195
+ // `mt-auto` and sit at the bottom the way the desktop panel's does.
196
+ "flex min-h-0 flex-1 flex-col space-y-6 overflow-y-auto overscroll-contain px-4 pb-4",
185
197
  // The sheet's close button floats over the top-right corner. A
186
198
  // header (or the back bar) sits under it and gets `pr-12`; with
187
199
  // neither, the list itself has to clear its 52px.
188
200
  r || a ? "pt-3" : "pt-14",
189
- w && "animate-in fade-in-0 duration-200 motion-reduce:animate-none",
190
- w && (r ? "slide-in-from-right-4" : "slide-in-from-left-4")
201
+ C && "animate-in fade-in-0 duration-200 motion-reduce:animate-none",
202
+ C && (r ? "slide-in-from-right-4" : "slide-in-from-left-4")
191
203
  ),
192
204
  children: r ? (
193
205
  // One card, no group heading: the level's title is already in the
194
206
  // back bar.
195
- /* @__PURE__ */ n("ul", { className: E, children: z.map((e) => /* @__PURE__ */ n("li", { children: A(e, {
196
- showIconColumn: H,
207
+ /* @__PURE__ */ n("ul", { className: $, children: z.map((e) => /* @__PURE__ */ n("li", { children: S(e, {
208
+ showIconColumn: V,
197
209
  hasChildren: !1
198
210
  }) }, v(r.key, e))) })
199
- ) : /* @__PURE__ */ f(L, { children: [
211
+ ) : /* @__PURE__ */ u(L, { children: [
200
212
  t.map(
201
- (e) => S(e, t.length > 1)
213
+ (e) => A(e, t.length > 1)
202
214
  ),
203
- i?.length ? (
215
+ l?.length ? (
204
216
  // Detached by a wider gap: the "system" block at the bottom of a
205
217
  // native Settings list. `pt-6` on top of the `space-y-6` — 8px
206
- // read as an accident, not as a separation.
207
- /* @__PURE__ */ n("div", { className: "space-y-6 pt-6", children: i.map((e) => S(e)) })
218
+ // read as an accident, not as a separation. `mt-auto` eats the
219
+ // leftover height when the list is short, so the block lands at
220
+ // the bottom of the panel; with a long list it resolves to 0 and
221
+ // the block just scrolls after the last group.
222
+ /* @__PURE__ */ n("div", { className: "mt-auto space-y-6 pt-6", children: l.map((e) => A(e)) })
208
223
  ) : null
209
224
  ] })
210
225
  },
211
226
  r ? `level-1 ${r.key}` : "level-0"
212
227
  ),
213
- h
228
+ d
214
229
  ] })
215
230
  }
216
231
  );
217
232
  }
218
233
  export {
219
- se as AppSidebarMobile
234
+ de as AppSidebarMobile
220
235
  };