@flamingo-stack/openframe-frontend-core 0.0.560 → 0.0.561-1846.5000.1
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.
- package/dist/{chunk-F3F3PVJ3.js → chunk-44U25GD5.js} +2 -2
- package/dist/{chunk-DCB4EBA5.cjs → chunk-4LHEQMOV.cjs} +3 -3
- package/dist/chunk-4LHEQMOV.cjs.map +1 -0
- package/dist/{chunk-IGO6GNG7.js → chunk-5AEHCGQV.js} +6 -6
- package/dist/{chunk-QUYIRF6U.cjs → chunk-AGQ4VTUX.cjs} +29 -29
- package/dist/{chunk-QUYIRF6U.cjs.map → chunk-AGQ4VTUX.cjs.map} +1 -1
- package/dist/{chunk-GMDPQ5FQ.js → chunk-B6TM3QAL.js} +4 -4
- package/dist/{chunk-GZSUDIJB.cjs → chunk-CR2CMLTQ.cjs} +4 -4
- package/dist/{chunk-GZSUDIJB.cjs.map → chunk-CR2CMLTQ.cjs.map} +1 -1
- package/dist/{chunk-MAX76H7U.cjs → chunk-D3OY4CJU.cjs} +3 -3
- package/dist/{chunk-MAX76H7U.cjs.map → chunk-D3OY4CJU.cjs.map} +1 -1
- package/dist/{chunk-WXJZULAB.cjs → chunk-DAVVVBNL.cjs} +26 -26
- package/dist/{chunk-WXJZULAB.cjs.map → chunk-DAVVVBNL.cjs.map} +1 -1
- package/dist/{chunk-XRP6VHOS.cjs → chunk-DHRJFUBR.cjs} +11 -11
- package/dist/{chunk-XRP6VHOS.cjs.map → chunk-DHRJFUBR.cjs.map} +1 -1
- package/dist/{chunk-N66ANBF6.cjs → chunk-EK346HD7.cjs} +39 -39
- package/dist/{chunk-N66ANBF6.cjs.map → chunk-EK346HD7.cjs.map} +1 -1
- package/dist/{chunk-UR2JKFIO.cjs → chunk-FSWJCGRJ.cjs} +7 -7
- package/dist/{chunk-UR2JKFIO.cjs.map → chunk-FSWJCGRJ.cjs.map} +1 -1
- package/dist/{chunk-RDFMBOSI.js → chunk-GKGQ4DWE.js} +120 -48
- package/dist/chunk-GKGQ4DWE.js.map +1 -0
- package/dist/{chunk-FEI4KQYW.cjs → chunk-GWRQ5EEI.cjs} +953 -881
- package/dist/chunk-GWRQ5EEI.cjs.map +1 -0
- package/dist/{chunk-NCU45OV4.cjs → chunk-GYC3OTBI.cjs} +9 -9
- package/dist/{chunk-NCU45OV4.cjs.map → chunk-GYC3OTBI.cjs.map} +1 -1
- package/dist/{chunk-WIPZ65KU.js → chunk-JCQUWIPT.js} +3 -3
- package/dist/{chunk-2IL7SPBP.js → chunk-KRYLVEXU.js} +2 -2
- package/dist/{chunk-YNRK3N2D.js → chunk-KWNVQZ5Y.js} +4 -4
- package/dist/{chunk-QXDEXQDI.js → chunk-MLSDBNKT.js} +2 -2
- package/dist/{chunk-65ZXR4YJ.js → chunk-NGY4KET4.js} +2 -2
- package/dist/{chunk-QKGNSXF2.js → chunk-OCJG3X4U.js} +2 -2
- package/dist/{chunk-VAAQZZHT.js → chunk-ONDI64M3.js} +2 -2
- package/dist/{chunk-BKQWCJP5.js → chunk-QGQ26NEE.js} +2 -2
- package/dist/{chunk-RSZXB6J2.js → chunk-RDKJCZHY.js} +3 -3
- package/dist/chunk-RDKJCZHY.js.map +1 -0
- package/dist/{chunk-CSXEOFJ4.cjs → chunk-SUPBFRUK.cjs} +5 -5
- package/dist/{chunk-CSXEOFJ4.cjs.map → chunk-SUPBFRUK.cjs.map} +1 -1
- package/dist/{chunk-I7RGBCG6.cjs → chunk-UJ7MSY6F.cjs} +31 -31
- package/dist/{chunk-I7RGBCG6.cjs.map → chunk-UJ7MSY6F.cjs.map} +1 -1
- package/dist/{chunk-UW42K54L.cjs → chunk-UV53Z64O.cjs} +14 -14
- package/dist/{chunk-UW42K54L.cjs.map → chunk-UV53Z64O.cjs.map} +1 -1
- package/dist/{chunk-NFMYNXFH.cjs → chunk-WCM4BGHO.cjs} +97 -97
- package/dist/{chunk-NFMYNXFH.cjs.map → chunk-WCM4BGHO.cjs.map} +1 -1
- package/dist/{chunk-57G6CG2E.js → chunk-YUAKZNK5.js} +6 -6
- package/dist/{chunk-XQJPFAA7.cjs → chunk-ZENRHE55.cjs} +62 -62
- package/dist/{chunk-XQJPFAA7.cjs.map → chunk-ZENRHE55.cjs.map} +1 -1
- package/dist/{chunk-PHIRRCPY.js → chunk-ZJ7RI62F.js} +2 -2
- package/dist/components/calendar.cjs +81 -4
- package/dist/components/calendar.cjs.map +1 -1
- package/dist/components/calendar.js +81 -4
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +3 -3
- package/dist/components/chat/index.js +2 -2
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +6 -6
- package/dist/components/docs/index.js +5 -5
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +5 -3
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +1 -1
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +4 -2
- package/dist/components/features/use-walkthrough-video.d.ts +1 -1
- package/dist/components/features/use-walkthrough-video.d.ts.map +1 -1
- package/dist/components/features/{floating-walkthrough-video.d.ts → walkthrough-video.d.ts} +61 -7
- package/dist/components/features/walkthrough-video.d.ts.map +1 -0
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +144 -140
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +16 -12
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/context-panel.d.ts +24 -6
- package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +529 -425
- package/dist/components/meeting-scheduler/index.cjs.map +1 -1
- package/dist/components/meeting-scheduler/index.d.ts +54 -1
- package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.js +507 -403
- package/dist/components/meeting-scheduler/index.js.map +1 -1
- package/dist/components/meeting-scheduler/slot-picker.d.ts +35 -31
- package/dist/components/meeting-scheduler/slot-picker.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/related-content/index.cjs +4 -4
- package/dist/components/related-content/index.js +3 -3
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/date-picker.d.ts +62 -1
- package/dist/components/ui/date-picker.d.ts.map +1 -1
- package/dist/components/ui/field-wrapper.d.ts +7 -0
- package/dist/components/ui/field-wrapper.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +36 -36
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +5 -3
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +4 -2
- package/dist/hooks/use-meeting-booking.d.ts +31 -8
- package/dist/hooks/use-meeting-booking.d.ts.map +1 -1
- package/dist/index.cjs +7 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -2
- package/package.json +1 -1
- package/src/components/features/{.floating-walkthrough-video.md → .walkthrough-video.md} +8 -5
- package/src/components/features/index.ts +1 -1
- package/src/components/features/use-walkthrough-video.ts +1 -1
- package/src/components/features/{floating-walkthrough-video.tsx → walkthrough-video.tsx} +108 -22
- package/src/components/meeting-scheduler/booking-form.tsx +60 -51
- package/src/components/meeting-scheduler/confirmation.tsx +1 -1
- package/src/components/meeting-scheduler/context-panel.tsx +166 -68
- package/src/components/meeting-scheduler/index.tsx +227 -81
- package/src/components/meeting-scheduler/slot-picker.tsx +275 -127
- package/src/components/ui/date-picker.tsx +200 -47
- package/src/components/ui/field-wrapper.tsx +9 -2
- package/src/hooks/use-meeting-booking.ts +97 -73
- package/dist/chunk-7J4DGFHZ.js +0 -88
- package/dist/chunk-7J4DGFHZ.js.map +0 -1
- package/dist/chunk-DCB4EBA5.cjs.map +0 -1
- package/dist/chunk-FEI4KQYW.cjs.map +0 -1
- package/dist/chunk-MKPJYEOR.cjs +0 -88
- package/dist/chunk-MKPJYEOR.cjs.map +0 -1
- package/dist/chunk-RDFMBOSI.js.map +0 -1
- package/dist/chunk-RSZXB6J2.js.map +0 -1
- package/dist/components/features/floating-walkthrough-video.d.ts.map +0 -1
- /package/dist/{chunk-F3F3PVJ3.js.map → chunk-44U25GD5.js.map} +0 -0
- /package/dist/{chunk-IGO6GNG7.js.map → chunk-5AEHCGQV.js.map} +0 -0
- /package/dist/{chunk-GMDPQ5FQ.js.map → chunk-B6TM3QAL.js.map} +0 -0
- /package/dist/{chunk-WIPZ65KU.js.map → chunk-JCQUWIPT.js.map} +0 -0
- /package/dist/{chunk-2IL7SPBP.js.map → chunk-KRYLVEXU.js.map} +0 -0
- /package/dist/{chunk-YNRK3N2D.js.map → chunk-KWNVQZ5Y.js.map} +0 -0
- /package/dist/{chunk-QXDEXQDI.js.map → chunk-MLSDBNKT.js.map} +0 -0
- /package/dist/{chunk-65ZXR4YJ.js.map → chunk-NGY4KET4.js.map} +0 -0
- /package/dist/{chunk-QKGNSXF2.js.map → chunk-OCJG3X4U.js.map} +0 -0
- /package/dist/{chunk-VAAQZZHT.js.map → chunk-ONDI64M3.js.map} +0 -0
- /package/dist/{chunk-BKQWCJP5.js.map → chunk-QGQ26NEE.js.map} +0 -0
- /package/dist/{chunk-57G6CG2E.js.map → chunk-YUAKZNK5.js.map} +0 -0
- /package/dist/{chunk-PHIRRCPY.js.map → chunk-ZJ7RI62F.js.map} +0 -0
|
@@ -68,7 +68,7 @@ export * from './video-bites-display'
|
|
|
68
68
|
export * from './card-hit-layer'
|
|
69
69
|
export * from './video-hover-preview'
|
|
70
70
|
export * from './entity-video-section'
|
|
71
|
-
export * from './
|
|
71
|
+
export * from './walkthrough-video'
|
|
72
72
|
export * from './use-walkthrough-video'
|
|
73
73
|
export * from './use-video-warmup'
|
|
74
74
|
export * from './captions-url'
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { useQuery } from '@tanstack/react-query';
|
|
20
|
-
import type { WalkthroughVideoData } from './floating-walkthrough-video';
|
|
21
20
|
import { useCaptions } from './use-captions';
|
|
21
|
+
import type { WalkthroughVideoData } from './walkthrough-video';
|
|
22
22
|
|
|
23
23
|
export interface UseWalkthroughVideoOptions {
|
|
24
24
|
/** Absolute or proxied URL of the public GET route. */
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* THE generic, embeddable per-platform demo-video widget, in the two placements
|
|
5
|
+
* hosts actually mount: <FloatingWalkthroughVideo> and <InlineWalkthroughVideo>.
|
|
6
|
+
*
|
|
7
|
+
* SHARED GRAMMAR. A collapsed card (bite-identical hover grammar via
|
|
7
8
|
* <VideoHoverPreviewSurface>) that opens a large in-page theater (Radix Dialog
|
|
8
9
|
* primitives, NOT native fullscreen) showing ONLY the video: a bare 16:9 stage
|
|
9
10
|
* with the player's own controls + captions. No card chrome, no summary.
|
|
11
|
+
* - FLOATING pins that card to a bottom corner over the page — left unless
|
|
12
|
+
* the video data says `position: 'right'`.
|
|
13
|
+
* - INLINE lays it out in the host's own flow, filling its container at 16:9.
|
|
14
|
+
* Every overlay-only behaviour drops out with the pinning: no fixed
|
|
15
|
+
* positioning, no z-layer, no shadow, no appear delay (nothing to stagger
|
|
16
|
+
* against an already-painted page), no footer fade (no footer to collide
|
|
17
|
+
* with) and no dismissal (an X is an overlay's "get out of my way"; in a
|
|
18
|
+
* host's own layout it just leaves a hole, and the cookie is per-platform,
|
|
19
|
+
* so one dismissal in the corner would silently empty the in-page block).
|
|
20
|
+
*
|
|
21
|
+
* ONE ENGINE, TWO PRESETS. <WalkthroughVideo> below takes `placement` and is
|
|
22
|
+
* deliberately NOT exported: placement is a constant at every call site, so the
|
|
23
|
+
* public API is two components that say what they are instead of one that takes
|
|
24
|
+
* a mode — a <FloatingWalkthroughVideo placement="inline"> reads as a
|
|
25
|
+
* contradiction, and naming a family after one of its variants is what produced
|
|
26
|
+
* it. The presets are one line each; everything below the wrapper — the player
|
|
27
|
+
* mode machine, the audio invariant, the corner map, dismissal — stays shared,
|
|
28
|
+
* because forking THAT is how the two placements would drift.
|
|
29
|
+
*
|
|
30
|
+
* The split pays off in the types: the overlay-only knobs are absent from
|
|
31
|
+
* `InlineWalkthroughVideoProps` outright, so "floating only" is a compile error
|
|
32
|
+
* rather than a line of prose each caller has to read.
|
|
10
33
|
*
|
|
11
34
|
* All UI lives here in the lib so every platform site AND the react-embedding
|
|
12
35
|
* example mount the same component; the host supplies only the video data.
|
|
@@ -78,7 +101,8 @@ export interface WalkthroughVideoData {
|
|
|
78
101
|
position?: 'left' | 'right' | null;
|
|
79
102
|
}
|
|
80
103
|
|
|
81
|
-
|
|
104
|
+
/** Everything the two placements share — the widget's actual contract. */
|
|
105
|
+
interface WalkthroughVideoBaseProps {
|
|
82
106
|
video: WalkthroughVideoData | null | undefined;
|
|
83
107
|
open?: boolean;
|
|
84
108
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -101,17 +125,35 @@ export interface FloatingWalkthroughVideoProps {
|
|
|
101
125
|
* so the hydrated (non-portal) markup is identical either way. */
|
|
102
126
|
deepLinkParam?: string;
|
|
103
127
|
label?: string;
|
|
128
|
+
className?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* <FloatingWalkthroughVideo>: the shared contract plus the knobs that only mean
|
|
133
|
+
* something for an OVERLAY. Each of these is inert inline — the appear delay
|
|
134
|
+
* staggers against a page the visitor hasn't seen yet, dismissal is an overlay
|
|
135
|
+
* affordance, and the footer fade (with the `pathname` that re-queries its
|
|
136
|
+
* target) exists to keep a pinned card off the page chrome. So they live here,
|
|
137
|
+
* not in the base.
|
|
138
|
+
*/
|
|
139
|
+
export interface FloatingWalkthroughVideoProps extends WalkthroughVideoBaseProps {
|
|
104
140
|
appearDelayMs?: number;
|
|
105
141
|
/** Cookie-based dismissal (id-match, mirrors the announcement bar). `false`
|
|
106
142
|
* disables the X entirely. `storageKey` is the per-platform cookie name. */
|
|
107
143
|
dismissal?: { storageKey?: string } | false;
|
|
144
|
+
/** Fades the card out while `hideNearSelector` is in view. */
|
|
108
145
|
hideNearSelector?: string;
|
|
109
146
|
/** Route identity from the host (the lib can't observe navigation). Changing
|
|
110
147
|
* it re-queries the footer IO target. */
|
|
111
148
|
pathname?: string;
|
|
112
|
-
className?: string;
|
|
113
149
|
}
|
|
114
150
|
|
|
151
|
+
/** <InlineWalkthroughVideo>: the shared contract and nothing else — see above. */
|
|
152
|
+
export type InlineWalkthroughVideoProps = WalkthroughVideoBaseProps;
|
|
153
|
+
|
|
154
|
+
/** Engine props. Internal: `placement` never reaches a call site. */
|
|
155
|
+
type WalkthroughVideoProps = FloatingWalkthroughVideoProps & { placement: 'floating' | 'inline' };
|
|
156
|
+
|
|
115
157
|
/** "Paused at the end" — used by BOTH the close snapshot and the reopen seed,
|
|
116
158
|
* which must agree or a finished video reopens at its own last frame. */
|
|
117
159
|
const isAtEnd = (duration: number, time: number) => duration > 0 && duration - time < 1;
|
|
@@ -124,7 +166,7 @@ interface Handoff {
|
|
|
124
166
|
playing: boolean;
|
|
125
167
|
}
|
|
126
168
|
|
|
127
|
-
|
|
169
|
+
function WalkthroughVideo({
|
|
128
170
|
video,
|
|
129
171
|
open: openProp,
|
|
130
172
|
onOpenChange,
|
|
@@ -132,12 +174,17 @@ export function FloatingWalkthroughVideo({
|
|
|
132
174
|
defaultOpenPaused,
|
|
133
175
|
deepLinkParam = WALKTHROUGH_OPEN_QUERY_PARAM,
|
|
134
176
|
label = 'Play Demo Video',
|
|
177
|
+
placement,
|
|
178
|
+
// Keeps the FLOATING card off the critical path. The inline preset passes 0:
|
|
179
|
+
// an in-page block is part of a page the visitor is already looking at, so
|
|
180
|
+
// staggering it in would only jump the layout.
|
|
135
181
|
appearDelayMs = 3000,
|
|
136
182
|
dismissal = {},
|
|
137
183
|
hideNearSelector = 'footer',
|
|
138
184
|
pathname,
|
|
139
185
|
className,
|
|
140
|
-
}:
|
|
186
|
+
}: WalkthroughVideoProps): React.ReactElement | null {
|
|
187
|
+
const inline = placement === 'inline';
|
|
141
188
|
const dismissEnabled = dismissal !== false;
|
|
142
189
|
const storageKey = (dismissEnabled && dismissal.storageKey) || WALKTHROUGH_VIDEO_DISMISS_KEY;
|
|
143
190
|
// id-match dismissal key: a new video (new id) re-shows the card. No id
|
|
@@ -257,6 +304,12 @@ export function FloatingWalkthroughVideo({
|
|
|
257
304
|
// --- footer fade (re-query on pathname change; null-tolerant) ---
|
|
258
305
|
useEffect(() => {
|
|
259
306
|
if (!mounted) return;
|
|
307
|
+
// An inline card scrolls WITH the page — fading it out over the footer
|
|
308
|
+
// would blank the very block the visitor scrolled down to.
|
|
309
|
+
if (inline) {
|
|
310
|
+
setFooterHidden(false);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
260
313
|
const el = typeof document !== 'undefined' ? document.querySelector(hideNearSelector) : null;
|
|
261
314
|
if (!el || typeof IntersectionObserver === 'undefined') {
|
|
262
315
|
setFooterHidden(false);
|
|
@@ -268,7 +321,7 @@ export function FloatingWalkthroughVideo({
|
|
|
268
321
|
);
|
|
269
322
|
io.observe(el);
|
|
270
323
|
return () => io.disconnect();
|
|
271
|
-
}, [mounted, hideNearSelector, pathname]);
|
|
324
|
+
}, [mounted, inline, hideNearSelector, pathname]);
|
|
272
325
|
|
|
273
326
|
// --- tab-hidden: pause resume-mode playback, keep the timestamp ---
|
|
274
327
|
useEffect(() => {
|
|
@@ -697,7 +750,10 @@ export function FloatingWalkthroughVideo({
|
|
|
697
750
|
// badge accents off it. It must live on an element that actually
|
|
698
751
|
// receives pointer events — the media layer is pointer-events-none, so
|
|
699
752
|
// a group inside it can never match :hover.
|
|
700
|
-
'group/card pointer-events-auto relative overflow-hidden rounded-
|
|
753
|
+
'group/card pointer-events-auto relative overflow-hidden rounded-md border border-ods-border bg-ods-card',
|
|
754
|
+
// The lift is what separates a FLOATING card from the page beneath it;
|
|
755
|
+
// an in-flow block sitting inside a host card needs no shadow at all.
|
|
756
|
+
!inline && 'shadow-2xl',
|
|
701
757
|
// MOBILE = PiP dimensions, not a shrunken desktop card. This is an
|
|
702
758
|
// UNINVITED overlay on the smallest screens, so it is sized against
|
|
703
759
|
// the corner-PiP conventions, NOT against system PiP (which is
|
|
@@ -713,7 +769,9 @@ export function FloatingWalkthroughVideo({
|
|
|
713
769
|
// Do NOT go below ~150px: the corner chrome (32px transport pair +
|
|
714
770
|
// 32px dismiss + the title pill) stops fitting, and the card's own
|
|
715
771
|
// controls — not the poster — are what set the real floor here.
|
|
716
|
-
'aspect-video
|
|
772
|
+
'aspect-video transition-opacity duration-200',
|
|
773
|
+
// Inline: the HOST owns the width — the card only keeps 16:9 within it.
|
|
774
|
+
inline ? 'w-full' : 'w-[min(52vw,192px)] sm:w-80',
|
|
717
775
|
footerHidden ? 'opacity-0 pointer-events-none' : 'opacity-100',
|
|
718
776
|
className,
|
|
719
777
|
)}
|
|
@@ -906,18 +964,23 @@ export function FloatingWalkthroughVideo({
|
|
|
906
964
|
|
|
907
965
|
return (
|
|
908
966
|
<>
|
|
909
|
-
{showCard &&
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
967
|
+
{showCard &&
|
|
968
|
+
(inline ? (
|
|
969
|
+
// No wrapper: a positioning / z-index / safe-area shell around an
|
|
970
|
+
// in-flow block is exactly what stops it sizing to its container.
|
|
971
|
+
collapsed
|
|
972
|
+
) : (
|
|
973
|
+
<div
|
|
974
|
+
className={cn(
|
|
975
|
+
'pointer-events-none fixed bottom-0 p-[var(--spacing-system-mf)]',
|
|
976
|
+
video.position === 'right' ? 'right-0' : 'left-0',
|
|
977
|
+
WALKTHROUGH_Z,
|
|
978
|
+
)}
|
|
979
|
+
style={{ paddingBottom: 'max(var(--spacing-system-mf), env(safe-area-inset-bottom))' }}
|
|
980
|
+
>
|
|
981
|
+
{collapsed}
|
|
982
|
+
</div>
|
|
983
|
+
))}
|
|
921
984
|
|
|
922
985
|
<DialogPrimitive.Root open={open} onOpenChange={handleOpenChange}>
|
|
923
986
|
<DialogPortal>
|
|
@@ -993,3 +1056,26 @@ export function FloatingWalkthroughVideo({
|
|
|
993
1056
|
</>
|
|
994
1057
|
);
|
|
995
1058
|
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* The widget pinned into a bottom corner over the page — the app-shell / site
|
|
1062
|
+
* placement. Every host that wants "a demo video somewhere on the page, out of
|
|
1063
|
+
* the way" mounts this.
|
|
1064
|
+
*/
|
|
1065
|
+
export function FloatingWalkthroughVideo(props: FloatingWalkthroughVideoProps): React.ReactElement {
|
|
1066
|
+
return <WalkthroughVideo {...props} placement="floating" />;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* The same widget as an in-flow block, sized by whatever the host puts it in —
|
|
1071
|
+
* for a page that gives the video a slot of its own rather than a corner.
|
|
1072
|
+
*
|
|
1073
|
+
* The two overlay defaults are fixed here rather than left to the caller: an
|
|
1074
|
+
* in-page block has nothing to stagger against (`appearDelayMs={0}`), and a
|
|
1075
|
+
* dismissable one would leave the host's layout with a hole while sharing the
|
|
1076
|
+
* floating card's per-platform cookie (`dismissal={false}`). Both are stated
|
|
1077
|
+
* once here instead of at every call site, where forgetting either is silent.
|
|
1078
|
+
*/
|
|
1079
|
+
export function InlineWalkthroughVideo(props: InlineWalkthroughVideoProps): React.ReactElement {
|
|
1080
|
+
return <WalkthroughVideo {...props} placement="inline" appearDelayMs={0} dismissal={false} />;
|
|
1081
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../schemas/meeting-booking-schema'
|
|
12
12
|
import {
|
|
13
13
|
Button,
|
|
14
|
+
FieldWrapper,
|
|
14
15
|
Input,
|
|
15
16
|
Textarea,
|
|
16
17
|
Label,
|
|
@@ -104,65 +105,76 @@ export function BookingForm({
|
|
|
104
105
|
|
|
105
106
|
// Field chrome mirrors ContactForm 1:1 (`contact/contact-form.tsx`) — the
|
|
106
107
|
// booking form must be indistinguishable from every other form in the app.
|
|
107
|
-
const inputClass = 'bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-12'
|
|
108
|
+
const inputClass = 'bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-11 md:h-12'
|
|
109
|
+
// One step ABOVE the `spacing system/m` the design names (16/24 instead of
|
|
110
|
+
// 12/16), because the field messages hang out of flow: they need ~16px on a
|
|
111
|
+
// phone and ~20 on desktop of clear space under the control, and `m` leaves
|
|
112
|
+
// 12/16 — four pixels short at both ends, so an error would print over the
|
|
113
|
+
// next field's label. The design has no error state drawn; this is the
|
|
114
|
+
// smallest ODS step that houses it.
|
|
115
|
+
const FORM_STACK = 'flex flex-col gap-[var(--spacing-system-l)]'
|
|
108
116
|
|
|
109
117
|
return (
|
|
110
|
-
<form onSubmit={submit} className=
|
|
118
|
+
<form onSubmit={submit} className={FORM_STACK} noValidate>
|
|
111
119
|
<HoneypotField {...honeypotInputProps} />
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
{/* Email first, name pair below — the order the desktop and mobile mocks
|
|
122
|
+
both draw. The pair stays TWO columns even on a phone (164px each at
|
|
123
|
+
375): two short fields side by side cost one line instead of two on
|
|
124
|
+
the layout that can least afford them.
|
|
125
|
+
|
|
126
|
+
Every field goes through `FieldWrapper`, which hangs its message OUT
|
|
127
|
+
OF FLOW below the control. That is the whole reason it is here: a
|
|
128
|
+
message rendered in flow grows its field, which pushes everything
|
|
129
|
+
under it down and — inside a card that states its height — walks the
|
|
130
|
+
submit button off the bottom the moment validation fails. Required-ness
|
|
131
|
+
is carried by `required` on the control (read out by assistive tech),
|
|
132
|
+
not by an asterisk the design does not draw. */}
|
|
133
|
+
<FieldWrapper label="Email" htmlFor="ms-email" error={errors.email?.message}>
|
|
134
|
+
<Input
|
|
135
|
+
id="ms-email"
|
|
136
|
+
type="email"
|
|
137
|
+
required
|
|
138
|
+
autoComplete="email"
|
|
139
|
+
placeholder="jane@company.com"
|
|
140
|
+
aria-invalid={!!errors.email}
|
|
141
|
+
className={inputClass}
|
|
142
|
+
{...register('email')}
|
|
143
|
+
/>
|
|
144
|
+
</FieldWrapper>
|
|
145
|
+
|
|
146
|
+
<div className="grid grid-cols-2 gap-[var(--spacing-system-m)]">
|
|
147
|
+
<FieldWrapper label="First Name" htmlFor="ms-first-name" error={errors.firstName?.message}>
|
|
118
148
|
<Input
|
|
119
149
|
id="ms-first-name"
|
|
150
|
+
required
|
|
120
151
|
autoComplete="given-name"
|
|
121
152
|
placeholder="Jane"
|
|
122
153
|
aria-invalid={!!errors.firstName}
|
|
123
154
|
className={inputClass}
|
|
124
155
|
{...register('firstName')}
|
|
125
156
|
/>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<div className="flex flex-col">
|
|
129
|
-
<Label htmlFor="ms-last-name">
|
|
130
|
-
Last name<span className="text-ods-accent">*</span>
|
|
131
|
-
</Label>
|
|
157
|
+
</FieldWrapper>
|
|
158
|
+
<FieldWrapper label="Last Name" htmlFor="ms-last-name" error={errors.lastName?.message}>
|
|
132
159
|
<Input
|
|
133
160
|
id="ms-last-name"
|
|
161
|
+
required
|
|
134
162
|
autoComplete="family-name"
|
|
135
163
|
placeholder="Doe"
|
|
136
164
|
aria-invalid={!!errors.lastName}
|
|
137
165
|
className={inputClass}
|
|
138
166
|
{...register('lastName')}
|
|
139
167
|
/>
|
|
140
|
-
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<div className="flex flex-col">
|
|
145
|
-
<Label htmlFor="ms-email">
|
|
146
|
-
Email<span className="text-ods-accent">*</span>
|
|
147
|
-
</Label>
|
|
148
|
-
<Input
|
|
149
|
-
id="ms-email"
|
|
150
|
-
type="email"
|
|
151
|
-
autoComplete="email"
|
|
152
|
-
placeholder="jane@company.com"
|
|
153
|
-
aria-invalid={!!errors.email}
|
|
154
|
-
className={inputClass}
|
|
155
|
-
{...register('email')}
|
|
156
|
-
/>
|
|
157
|
-
{errors.email && <span className="text-ods-error text-h6 mt-1">{errors.email.message}</span>}
|
|
168
|
+
</FieldWrapper>
|
|
158
169
|
</div>
|
|
159
170
|
|
|
160
171
|
{supportedFields.map((field) => (
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
172
|
+
<FieldWrapper
|
|
173
|
+
key={field.name}
|
|
174
|
+
label={field.label}
|
|
175
|
+
htmlFor={`ms-q-${field.name}`}
|
|
176
|
+
error={fieldError(field.name)}
|
|
177
|
+
>
|
|
166
178
|
{field.type === 'textarea' && (
|
|
167
179
|
<Textarea
|
|
168
180
|
id={`ms-q-${field.name}`}
|
|
@@ -173,7 +185,7 @@ export function BookingForm({
|
|
|
173
185
|
{field.type === 'text' && (
|
|
174
186
|
<Input
|
|
175
187
|
id={`ms-q-${field.name}`}
|
|
176
|
-
className="bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-12"
|
|
188
|
+
className="bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-11 md:h-12"
|
|
177
189
|
{...register(`formFields.${field.name}` as never)}
|
|
178
190
|
/>
|
|
179
191
|
)}
|
|
@@ -226,14 +238,16 @@ export function BookingForm({
|
|
|
226
238
|
)}
|
|
227
239
|
/>
|
|
228
240
|
)}
|
|
229
|
-
|
|
230
|
-
<span className="text-ods-error text-h6 mt-1">{fieldError(field.name)}</span>
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
241
|
+
</FieldWrapper>
|
|
233
242
|
))}
|
|
234
243
|
|
|
235
244
|
{legalConsent && (
|
|
236
|
-
|
|
245
|
+
// Same out-of-flow message as the fields above, so a missed consent
|
|
246
|
+
// box doesn't shove the submit button down the card. With no error the
|
|
247
|
+
// wrapper is `display:contents` and the panel below is the flex item,
|
|
248
|
+
// exactly as it was.
|
|
249
|
+
<FieldWrapper error={errors.legalConsentResponses?.message as string | undefined}>
|
|
250
|
+
<div className="flex flex-col gap-[var(--spacing-system-xs)] border border-ods-border rounded-md p-[var(--spacing-system-m)]">
|
|
237
251
|
{/* GDPR surface — HubSpot's consent copy rendered VERBATIM, never edited. */}
|
|
238
252
|
<p className="text-h6 text-ods-text-secondary">{legalConsent.processingConsentText}</p>
|
|
239
253
|
{legalConsent.communicationConsentText && (
|
|
@@ -260,10 +274,7 @@ export function BookingForm({
|
|
|
260
274
|
)
|
|
261
275
|
}
|
|
262
276
|
/>
|
|
263
|
-
<Label htmlFor={`ms-consent-${box.communicationTypeId}`}>
|
|
264
|
-
{box.label}
|
|
265
|
-
{box.required ? ' *' : ''}
|
|
266
|
-
</Label>
|
|
277
|
+
<Label htmlFor={`ms-consent-${box.communicationTypeId}`}>{box.label}</Label>
|
|
267
278
|
</div>
|
|
268
279
|
)
|
|
269
280
|
})}
|
|
@@ -273,18 +284,16 @@ export function BookingForm({
|
|
|
273
284
|
{legalConsent.privacyPolicyText && (
|
|
274
285
|
<p className="text-h6 text-ods-text-secondary">{legalConsent.privacyPolicyText}</p>
|
|
275
286
|
)}
|
|
276
|
-
{errors.legalConsentResponses && (
|
|
277
|
-
<p className="text-h6 text-ods-error">{errors.legalConsentResponses.message as string}</p>
|
|
278
|
-
)}
|
|
279
287
|
</div>
|
|
288
|
+
</FieldWrapper>
|
|
280
289
|
)}
|
|
281
290
|
|
|
282
291
|
{/* Step navigation back to the calendar lives in the step header (the
|
|
283
292
|
app-standard BackButton, rendered by the parent) — the form ships
|
|
284
293
|
only its submit. */}
|
|
285
294
|
<div className="flex">
|
|
286
|
-
<Button type="submit" loading={isSubmitting} disabled={isSubmitting}
|
|
287
|
-
Confirm
|
|
295
|
+
<Button type="submit" loading={isSubmitting} disabled={isSubmitting}>
|
|
296
|
+
Confirm Booking
|
|
288
297
|
</Button>
|
|
289
298
|
</div>
|
|
290
299
|
</form>
|
|
@@ -27,7 +27,7 @@ export function Confirmation({ confirmation, timezone }: ConfirmationProps) {
|
|
|
27
27
|
}).format(new Date(confirmation.startTimeMs))
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<div className="flex flex-1 flex-col items-center justify-center gap-[var(--spacing-system-
|
|
30
|
+
<div className="flex flex-1 flex-col items-center justify-center gap-[var(--spacing-system-m)] text-center">
|
|
31
31
|
<p className="text-h1" role="img" aria-label="Celebration">
|
|
32
32
|
🎉
|
|
33
33
|
</p>
|