@burdenoff/fe-libs 2026.1027.6 → 2026.1027.7

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.
@@ -26,7 +26,15 @@ export interface MobileTabBarProps {
26
26
  */
27
27
  hideWhenKeyboardOpen?: boolean;
28
28
  className?: string;
29
+ /**
30
+ * Controlled open state for the More sheet, so a header hamburger button
31
+ * can open the SAME sheet instead of a second, divergent one. Omit to let
32
+ * the tab bar manage it internally (the default, tap-the-More-tab-only
33
+ * behavior).
34
+ */
35
+ open?: boolean;
36
+ onOpenChange?: (open: boolean) => void;
29
37
  }
30
- export declare function MobileTabBar({ appName, logo, groupOrder, groupLabels, maxTabs, moreLabel, mobileActions, hideWhenKeyboardOpen, className, }: MobileTabBarProps): import('react/jsx-runtime').JSX.Element;
38
+ export declare function MobileTabBar({ appName, logo, groupOrder, groupLabels, maxTabs, moreLabel, mobileActions, hideWhenKeyboardOpen, className, open, onOpenChange, }: MobileTabBarProps): import('react/jsx-runtime').JSX.Element;
31
39
  export { useMobileTabBarVisible } from './mobileTabBar/useMobileTabBarVisible';
32
40
  export { selectMobileTabs } from './mobileTabBar/selectTabs';
@@ -1,23 +1,23 @@
1
1
  import { cn as e } from "../lib/utils.js";
2
2
  import { BottomSheet as t, BottomSheetContent as n, BottomSheetHeader as r, BottomSheetTitle as i } from "../ui/bottom-sheet.js";
3
3
  import { getNativeBridge as a } from "../shared/native/bridge.js";
4
- import { useShellLabel as o } from "../shared/providers/shell/useShellLabel.js";
5
- import { appendContextToPath as s, useCurrentContextSearchParams as c } from "../shared/hooks/useCurrentContextSearchParams.js";
4
+ import { useShellLabel as ee } from "../shared/providers/shell/useShellLabel.js";
5
+ import { appendContextToPath as o, useCurrentContextSearchParams as s } from "../shared/hooks/useCurrentContextSearchParams.js";
6
6
  import "./sideNavigation/navVisibility.js";
7
- import { useVisibleNavItems as l } from "./mobileTabBar/useVisibleNavItems.js";
8
- import { isItemOrChildActive as u } from "./sideNavigation/helpers.js";
9
- import { resolveTabPath as d, selectMobileTabs as f } from "./mobileTabBar/selectTabs.js";
7
+ import { useVisibleNavItems as c } from "./mobileTabBar/useVisibleNavItems.js";
8
+ import { isItemOrChildActive as l } from "./sideNavigation/helpers.js";
9
+ import { resolveTabPath as u, selectMobileTabs as d } from "./mobileTabBar/selectTabs.js";
10
10
  import "./mobileTabBar/useMobileTabBarVisible.js";
11
- import { useCallback as p, useEffect as m, useLayoutEffect as h, useMemo as g, useRef as _, useState as v } from "react";
12
- import { MoreHorizontal as y } from "lucide-react";
13
- import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
14
- import { useLocation as C, useNavigate as w } from "react-router";
11
+ import { useCallback as f, useEffect as p, useLayoutEffect as m, useMemo as h, useRef as g, useState as te } from "react";
12
+ import { MoreHorizontal as _ } from "lucide-react";
13
+ import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
14
+ import { useLocation as x, useNavigate as S } from "react-router";
15
15
  //#region src/chrome/MobileTabBar.tsx
16
- var T = "--mobile-tabbar-height";
17
- function E() {
16
+ var C = "--mobile-tabbar-height";
17
+ function w() {
18
18
  a()?.selection?.();
19
19
  }
20
- function D(e, t, n) {
20
+ function T(e, t, n) {
21
21
  let r = /* @__PURE__ */ new Map();
22
22
  for (let t of e) {
23
23
  if (t.id.startsWith("__nav_more__:") && t.children) {
@@ -39,42 +39,42 @@ function D(e, t, n) {
39
39
  items: r.get(e) ?? []
40
40
  }));
41
41
  }
42
- function O({ appName: a, logo: O, groupOrder: A = [], groupLabels: j = {}, maxTabs: M = 4, moreLabel: N, mobileActions: P, hideWhenKeyboardOpen: F = !0, className: I }) {
43
- let L = o(), R = N ?? L("nav.more", "More"), z = w(), B = C(), V = c(), { items: H } = l(), [U, W] = v(!1), G = _(null), { tabs: K, more: q, hasMore: J } = g(() => f(H, M), [H, M]), Y = g(() => D(q, A, j), [
42
+ function E({ appName: a, logo: E, groupOrder: O = [], groupLabels: k = {}, maxTabs: A = 4, moreLabel: ne, mobileActions: j, hideWhenKeyboardOpen: re = !0, className: M, open: N, onOpenChange: P }) {
43
+ let F = ee(), I = ne ?? F("nav.more", "More"), L = S(), R = x(), z = s(), { items: B } = c(), [V, H] = te(!1), U = N ?? V, W = P ?? H, G = g(null), { tabs: K, more: q, hasMore: J } = h(() => d(B, A), [B, A]), Y = h(() => T(q, O, k), [
44
44
  q,
45
- A,
46
- j
47
- ]), X = !!P && P.length > 0, Z = J || X;
48
- h(() => {
45
+ O,
46
+ k
47
+ ]), X = !!j && j.length > 0, Z = J || X;
48
+ m(() => {
49
49
  let e = G.current;
50
50
  if (!e || typeof window > "u") return;
51
- let t = document.documentElement, n = () => t.style.setProperty(T, `${e.offsetHeight}px`);
51
+ let t = document.documentElement, n = () => t.style.setProperty(C, `${e.offsetHeight}px`);
52
52
  n();
53
53
  let r = typeof ResizeObserver < "u" ? new ResizeObserver(n) : null;
54
54
  return r?.observe(e), () => {
55
- r?.disconnect(), t.style.removeProperty(T);
55
+ r?.disconnect(), t.style.removeProperty(C);
56
56
  };
57
- }, []), m(() => {
57
+ }, []), p(() => {
58
58
  W(!1);
59
- }, [B.pathname]);
60
- let Q = p((e) => {
61
- E(), z(s(e, V));
62
- }, [z, V]), $ = g(() => !!J && q.some((e) => u(e, B.pathname)), [
59
+ }, [R.pathname, W]);
60
+ let Q = f((e) => {
61
+ w(), L(o(e, z));
62
+ }, [L, z]), $ = h(() => !!J && q.some((e) => l(e, R.pathname)), [
63
63
  J,
64
64
  q,
65
- B.pathname
65
+ R.pathname
66
66
  ]);
67
- return /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ S("nav", {
67
+ return /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ b("nav", {
68
68
  ref: G,
69
69
  role: "tablist",
70
- "aria-label": L("nav.primaryNavigation", `${a} navigation`),
70
+ "aria-label": F("nav.primaryNavigation", `${a} navigation`),
71
71
  "data-mobile-tabbar": "",
72
72
  "data-shell-chrome": "",
73
- "data-keyboard-keep": F ? void 0 : "",
74
- className: e("fixed inset-x-0 bottom-0 z-[60] flex items-stretch", "bg-bg-surface text-text-primary", I),
73
+ "data-keyboard-keep": re ? void 0 : "",
74
+ className: e("fixed inset-x-0 bottom-0 z-[60] flex items-stretch", "bg-bg-surface text-text-primary", M),
75
75
  children: [K.map((t) => {
76
- let n = u(t, B.pathname);
77
- return /* @__PURE__ */ S("button", {
76
+ let n = l(t, R.pathname);
77
+ return /* @__PURE__ */ b("button", {
78
78
  type: "button",
79
79
  role: "tab",
80
80
  "aria-selected": n,
@@ -83,91 +83,91 @@ function O({ appName: a, logo: O, groupOrder: A = [], groupLabels: j = {}, maxTa
83
83
  "data-mobile-tab": t.id,
84
84
  className: e("mobile-tab relative flex min-w-0 flex-1 flex-col items-center justify-center gap-0.5", "text-text-muted transition-colors duration-150", n && "text-text-link"),
85
85
  onClick: () => {
86
- n && !t.children?.length || Q(d(t));
86
+ n && !t.children?.length || Q(u(t));
87
87
  },
88
- children: [/* @__PURE__ */ S("span", {
88
+ children: [/* @__PURE__ */ b("span", {
89
89
  className: "mobile-tab__icon relative inline-flex items-center justify-center",
90
90
  "aria-hidden": "true",
91
- children: [t.icon, t.badge ? /* @__PURE__ */ x("span", {
91
+ children: [t.icon, t.badge ? /* @__PURE__ */ y("span", {
92
92
  className: "absolute -top-1 -right-2 min-w-4 rounded-full bg-status-error-bg px-1 text-[10px] font-semibold leading-4 text-status-error-text",
93
93
  children: t.badge > 99 ? "99+" : t.badge
94
94
  }) : null]
95
- }), /* @__PURE__ */ x("span", {
95
+ }), /* @__PURE__ */ y("span", {
96
96
  className: "mobile-tab__label max-w-full truncate",
97
97
  children: t.label
98
98
  })]
99
99
  }, t.id);
100
- }), Z ? /* @__PURE__ */ S("button", {
100
+ }), Z ? /* @__PURE__ */ b("button", {
101
101
  type: "button",
102
102
  role: "tab",
103
103
  "aria-selected": $,
104
104
  "aria-haspopup": "dialog",
105
105
  "aria-expanded": U,
106
- "aria-label": R,
106
+ "aria-label": I,
107
107
  "data-active": $ ? "" : void 0,
108
108
  "data-mobile-tab": "__more",
109
109
  className: e("mobile-tab relative flex min-w-0 flex-1 flex-col items-center justify-center gap-0.5", "text-text-muted transition-colors duration-150", $ && "text-text-link"),
110
110
  onClick: () => {
111
- E(), W(!0);
111
+ w(), W(!0);
112
112
  },
113
- children: [/* @__PURE__ */ x("span", {
113
+ children: [/* @__PURE__ */ y("span", {
114
114
  className: "mobile-tab__icon inline-flex items-center justify-center",
115
115
  "aria-hidden": "true",
116
- children: /* @__PURE__ */ x(y, {})
117
- }), /* @__PURE__ */ x("span", {
116
+ children: /* @__PURE__ */ y(_, {})
117
+ }), /* @__PURE__ */ y("span", {
118
118
  className: "mobile-tab__label max-w-full truncate",
119
- children: R
119
+ children: I
120
120
  })]
121
121
  }) : null]
122
- }), Z ? /* @__PURE__ */ x(t, {
122
+ }), Z ? /* @__PURE__ */ y(t, {
123
123
  open: U,
124
124
  onOpenChange: W,
125
- children: /* @__PURE__ */ S(n, {
126
- "aria-label": R,
125
+ children: /* @__PURE__ */ b(n, {
126
+ "aria-label": I,
127
127
  "data-swipe-ignore": "",
128
- children: [/* @__PURE__ */ S(r, { children: [O ? /* @__PURE__ */ S("div", {
128
+ children: [/* @__PURE__ */ b(r, { children: [E ? /* @__PURE__ */ b("div", {
129
129
  className: "mb-1 flex items-center gap-2",
130
- children: [/* @__PURE__ */ x("div", {
130
+ children: [/* @__PURE__ */ y("div", {
131
131
  className: "flex size-8 flex-shrink-0 items-center justify-center",
132
- children: O
133
- }), /* @__PURE__ */ x("span", {
132
+ children: E
133
+ }), /* @__PURE__ */ y("span", {
134
134
  className: "text-heading-h4 font-semibold text-text-primary",
135
135
  children: a
136
136
  })]
137
- }) : null, /* @__PURE__ */ x(i, { children: R })] }), /* @__PURE__ */ S("div", {
137
+ }) : null, /* @__PURE__ */ y(i, { children: I })] }), /* @__PURE__ */ b("div", {
138
138
  className: "flex flex-col gap-4 pb-2",
139
139
  "data-mobile-tabbar-more": "",
140
- children: [Y.map((e) => /* @__PURE__ */ S("section", {
140
+ children: [Y.map((e) => /* @__PURE__ */ b("section", {
141
141
  "aria-label": e.label ?? void 0,
142
- children: [e.label ? /* @__PURE__ */ x("p", {
142
+ children: [e.label ? /* @__PURE__ */ y("p", {
143
143
  className: "mb-1 px-1 text-[11px] font-medium uppercase tracking-wider text-text-muted",
144
144
  children: e.label
145
- }) : null, /* @__PURE__ */ x("ul", {
145
+ }) : null, /* @__PURE__ */ y("ul", {
146
146
  className: "flex flex-col",
147
147
  children: e.items.map((e) => {
148
- let t = u(e, B.pathname);
149
- return /* @__PURE__ */ x("li", { children: /* @__PURE__ */ x(k, {
148
+ let t = l(e, R.pathname);
149
+ return /* @__PURE__ */ y("li", { children: /* @__PURE__ */ y(D, {
150
150
  icon: e.icon,
151
151
  label: e.label,
152
152
  badge: e.badge,
153
153
  active: t,
154
- onSelect: () => Q(d(e))
154
+ onSelect: () => Q(u(e))
155
155
  }) }, e.id);
156
156
  })
157
157
  })]
158
- }, e.key || "__ungrouped")), X ? /* @__PURE__ */ S("section", {
159
- "aria-label": L("appShell.quickActions", "Quick Actions"),
160
- children: [/* @__PURE__ */ x("p", {
158
+ }, e.key || "__ungrouped")), X ? /* @__PURE__ */ b("section", {
159
+ "aria-label": F("appShell.quickActions", "Quick Actions"),
160
+ children: [/* @__PURE__ */ y("p", {
161
161
  className: "mb-1 px-1 text-[11px] font-medium uppercase tracking-wider text-text-muted",
162
- children: L("appShell.quickActions", "Quick Actions")
163
- }), /* @__PURE__ */ x("ul", {
162
+ children: F("appShell.quickActions", "Quick Actions")
163
+ }), /* @__PURE__ */ y("ul", {
164
164
  className: "flex flex-col",
165
- children: P?.map((e) => /* @__PURE__ */ x("li", { children: /* @__PURE__ */ x(k, {
165
+ children: j?.map((e) => /* @__PURE__ */ y("li", { children: /* @__PURE__ */ y(D, {
166
166
  icon: e.icon,
167
167
  label: e.label,
168
168
  active: !!e.isToggle && !!e.isActive,
169
169
  onSelect: () => {
170
- E(), e.onClick(), W(!1);
170
+ w(), e.onClick(), W(!1);
171
171
  }
172
172
  }) }, e.id))
173
173
  })]
@@ -176,23 +176,23 @@ function O({ appName: a, logo: O, groupOrder: A = [], groupLabels: j = {}, maxTa
176
176
  })
177
177
  }) : null] });
178
178
  }
179
- function k({ icon: t, label: n, badge: r, active: i, onSelect: a }) {
180
- return /* @__PURE__ */ S("button", {
179
+ function D({ icon: t, label: n, badge: r, active: i, onSelect: a }) {
180
+ return /* @__PURE__ */ b("button", {
181
181
  type: "button",
182
182
  onClick: a,
183
183
  "aria-current": i ? "page" : void 0,
184
184
  className: e("flex w-full min-h-11 items-center gap-3 rounded-button px-3 py-2 text-left text-body-sm", "transition-colors duration-150", i ? "bg-[var(--color-accent-soft)] text-text-primary font-medium" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
185
185
  children: [
186
- t ? /* @__PURE__ */ x("span", {
186
+ t ? /* @__PURE__ */ y("span", {
187
187
  className: "inline-flex size-5 shrink-0 items-center justify-center",
188
188
  "aria-hidden": "true",
189
189
  children: t
190
190
  }) : null,
191
- /* @__PURE__ */ x("span", {
191
+ /* @__PURE__ */ y("span", {
192
192
  className: "min-w-0 flex-1 truncate",
193
193
  children: n
194
194
  }),
195
- r ? /* @__PURE__ */ x("span", {
195
+ r ? /* @__PURE__ */ y("span", {
196
196
  className: "rounded-full bg-status-error-bg px-1.5 text-[10px] font-semibold leading-4 text-status-error-text",
197
197
  children: r > 99 ? "99+" : r
198
198
  }) : null
@@ -200,4 +200,4 @@ function k({ icon: t, label: n, badge: r, active: i, onSelect: a }) {
200
200
  });
201
201
  }
202
202
  //#endregion
203
- export { O as MobileTabBar };
203
+ export { E as MobileTabBar };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/fe-libs",
3
- "version": "2026.1027.6",
3
+ "version": "2026.1027.7",
4
4
  "description": "Burdenoff frontend primitives and domain libraries",
5
5
  "type": "module",
6
6
  "bin": {
@@ -252,10 +252,27 @@ body.keyboard-open [data-native-shell] main {
252
252
  backdrop-filter: saturate(180%) blur(20px);
253
253
  }
254
254
 
255
+ /* Normal flow, not absolutely centered: with 5+ trailing actions (context,
256
+ search, notifications, profile, plus a leading hamburger) an
257
+ absolutely-positioned title ignores the row's actual layout and visually
258
+ overlaps the icons instead of yielding space to them. Flex "centers" it
259
+ well enough when lead/trailing are similar widths, and truncates instead
260
+ of overlapping when they are not — same mechanism Android already used
261
+ below, just keeping iOS's centered alignment. */
255
262
  .platform-ios [data-native-topbar] .native-topbar__title {
263
+ position: static;
264
+ margin: 0;
265
+ max-width: none;
266
+ min-width: 0;
267
+ flex: 1 1 auto;
256
268
  text-align: center;
257
269
  }
258
270
 
271
+ .platform-ios [data-native-topbar] .native-topbar__lead,
272
+ .platform-ios [data-native-topbar] .native-topbar__actions {
273
+ flex: 0 0 auto;
274
+ }
275
+
259
276
  /* Android: opaque 56px bar, title left-aligned next to the back arrow, and no
260
277
  iOS-style back TEXT label. */
261
278
  .platform-android [data-native-topbar] {