@floegence/floe-webapp-core 0.77.3 → 0.78.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.
@@ -0,0 +1,20 @@
1
+ import { type JSX } from 'solid-js';
2
+ export interface MobileNavigationPanelProps {
3
+ id: string;
4
+ open: boolean;
5
+ title: string;
6
+ children: JSX.Element;
7
+ onOpenChange: (open: boolean) => void;
8
+ trigger?: HTMLElement | null;
9
+ /** Null allows content with its own identity and close controls. */
10
+ header?: JSX.Element;
11
+ closeLabel?: string;
12
+ class?: string;
13
+ contentClass?: string;
14
+ onKeyDown?: JSX.EventHandler<HTMLDivElement, KeyboardEvent>;
15
+ onPresenceChange?: (present: boolean) => void;
16
+ }
17
+ /** Shell-owned navigation surface; its backdrop never covers the bottom navigation. */
18
+ export declare function MobileNavigationPanel(props: MobileNavigationPanelProps & {
19
+ navigation: () => HTMLElement | undefined;
20
+ }): JSX.Element;
@@ -0,0 +1,95 @@
1
+ import { createComponent as b, use as E, setAttribute as a, insert as f, effect as S, memo as N, className as P, template as g, delegateEvents as T } from "solid-js/web";
2
+ import { createUniqueId as j, createEffect as M, onCleanup as q, createMemo as A, Show as _, untrack as I } from "solid-js";
3
+ import { useOverlayMask as K } from "../../hooks/useOverlayMask.js";
4
+ import { createFloatingPresence as O } from "../ui/floatingPresence.js";
5
+ import { cn as F } from "../../utils/cn.js";
6
+ import { deferAfterPaint as z } from "../../utils/defer.js";
7
+ import { X as L } from "../icons/index.js";
8
+ var R = /* @__PURE__ */ g('<header class="flex shrink-0 items-center gap-2 border-b px-4 py-2"><h2 class="min-w-0 flex-1 text-base font-semibold"></h2><button type=button data-floe-autofocus class="flex h-11 w-11 shrink-0 items-center justify-center rounded-md hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">'), U = /* @__PURE__ */ g('<div data-floe-mobile-navigation-overlay class="absolute inset-0 z-50 overflow-hidden p-2"><div class="absolute inset-0 floe-floating-presence floe-floating-backdrop floe-bottom-drawer-backdrop"data-floe-mobile-navigation-backdrop></div><div class="relative pointer-events-none flex h-full min-h-0 items-end justify-center"><div role=dialog tabindex=-1 data-floe-mobile-navigation-panel data-floe-surface=floating data-floe-surface-portal-layer=true style="max-height:min(680px, 86%);border-radius:24px"><div>'), W = /* @__PURE__ */ g("<h2 class=sr-only>");
9
+ function Z(t) {
10
+ const u = `mobile-navigation-${j()}`, r = O({
11
+ open: () => t.open,
12
+ exitDurationMs: 180
13
+ });
14
+ let i, d = null;
15
+ const v = () => {
16
+ d = t.trigger ?? null, t.onOpenChange(!1);
17
+ };
18
+ M(() => {
19
+ const n = r.mounted();
20
+ if (t.onPresenceChange?.(n), !n && d) {
21
+ const l = d;
22
+ d = null, l.isConnected && !l.closest("[hidden], [inert]") && l.focus({
23
+ preventScroll: !0
24
+ });
25
+ }
26
+ }), q(() => t.onPresenceChange?.(!1)), M(() => {
27
+ const n = t.id;
28
+ t.open && (d = null, z(() => I(() => {
29
+ if (!t.open || t.id !== n || !i?.isConnected) return;
30
+ const l = i.ownerDocument.activeElement;
31
+ l && i.contains(l) || (i.querySelector("[data-floe-autofocus]") ?? i).focus({
32
+ preventScroll: !0
33
+ });
34
+ })));
35
+ }), K({
36
+ open: r.mounted,
37
+ root: () => i,
38
+ focusRoots: () => [i, t.navigation()].filter((n) => !!n),
39
+ containsTarget: (n) => n instanceof Node && !!(i?.contains(n) || t.navigation()?.contains(n)),
40
+ onClose: v,
41
+ lockBodyScroll: !1,
42
+ trapFocus: !0,
43
+ blockWheel: "outside",
44
+ blockTouchMove: "outside",
45
+ escapeKeyPhase: "bubble",
46
+ autoFocus: !1,
47
+ restoreFocus: !1
48
+ });
49
+ const B = A(() => t.children);
50
+ return b(_, {
51
+ get when() {
52
+ return r.mounted();
53
+ },
54
+ get children() {
55
+ var n = U(), l = n.firstChild, D = l.nextSibling, o = D.firstChild, m = o.firstChild;
56
+ l.$$click = () => {
57
+ t.open && v();
58
+ }, o.$$keydown = (e) => t.onKeyDown?.(e);
59
+ var h = i;
60
+ return typeof h == "function" ? E(h, o) : i = o, a(o, "aria-labelledby", u), f(o, b(_, {
61
+ get when() {
62
+ return t.header === void 0;
63
+ },
64
+ get fallback() {
65
+ return [(() => {
66
+ var e = W();
67
+ return a(e, "id", u), f(e, () => t.title), e;
68
+ })(), N(() => t.header)];
69
+ },
70
+ get children() {
71
+ var e = R(), s = e.firstChild, c = s.nextSibling;
72
+ return a(s, "id", u), f(s, () => t.title), c.$$click = v, f(c, b(L, {
73
+ class: "h-5 w-5"
74
+ })), S(() => a(c, "aria-label", t.closeLabel ?? "Close")), e;
75
+ }
76
+ }), m), f(m, B), S((e) => {
77
+ var s = r.state(), c = r.state(), x = t.id, w = r.state(), y = r.exiting(), k = r.exiting() ? "true" : void 0, C = F("pointer-events-auto relative flex min-h-0 w-full max-w-[640px] flex-col border border-border bg-card text-card-foreground shadow-xl floe-floating-presence floe-bottom-drawer-panel", t.class), $ = F("flex min-h-0 flex-col overflow-auto overscroll-contain", t.contentClass);
78
+ return s !== e.e && a(n, "data-floating-presence", e.e = s), c !== e.t && a(l, "data-floating-presence", e.t = c), x !== e.a && a(o, "id", e.a = x), w !== e.o && a(o, "data-floating-presence", e.o = w), y !== e.i && (o.inert = e.i = y), k !== e.n && a(o, "aria-hidden", e.n = k), C !== e.s && P(o, e.s = C), $ !== e.h && P(m, e.h = $), e;
79
+ }, {
80
+ e: void 0,
81
+ t: void 0,
82
+ a: void 0,
83
+ o: void 0,
84
+ i: void 0,
85
+ n: void 0,
86
+ s: void 0,
87
+ h: void 0
88
+ }), n;
89
+ }
90
+ });
91
+ }
92
+ T(["click", "keydown"]);
93
+ export {
94
+ Z as MobileNavigationPanel
95
+ };
@@ -11,6 +11,8 @@ export interface MobileTabBarItem extends Pick<ActivityBarItem, 'buttonRef' | 'a
11
11
  onClick?: () => void;
12
12
  }
13
13
  export interface MobileTabBarProps {
14
+ ref?: (element: HTMLElement) => void;
15
+ actions?: MobileTabBarItem[];
14
16
  hidden?: boolean;
15
17
  items: MobileTabBarItem[];
16
18
  activeId: string;
@@ -1,106 +1,129 @@
1
- import { insert as m, createComponent as v, effect as L, className as w, setAttribute as f, template as x, use as E, Dynamic as _, memo as z, delegateEvents as D } from "solid-js/web";
2
- import { createSignal as $, onMount as F, onCleanup as W, Show as S, For as j } from "solid-js";
3
- import { cn as y } from "../../utils/cn.js";
4
- import { deferNonBlocking as R } from "../../utils/defer.js";
5
- var q = /* @__PURE__ */ x('<div class="absolute left-0 top-0 bottom-0 w-8 bg-gradient-to-r from-background to-transparent z-10 pointer-events-none">'), B = /* @__PURE__ */ x('<div class="absolute right-0 top-0 bottom-0 w-8 bg-gradient-to-l from-background to-transparent z-10 pointer-events-none">'), H = /* @__PURE__ */ x('<nav data-floe-shell-slot=mobile-tab-bar style=border-top-color:var(--bottom-bar-border)><div class="relative h-14"><div class="h-full flex items-center overflow-x-auto scrollbar-hide snap-x snap-mandatory"role=tablist aria-orientation=horizontal style=-webkit-overflow-scrolling:touch>'), K = /* @__PURE__ */ x("<span>"), N = /* @__PURE__ */ x('<div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-8 h-0.5 bg-primary rounded-t">'), T = /* @__PURE__ */ x("<button type=button role=tab><div></div><span>");
6
- function O(e) {
7
- let l;
8
- const [I, b] = $(!1), [n, h] = $(!1);
9
- let c = null;
1
+ import { use as R, insert as u, createComponent as d, effect as _, className as C, setAttribute as s, template as v, Dynamic as j, memo as q, delegateEvents as B } from "solid-js/web";
2
+ import { createSignal as E, onMount as z, onCleanup as D, Show as S, For as F } from "solid-js";
3
+ import { cn as A } from "../../utils/cn.js";
4
+ import { deferNonBlocking as W } from "../../utils/defer.js";
5
+ var H = /* @__PURE__ */ v('<div class="absolute left-0 top-0 bottom-0 w-8 bg-gradient-to-r from-background to-transparent z-10 pointer-events-none">'), N = /* @__PURE__ */ v('<div class="absolute right-0 top-0 bottom-0 w-8 bg-gradient-to-l from-background to-transparent z-10 pointer-events-none">'), T = /* @__PURE__ */ v('<div data-floe-mobile-navigation-actions class="flex shrink-0 border-l border-border">'), P = /* @__PURE__ */ v('<nav data-floe-shell-slot=mobile-tab-bar style=border-top-color:var(--bottom-bar-border)><div class="relative flex h-14"><div class="relative min-w-0 flex-1"><div class="h-full flex items-center overflow-x-auto scrollbar-hide snap-x snap-mandatory"role=tablist aria-orientation=horizontal style=-webkit-overflow-scrolling:touch>'), U = /* @__PURE__ */ v("<span>"), V = /* @__PURE__ */ v('<div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-8 h-0.5 bg-primary rounded-t">'), G = /* @__PURE__ */ v("<button type=button><div></div><span>");
6
+ function Y(e) {
7
+ let r;
8
+ const [$, b] = E(!1), [i, h] = E(!1);
9
+ let f = null;
10
10
  const g = () => {
11
- l && (b(l.scrollLeft > 0), h(l.scrollLeft < l.scrollWidth - l.clientWidth - 1));
11
+ r && (b(r.scrollLeft > 0), h(r.scrollLeft < r.scrollWidth - r.clientWidth - 1));
12
12
  }, t = () => {
13
- if (c === null) {
13
+ if (f === null) {
14
14
  if (typeof requestAnimationFrame > "u") {
15
15
  g();
16
16
  return;
17
17
  }
18
- c = requestAnimationFrame(() => {
19
- c = null, g();
18
+ f = requestAnimationFrame(() => {
19
+ f = null, g();
20
20
  });
21
21
  }
22
- }, o = () => e.items, C = (a) => {
23
- const i = o()[a];
24
- if (i) {
25
- if (i.onClick) {
26
- R(() => i.onClick());
22
+ }, o = () => e.items, I = (l) => {
23
+ const a = o()[l];
24
+ if (a) {
25
+ if (a.onClick) {
26
+ W(() => a.onClick());
27
27
  return;
28
28
  }
29
- e.onSelect(i.id);
29
+ e.onSelect(a.id);
30
30
  }
31
31
  };
32
- return F(() => {
32
+ return z(() => {
33
33
  g();
34
- const a = e.items.findIndex((i) => i.id === e.activeId);
35
- if (a > 0 && l) {
36
- const i = l.scrollWidth / e.items.length;
37
- l.scrollLeft = Math.max(0, i * a - l.clientWidth / 2 + i / 2);
34
+ const l = e.items.findIndex((a) => a.id === e.activeId);
35
+ if (l > 0 && r) {
36
+ const a = r.scrollWidth / e.items.length;
37
+ r.scrollLeft = Math.max(0, a * l - r.clientWidth / 2 + a / 2);
38
38
  }
39
- }), W(() => {
40
- c !== null && typeof cancelAnimationFrame < "u" && (cancelAnimationFrame(c), c = null);
39
+ }), D(() => {
40
+ f !== null && typeof cancelAnimationFrame < "u" && (cancelAnimationFrame(f), f = null);
41
41
  }), (() => {
42
- var a = H(), i = a.firstChild, d = i.firstChild;
43
- m(i, v(S, {
42
+ var l = P(), a = l.firstChild, x = a.firstChild, m = x.firstChild, w = e.ref;
43
+ typeof w == "function" ? R(w, l) : e.ref = l, u(x, d(S, {
44
44
  get when() {
45
- return I();
45
+ return $();
46
46
  },
47
47
  get children() {
48
- return q();
48
+ return H();
49
49
  }
50
- }), d), m(i, v(S, {
50
+ }), m), u(x, d(S, {
51
51
  get when() {
52
- return n();
52
+ return i();
53
53
  },
54
54
  get children() {
55
- return B();
55
+ return N();
56
56
  }
57
- }), d), d.addEventListener("scroll", t);
58
- var k = l;
59
- return typeof k == "function" ? E(k, d) : l = d, m(d, v(j, {
57
+ }), m), m.addEventListener("scroll", t);
58
+ var y = r;
59
+ return typeof y == "function" ? R(y, m) : r = m, u(m, d(F, {
60
60
  get each() {
61
61
  return e.items;
62
62
  },
63
- children: (r, s) => v(P, {
64
- item: r,
63
+ children: (n, c) => d(M, {
64
+ item: n,
65
65
  get isActive() {
66
- return e.activeId === r.id;
66
+ return e.activeId === n.id;
67
67
  },
68
68
  get index() {
69
- return s();
69
+ return c();
70
70
  },
71
71
  get itemCount() {
72
72
  return e.items.length;
73
73
  },
74
74
  onClick: () => {
75
75
  "vibrate" in navigator && navigator.vibrate(10);
76
- const u = r.onClick, A = e.onSelect, M = r.id;
77
- r.onClick ? R(() => u()) : A(M);
76
+ const k = n.onClick, L = e.onSelect, K = n.id;
77
+ n.onClick ? W(() => k()) : L(K);
78
78
  },
79
- onKeyboardSelect: (u) => C(u)
79
+ onKeyboardSelect: (k) => I(k)
80
80
  })
81
- })), L((r) => {
82
- var s = e.hidden, u = y(
81
+ })), u(a, d(S, {
82
+ get when() {
83
+ return e.actions?.length;
84
+ },
85
+ get children() {
86
+ var n = T();
87
+ return u(n, d(F, {
88
+ get each() {
89
+ return e.actions;
90
+ },
91
+ children: (c) => d(M, {
92
+ item: c,
93
+ action: !0,
94
+ isActive: !1,
95
+ index: 0,
96
+ itemCount: 1,
97
+ onKeyboardSelect: () => {
98
+ },
99
+ onClick: () => c.onClick?.()
100
+ })
101
+ })), n;
102
+ }
103
+ }), null), _((n) => {
104
+ var c = e.hidden, k = A(
83
105
  // Same safe-area rule as TopBar: keep the visual bar height stable, and
84
106
  // add safe-area padding on an outer wrapper (avoid squeezing content).
85
107
  "relative shrink-0 bg-background border-t border-border safe-bottom safe-left safe-right",
86
108
  e.class
87
- ), A = e.ariaLabel;
88
- return s !== r.e && (a.hidden = r.e = s), u !== r.t && w(a, r.t = u), A !== r.a && f(a, "aria-label", r.a = A), r;
109
+ ), L = e.ariaLabel;
110
+ return c !== n.e && (l.hidden = n.e = c), k !== n.t && C(l, n.t = k), L !== n.a && s(l, "aria-label", n.a = L), n;
89
111
  }, {
90
112
  e: void 0,
91
113
  t: void 0,
92
114
  a: void 0
93
- }), a;
115
+ }), l;
94
116
  })();
95
117
  }
96
- function P(e) {
97
- let l;
98
- const I = () => typeof e.item.ariaExpanded == "function" ? e.item.ariaExpanded() : e.item.ariaExpanded;
99
- F(() => e.item.buttonRef?.(l ?? null)), W(() => e.item.buttonRef?.(null));
118
+ function M(e) {
119
+ let r;
120
+ const $ = () => typeof e.item.ariaExpanded == "function" ? e.item.ariaExpanded() : e.item.ariaExpanded;
121
+ z(() => e.item.buttonRef?.(r ?? null)), D(() => e.item.buttonRef?.(null));
100
122
  const b = () => typeof e.item.badge == "function" ? e.item.badge() : e.item.badge;
101
123
  return (() => {
102
- var n = T(), h = n.firstChild, c = h.nextSibling;
103
- n.$$keydown = (t) => {
124
+ var i = G(), h = i.firstChild, f = h.nextSibling;
125
+ i.$$keydown = (t) => {
126
+ if (e.action) return;
104
127
  let o = null;
105
128
  switch (t.key) {
106
129
  case "ArrowRight":
@@ -121,34 +144,34 @@ function P(e) {
121
144
  return;
122
145
  }
123
146
  t.preventDefault(), e.onKeyboardSelect(o);
124
- }, n.$$click = () => e.onClick();
125
- var g = l;
126
- return typeof g == "function" ? E(g, n) : l = n, m(h, v(_, {
147
+ }, i.$$click = () => e.onClick();
148
+ var g = r;
149
+ return typeof g == "function" ? R(g, i) : r = i, u(h, d(j, {
127
150
  get component() {
128
151
  return e.item.icon;
129
152
  },
130
153
  class: "w-6 h-6"
131
- }), null), m(h, v(S, {
154
+ }), null), u(h, d(S, {
132
155
  get when() {
133
156
  return b();
134
157
  },
135
158
  get children() {
136
- var t = K();
137
- return m(t, (() => {
138
- var o = z(() => typeof b() == "number" && b() > 99);
159
+ var t = U();
160
+ return u(t, (() => {
161
+ var o = q(() => typeof b() == "number" && b() > 99);
139
162
  return () => o() ? "99+" : b();
140
- })()), L(() => w(t, y("absolute -top-1 -right-2 min-w-4 h-4 px-1", "flex items-center justify-center", "text-[10px] font-medium rounded-full", "bg-primary text-primary-foreground"))), t;
163
+ })()), _(() => C(t, A("absolute -top-1 -right-2 min-w-4 h-4 px-1", "flex items-center justify-center", "text-[10px] font-medium rounded-full", "bg-primary text-primary-foreground"))), t;
141
164
  }
142
- }), null), m(c, () => e.item.label), m(n, v(S, {
165
+ }), null), u(f, () => e.item.label), u(i, d(S, {
143
166
  get when() {
144
167
  return e.isActive;
145
168
  },
146
169
  get children() {
147
- return N();
170
+ return V();
148
171
  }
149
- }), null), L((t) => {
150
- var o = y("relative flex-shrink-0 flex flex-col items-center justify-center", "min-w-16 h-full px-4 snap-center", "transition-[color] duration-150", "focus:outline-none focus-visible:bg-muted", e.isActive ? "text-primary" : "text-muted-foreground active:text-foreground"), C = e.item.label, a = e.isActive, i = I(), d = e.item.ariaControls, k = e.item.ariaHasPopup, r = e.isActive ? 0 : -1, s = y("relative transition-transform duration-150", e.isActive && "scale-110"), u = y("mt-1 text-[10px] font-medium truncate max-w-full", e.isActive && "font-semibold");
151
- return o !== t.e && w(n, t.e = o), C !== t.t && f(n, "aria-label", t.t = C), a !== t.a && f(n, "aria-selected", t.a = a), i !== t.o && f(n, "aria-expanded", t.o = i), d !== t.i && f(n, "aria-controls", t.i = d), k !== t.n && f(n, "aria-haspopup", t.n = k), r !== t.s && f(n, "tabindex", t.s = r), s !== t.h && w(h, t.h = s), u !== t.r && w(c, t.r = u), t;
172
+ }), null), _((t) => {
173
+ var o = A("relative flex-shrink-0 flex flex-col items-center justify-center", "min-w-16 h-full px-4 snap-center", "transition-[color] duration-150", "focus:outline-none focus-visible:bg-muted", e.action && "w-16 px-1", $() && "bg-muted text-foreground", e.isActive ? "text-primary" : "text-muted-foreground active:text-foreground"), I = e.item.label, l = e.action ? void 0 : e.isActive, a = $(), x = e.item.ariaControls, m = e.item.ariaHasPopup, w = e.action ? void 0 : "tab", y = e.action || e.isActive ? 0 : -1, n = A("relative transition-transform duration-150", e.isActive && "scale-110"), c = A("mt-1 text-[10px] font-medium truncate max-w-full", e.isActive && "font-semibold");
174
+ return o !== t.e && C(i, t.e = o), I !== t.t && s(i, "aria-label", t.t = I), l !== t.a && s(i, "aria-selected", t.a = l), a !== t.o && s(i, "aria-expanded", t.o = a), x !== t.i && s(i, "aria-controls", t.i = x), m !== t.n && s(i, "aria-haspopup", t.n = m), w !== t.s && s(i, "role", t.s = w), y !== t.h && s(i, "tabindex", t.h = y), n !== t.r && C(h, t.r = n), c !== t.d && C(f, t.d = c), t;
152
175
  }, {
153
176
  e: void 0,
154
177
  t: void 0,
@@ -158,11 +181,12 @@ function P(e) {
158
181
  n: void 0,
159
182
  s: void 0,
160
183
  h: void 0,
161
- r: void 0
162
- }), n;
184
+ r: void 0,
185
+ d: void 0
186
+ }), i;
163
187
  })();
164
188
  }
165
- D(["click", "keydown"]);
189
+ B(["click", "keydown"]);
166
190
  export {
167
- O as MobileTabBar
191
+ Y as MobileTabBar
168
192
  };
@@ -1,4 +1,6 @@
1
1
  import { type JSX } from 'solid-js';
2
+ import { type MobileTabBarItem } from './MobileTabBar';
3
+ import { type MobileNavigationPanelProps } from './MobileNavigationPanel';
2
4
  import { type ActivityBarItem } from './ActivityBar';
3
5
  import type { SidebarVisibilityMotion } from '../../context/LayoutContext';
4
6
  import { type UIFirstSelectionEvent } from '../../utils/uiFirstSelection';
@@ -25,6 +27,12 @@ export interface ShellSlotClassNames {
25
27
  mobileTabBar?: string;
26
28
  }
27
29
  export interface ShellProps {
30
+ /** Defaults to visible; hidden transfers the top safe area to Shell. */
31
+ topBarMobileMode?: 'visible' | 'hidden';
32
+ /** Fixed actions outside the horizontally scrolling mobile tabs. */
33
+ mobileNavigationActions?: MobileTabBarItem[];
34
+ /** A controlled navigation surface above the persistent mobile tab bar. */
35
+ mobileNavigationPanel?: MobileNavigationPanelProps;
28
36
  children: JSX.Element;
29
37
  /** Fill a containing AppViewport or embedded host instead of the document viewport. */
30
38
  fillParent?: boolean;