@floegence/floe-webapp-core 0.39.5 → 0.39.6

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,33 @@
1
+ import { type JSX } from 'solid-js';
2
+ export type BottomBarCompanionPhase = 'collapsed' | 'expanding' | 'expanded' | 'collapsing';
3
+ export type BottomBarCompanionDismissReason = 'outside-pointer' | 'escape';
4
+ export interface BottomBarCompanionProps {
5
+ retained: boolean;
6
+ visible: boolean;
7
+ open: boolean;
8
+ anchor: HTMLElement | null;
9
+ mount: HTMLElement | null;
10
+ id: string;
11
+ label: string;
12
+ children?: JSX.Element;
13
+ maxWidth?: number;
14
+ maxHeight?: number;
15
+ viewportPadding?: number;
16
+ class?: string;
17
+ contentClass?: string;
18
+ surfaceRef?: (element: HTMLElement | null) => void;
19
+ contentHostRef?: (element: HTMLElement | null) => void;
20
+ isOwnedInteraction?: (event: PointerEvent | KeyboardEvent) => boolean;
21
+ onDismiss?: (reason: BottomBarCompanionDismissReason) => void;
22
+ onPhaseChange?: (phase: BottomBarCompanionPhase) => void;
23
+ }
24
+ /**
25
+ * A non-modal surface that grows upward from a Bottom Bar anchor.
26
+ *
27
+ * This is intentionally distinct from windows, popovers, and modal drawers:
28
+ * it retains one fixed shell and one content host across collapsed and expanded
29
+ * presentation. Products that also have a full-page placement can Portal their
30
+ * stable product tree into `contentHostRef` without giving this component
31
+ * ownership of that tree.
32
+ */
33
+ export declare function BottomBarCompanion(props: BottomBarCompanionProps): JSX.Element;
@@ -0,0 +1,233 @@
1
+ import { createComponent as Z, Portal as ce, use as $, insert as le, effect as ue, setAttribute as R, className as p, style as de, memo as fe, template as me } from "solid-js/web";
2
+ import { createSignal as N, createEffect as k, untrack as G, onCleanup as U, Show as he } from "solid-js";
3
+ import { cn as ee } from "../../utils/cn.js";
4
+ var ve = /* @__PURE__ */ me("<section role=region data-floe-bottom-bar-companion=true><div data-floe-bottom-bar-companion-content=true>");
5
+ const we = 544, be = 544, ge = 12, z = {
6
+ top: "--floe-bottom-bar-companion-safe-area-top",
7
+ right: "--floe-bottom-bar-companion-safe-area-right",
8
+ bottom: "--floe-bottom-bar-companion-safe-area-bottom",
9
+ left: "--floe-bottom-bar-companion-safe-area-left"
10
+ };
11
+ function te(e, a) {
12
+ const l = Number(e);
13
+ return Number.isFinite(l) && l > 0 ? l : a;
14
+ }
15
+ function Ce(e, a) {
16
+ const l = Number(e);
17
+ return Number.isFinite(l) && l >= 0 ? l : a;
18
+ }
19
+ function ne(e) {
20
+ if (!e?.isConnected) return null;
21
+ const a = e.getBoundingClientRect();
22
+ return !Number.isFinite(a.left) || !Number.isFinite(a.top) || a.width <= 0 || a.height <= 0 ? null : {
23
+ left: a.left,
24
+ top: a.top,
25
+ width: a.width,
26
+ height: a.height
27
+ };
28
+ }
29
+ function B(e, a) {
30
+ const l = e.ownerDocument.defaultView;
31
+ if (!l) return 0;
32
+ const m = Number.parseFloat(l.getComputedStyle(e).getPropertyValue(a));
33
+ return Number.isFinite(m) && m > 0 ? m : 0;
34
+ }
35
+ function Fe(e) {
36
+ return {
37
+ top: B(e, z.top),
38
+ right: B(e, z.right),
39
+ bottom: B(e, z.bottom),
40
+ left: B(e, z.left)
41
+ };
42
+ }
43
+ function ye(e, a, l, m, C) {
44
+ const F = a.ownerDocument.defaultView, y = F?.visualViewport, v = y?.offsetLeft ?? 0, D = y?.offsetTop ?? 0, q = y?.width ?? F?.innerWidth ?? e.left + e.width, H = y?.height ?? F?.innerHeight ?? e.top + e.height, E = Fe(a), W = v + E.left + C, w = v + q - E.right - C, b = D + E.top + C, h = D + H - E.bottom - C, g = Math.max(0, w - W), d = Math.min(e.width, l, g), P = e.left + (e.width - d) / 2, M = Math.max(W, Math.min(P, w - d)), L = Math.min(e.top + e.height, h), V = Math.max(e.height, L - b), x = Math.min(m, V);
45
+ return {
46
+ left: M,
47
+ top: L - x,
48
+ width: d,
49
+ height: x
50
+ };
51
+ }
52
+ function Ee(e, a) {
53
+ return !!(e && e.left === a.left && e.top === a.top && e.width === a.width && e.height === a.height);
54
+ }
55
+ function Ae(e) {
56
+ return e ? {
57
+ left: `${e.left}px`,
58
+ top: `${e.top}px`,
59
+ width: `${e.width}px`,
60
+ height: `${e.height}px`
61
+ } : {};
62
+ }
63
+ function Me(e) {
64
+ const [a, l] = N(null), [m, C] = N(!1), [F, y] = N(!1), [v, D] = N(null), [q, H] = N(null), [E, W] = N("collapsed");
65
+ let w = null, b = 0, h = 0, g = null, d = null, P = null, M = !1, L = !1;
66
+ const V = (i) => {
67
+ E() !== i && (W(i), e.onPhaseChange?.(i));
68
+ }, x = () => a()?.ownerDocument.defaultView ?? e.anchor?.ownerDocument.defaultView ?? null, oe = () => x()?.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? !1, _ = (i) => {
69
+ const t = e.mount, r = e.anchor, n = !!(t?.isConnected && i && r?.ownerDocument === t.ownerDocument);
70
+ n && l(t);
71
+ const c = a();
72
+ if (c) {
73
+ C(c.isConnected);
74
+ const o = !!(n && c === t);
75
+ return y(o), {
76
+ mount: c,
77
+ ready: o
78
+ };
79
+ }
80
+ return C(!1), y(!1), {
81
+ mount: null,
82
+ ready: !1
83
+ };
84
+ }, ie = (i) => {
85
+ const t = ne(e.anchor);
86
+ t && H(t);
87
+ const {
88
+ mount: r,
89
+ ready: n
90
+ } = _(t), c = t ?? q();
91
+ return !c || !r || !n ? null : i ? ye(c, r, te(e.maxWidth, we), te(e.maxHeight, be), Ce(e.viewportPadding, ge)) : c;
92
+ }, X = () => {
93
+ b && g && g.cancelAnimationFrame(b), h && d && d.cancelAnimationFrame(h), b = 0, h = 0, g = null, d = null, P = null;
94
+ }, K = (i, t) => {
95
+ const r = ie(i);
96
+ if (!r) return;
97
+ const n = v(), c = i ? "expanded" : "collapsed";
98
+ if (!t || oe() || Ee(n, r)) {
99
+ D(r), P = null, V(c);
100
+ return;
101
+ }
102
+ P = ["height", "width", "top", "left"].find((u) => n?.[u] !== r[u]) ?? null, V(i ? "expanding" : "collapsing");
103
+ const o = x();
104
+ if (!o) {
105
+ D(r), V(c);
106
+ return;
107
+ }
108
+ h = o.requestAnimationFrame(() => {
109
+ h = 0, d = null, D(r);
110
+ }), d = o;
111
+ }, j = (i, t) => {
112
+ const r = x();
113
+ if (!r) {
114
+ K(t, i);
115
+ return;
116
+ }
117
+ b && g && g.cancelAnimationFrame(b), h && d && d.cancelAnimationFrame(h), h = 0, d = null, b = r.requestAnimationFrame(() => {
118
+ b = 0, g = null, G(() => K(t, i));
119
+ }), g = r;
120
+ };
121
+ k(() => {
122
+ const i = e.retained, t = e.visible, r = e.open;
123
+ if (e.anchor, e.mount, e.maxWidth, e.maxHeight, e.viewportPadding, !i) {
124
+ X(), l(null), C(!1), y(!1), D(null), H(null), w = null, e.surfaceRef?.(null), e.contentHostRef?.(null), M = !1, L = r;
125
+ return;
126
+ }
127
+ const n = ne(e.anchor);
128
+ if (n && H(n), _(n), !t) {
129
+ M = !1, L = r;
130
+ return;
131
+ }
132
+ const c = !M, o = L !== r;
133
+ M = !0, L = r, j(!c && o, r);
134
+ }), k(() => {
135
+ if (!e.retained) return;
136
+ const i = e.anchor, t = a(), r = e.mount, n = /* @__PURE__ */ new Set(), c = /* @__PURE__ */ new Set();
137
+ for (const s of [i, t, r]) {
138
+ if (!s) continue;
139
+ n.add(s.ownerDocument);
140
+ const f = s.ownerDocument.defaultView;
141
+ f && c.add(f);
142
+ }
143
+ if (c.size === 0 || n.size === 0) return;
144
+ const o = () => j(!1, G(() => e.open)), u = [];
145
+ for (const s of /* @__PURE__ */ new Set([i, t, r])) {
146
+ const f = s?.ownerDocument.defaultView;
147
+ if (!s || !f || typeof f.ResizeObserver != "function") continue;
148
+ const A = new f.ResizeObserver(o);
149
+ A.observe(s), u.push(A);
150
+ }
151
+ let S = i?.isConnected ?? !1, T = t?.isConnected ?? !1, I = r?.isConnected ?? !1;
152
+ const O = [];
153
+ for (const s of n) {
154
+ const f = s.defaultView;
155
+ if (!f || typeof f.MutationObserver != "function" || !s.documentElement) continue;
156
+ const A = new f.MutationObserver(() => {
157
+ const J = i?.isConnected ?? !1, Q = t?.isConnected ?? !1, Y = r?.isConnected ?? !1;
158
+ S === J && T === Q && I === Y || (S = J, T = Q, I = Y, G(o));
159
+ });
160
+ A.observe(s.documentElement, {
161
+ childList: !0,
162
+ subtree: !0
163
+ }), O.push(A);
164
+ }
165
+ for (const s of c)
166
+ s.addEventListener("resize", o), s.visualViewport?.addEventListener("resize", o), s.visualViewport?.addEventListener("scroll", o);
167
+ U(() => {
168
+ for (const s of u) s.disconnect();
169
+ for (const s of O) s.disconnect();
170
+ for (const s of c)
171
+ s.removeEventListener("resize", o), s.visualViewport?.removeEventListener("resize", o), s.visualViewport?.removeEventListener("scroll", o);
172
+ });
173
+ }), k(() => {
174
+ if (!e.retained || !e.visible || !e.open || !m() || !F() || !v()) return;
175
+ const t = a()?.ownerDocument;
176
+ if (!t) return;
177
+ const r = (o) => {
178
+ const u = typeof o.composedPath == "function" ? o.composedPath() : [];
179
+ return w && u.includes(w) || e.anchor && u.includes(e.anchor) ? !0 : e.isOwnedInteraction?.(o) ?? !1;
180
+ }, n = (o) => {
181
+ r(o) || e.onDismiss?.("outside-pointer");
182
+ }, c = (o) => {
183
+ if (o.key !== "Escape" || o.defaultPrevented || o.isComposing || o.keyCode === 229) return;
184
+ const u = t.activeElement;
185
+ !w?.contains(u) && !e.isOwnedInteraction?.(o) || (o.preventDefault(), e.onDismiss?.("escape"));
186
+ };
187
+ t.addEventListener("pointerdown", n, !0), t.addEventListener("keydown", c), U(() => {
188
+ t.removeEventListener("pointerdown", n, !0), t.removeEventListener("keydown", c);
189
+ });
190
+ }), U(() => {
191
+ X(), e.surfaceRef?.(null), e.contentHostRef?.(null);
192
+ });
193
+ const re = (i) => {
194
+ w = i, e.surfaceRef?.(i);
195
+ }, ae = (i) => {
196
+ e.contentHostRef?.(i);
197
+ }, se = (i) => {
198
+ if (i.target !== w || i.propertyName !== P) return;
199
+ const t = e.open ? "expanding" : "collapsing";
200
+ E() === t && (P = null, V(e.open ? "expanded" : "collapsed"));
201
+ };
202
+ return Z(he, {
203
+ get when() {
204
+ return fe(() => !!e.retained)() && a();
205
+ },
206
+ children: (i) => Z(ce, {
207
+ get mount() {
208
+ return i();
209
+ },
210
+ get children() {
211
+ var t = ve(), r = t.firstChild;
212
+ return t.addEventListener("transitionend", se), $((n) => re(n), t), $((n) => ae(n), r), le(r, () => e.children), ue((n) => {
213
+ var c = e.id, o = e.label, u = !e.visible || !m() || !F() || !v() ? "true" : void 0, S = !e.visible || !m() || !F() || !v(), T = ee("floe-bottom-bar-companion", e.class), I = E(), O = e.visible && m() && F() ? "visible" : "hidden", s = v() ? "true" : "false", f = Ae(v()), A = ee("floe-bottom-bar-companion__content", e.contentClass);
214
+ return c !== n.e && R(t, "id", n.e = c), o !== n.t && R(t, "aria-label", n.t = o), u !== n.a && R(t, "aria-hidden", n.a = u), S !== n.o && (t.inert = n.o = S), T !== n.i && p(t, n.i = T), I !== n.n && R(t, "data-companion-phase", n.n = I), O !== n.s && R(t, "data-companion-visibility", n.s = O), s !== n.h && R(t, "data-companion-frame-ready", n.h = s), n.r = de(t, f, n.r), A !== n.d && p(r, n.d = A), n;
215
+ }, {
216
+ e: void 0,
217
+ t: void 0,
218
+ a: void 0,
219
+ o: void 0,
220
+ i: void 0,
221
+ n: void 0,
222
+ s: void 0,
223
+ h: void 0,
224
+ r: void 0,
225
+ d: void 0
226
+ }), t;
227
+ }
228
+ })
229
+ });
230
+ }
231
+ export {
232
+ Me as BottomBarCompanion
233
+ };
@@ -4,6 +4,7 @@ export { Sidebar, SidebarContent, SidebarItemList, SidebarSection, SidebarItem,
4
4
  export { TopBar, type TopBarProps } from './TopBar';
5
5
  export { TopBarIconButton, type TopBarIconButtonProps } from './TopBarIconButton';
6
6
  export { BottomBar, BottomBarItem, StatusIndicator, type BottomBarProps, type BottomBarItemProps, type StatusIndicatorProps } from './BottomBar';
7
+ export { BottomBarCompanion, type BottomBarCompanionDismissReason, type BottomBarCompanionPhase, type BottomBarCompanionProps, } from './BottomBarCompanion';
7
8
  export { MobileTabBar, type MobileTabBarItem, type MobileTabBarProps } from './MobileTabBar';
8
9
  export { ResizeHandle, type ResizeHandleProps } from './ResizeHandle';
9
10
  export { SidebarPane, type SidebarPaneProps } from './SidebarPane';
package/dist/floe.css CHANGED
@@ -687,6 +687,66 @@
687
687
  }
688
688
  }
689
689
 
690
+ .floe-bottom-bar-companion {
691
+ position: fixed;
692
+ z-index: var(--floe-bottom-bar-companion-z-index, 72);
693
+ min-width: 0;
694
+ min-height: 0;
695
+ overflow: hidden;
696
+ border: 1px solid color-mix(in srgb, var(--border) 92%, transparent);
697
+ border-radius: var(--floe-bottom-bar-companion-collapsed-radius, 0.3125rem);
698
+ background: var(--background);
699
+ color: var(--foreground);
700
+ opacity: 1;
701
+ pointer-events: auto;
702
+ transform-origin: bottom center;
703
+ transition:
704
+ left 240ms cubic-bezier(0.22, 1, 0.36, 1),
705
+ top 240ms cubic-bezier(0.22, 1, 0.36, 1),
706
+ width 240ms cubic-bezier(0.22, 1, 0.36, 1),
707
+ height 240ms cubic-bezier(0.22, 1, 0.36, 1),
708
+ border-radius 200ms ease,
709
+ border-color 180ms ease,
710
+ box-shadow 200ms ease,
711
+ opacity 120ms ease;
712
+ will-change: left, top, width, height;
713
+ }
714
+
715
+ .floe-bottom-bar-companion[data-companion-phase='expanding'],
716
+ .floe-bottom-bar-companion[data-companion-phase='expanded'] {
717
+ border-radius: var(--floe-bottom-bar-companion-expanded-radius, 0.5rem);
718
+ border-color: color-mix(in srgb, var(--border) 96%, transparent);
719
+ box-shadow:
720
+ 0 22px 54px -24px color-mix(in srgb, var(--foreground) 34%, transparent),
721
+ 0 6px 18px -12px color-mix(in srgb, var(--foreground) 24%, transparent);
722
+ }
723
+
724
+ .floe-bottom-bar-companion[data-companion-phase='collapsing'] {
725
+ transition-duration: 180ms, 180ms, 180ms, 180ms, 160ms, 140ms, 160ms, 100ms;
726
+ }
727
+
728
+ .floe-bottom-bar-companion[data-companion-visibility='hidden'],
729
+ .floe-bottom-bar-companion[data-companion-frame-ready='false'] {
730
+ visibility: hidden;
731
+ opacity: 0;
732
+ pointer-events: none;
733
+ }
734
+
735
+ .floe-bottom-bar-companion__content {
736
+ width: 100%;
737
+ height: 100%;
738
+ min-width: 0;
739
+ min-height: 0;
740
+ overflow: hidden;
741
+ }
742
+
743
+ @media (prefers-reduced-motion: reduce) {
744
+ .floe-bottom-bar-companion {
745
+ transition: none;
746
+ will-change: auto;
747
+ }
748
+ }
749
+
690
750
  @keyframes glow-pulse {
691
751
  0%, 100% {
692
752
  box-shadow: 0 0 5px var(--primary), 0 0 20px var(--primary);