import type { ComponentType, ReactNode } from 'react'; import { type BookingConfirmation, type MeetingAvailability, type MeetingBookingErrorCode, type MeetingHost } from '../../schemas/meeting-booking-schema'; import { type BookingFormProps } from './booking-form'; export interface HubSpotMeetingSchedulerProps { /** Directory id of the meeting link (from the host's `/api/meetings` payload). */ meetingId: string; /** Endpoints prefix, default '' (same-origin `/api/meetings/*`) — FaqSection precedent. */ apiBaseUrl?: string; /** SSR-mode seed (host-fetched). Omitted → client mode (self-fetch on mount). */ initialAvailability?: MeetingAvailability; /** Meeting title shown in the context panel (host page owns the h1). */ title?: string; /** Short description under the title in the context panel. */ description?: string | null; /** Override the hosts shown (defaults to `availability.hosts`). */ hosts?: MeetingHost[]; /** Pin the DISPLAY zone (rendering only — never sent upstream, never a cache key). */ displayTimezone?: string; /** The link's public HubSpot booking URL — the "Open in HubSpot" escape hatch target. */ fallbackUrl?: string; /** * Host-level exit, rendered as a back edge at the top of the context panel * (and in the loading skeleton, so it doesn't pop in). This is "leave the * scheduler", NOT the details step's back edge — it is suppressed once a * slot is chosen so only one Back is ever on screen. Omit it and no back * affordance renders at all. */ onBack?: () => void; onBooked?: (b: BookingConfirmation) => void; className?: string; /** * Panel order. * * `'slot-first'` (default, unchanged): calendar → details → confirmed. * `'details-first'`: the form comes FIRST and the slot click submits the * already-validated payload. Campaign landing pages use it, where "tell us * about your setup, then pick a time" is the conversion shape. * * This is not a cosmetic swap — see the flow-dependent branches below. It is * opt-in precisely so `slot-first` stays byte-identical. */ flow?: SchedulerFlow; /** * Replace the details panel — step ONE under `flow="details-first"`, step two * otherwise — with a host-supplied form. It receives exactly what the built-in * one does, so the injected form keeps the widget's contract: the deferred * schema, the honeypot and elapsed-ms signals, the verbatim consent block, the * `onSubmit` handoff and the `isSubmitting` lock. * * The intended shape is a THIN wrapper that re-renders `BookingForm` with * `fieldRows` (a layout re-arrangement, all machinery reused), not a * hand-written form. Anything reimplementing the contract loses the bot * protection and the consent guarantees. * * A COMPONENT type, not a render callback: React then owns its identity, so * it reconciles across steps and keeps its own state instead of remounting — * and the honeypot `ref` rides through as a normal prop. * * Defaults to the built-in form, so every existing embed is untouched. */ detailsForm?: ComponentType; /** * The DATA form of the same override: `fieldRows` and a host consent row, * spread onto whichever form renders. Serialisable, so a Server Component * can pass it across the RSC boundary where a component cannot. */ detailsFormProps?: Pick; } type Step = 'slot' | 'details' | 'confirmed'; /** * The widget's height on desktop: a FIXED 380px, and the ONLY size the whole * card states. * * A floor was not enough. Every stage has a different natural height — the * context panel with two hosts ~340, the calendar column ~290, a degraded * stage two lines — so `min-height` only pinned the SHORT ones and let the * tall one push the wrapper, which is exactly the screen-shake this removes. * With a fixed height the box is stated once and nothing inside can move it; * anything taller scrolls in place. * * The elastic half derives from it: the panel passes it to the slot row and * the times column scrolls inside what is left. The calendar does not stretch * — a date grid has a size, and stretching one is how a month turns into * page-tall bands the moment a layout stops pinning a height. It states a * WIDTH (`CALENDAR_W`) and its square cells make it as tall as it is wide; * that width is chosen to fit inside this number, and the two are checked * against each other in one place rather than three. * * Six week rows, always — `fixedWeeks` — is why a height can be stated at all: * a 5-week month and a 6-week month occupy the same box, so paging the * calendar cannot resize the card. * * Exported because the stability has to survive OUTSIDE the card too: a host * that swaps the scheduler in and out of a slot (the onboarding "Book a call" * promo does exactly that) reserves the same box for whatever it shows * instead, so the swap moves nothing below it. * * Goes on the BORDERED element, always. Under `box-sizing: border-box` the * declared height swallows the 1px edges, so a card that carries the border * and a host box that carries its own both come out at exactly 380 — put it on * an inner wrapper instead and the border lands OUTSIDE the 380, making that * card 2px taller than everything it is supposed to match. * * TWO numbers, because the card has two shapes: 380 as a sidebar beside the * calendar (`lg`), 550 as a header strip over it (`md`). Phones get neither — * there a card the height of the hand holding it is the right answer and the * page is the scroller. * * A host that reserves this box owes it one thing: content that ADAPTS to the * height rather than assuming it. The onboarding promo does it by letting its * video take the leftover space at 16:9; a stand-in that just stacks fixed * blocks will overflow the shorter of the two. */ export declare const MEETING_SCHEDULER_H = "md:h-[34.375rem] lg:h-[23.75rem]"; /** * The same box for `flow="details-first"`, where the tallest stage is the * DETAILS FORM (email + first/last + the link's declared questions + the * consent block + Continue) rather than the calendar. * * Still a fixed pair, not a floor: everything below the card derives a definite * height from it, and a floor was already tried here — it pinned the short * stages and let the tall one push the wrapper, which is the screen-shake the * fixed height exists to remove. * * ONE number from `md` up, not two: 638px, the card both mocks draw * (`4904:117130` form-only, `4904:118213` three columns). The form stage has no * sidebar to stack into a header strip, so nothing about it changes between * tablet and desktop, and the calendar stage fits the same box at both — the * times column scrolls inside it as it always has. * * PHONES get a stated height too — the one place this flow departs from * slot-first's "the page is the scroller". Slot-first's two stages are the * same shape on a phone (calendar + a capped times list either way), so it * needs none. Details-first's are not: the form stage, the calendar stage and * the two-line confirmation each have their own natural height, and a paid * landing page that grows and shrinks by tens of pixels on every step reads as * broken. 812px is the calendar stage's natural height at 375px (header strip * + six fixed weeks + the 9.75rem chip cap), so that stage fits exactly and the * form stage — shorter on the shipped links — sits in it; a link declaring more * questions scrolls the form inside the card, as it already does from `md`. * * Hosts read it through `SCHEDULER_FLOW_PRESETS[flow].height`, never directly. */ export declare const MEETING_SCHEDULER_DETAILS_FIRST_H = "h-[50.75rem] md:h-[39.875rem]"; export type SchedulerFlow = 'slot-first' | 'details-first'; /** The flow every existing embed gets — the ONE spelling of the default. */ export declare const DEFAULT_SCHEDULER_FLOW: SchedulerFlow; /** * What differs between the two flows as DATA — first step, the box a host * reserves, the form's submit copy. Exported so a host that swaps the card in * and out reads `SCHEDULER_FLOW_PRESETS[flow].height` instead of re-deriving * the pairing. The behavioural branches (lock, back edge, error routing) stay * in the component: they are logic, not configuration. */ export declare const SCHEDULER_FLOW_PRESETS: Record; height: string; submitLabel: string; footerNote?: string; }>; /** * The two-line stages of the card (load failure, "booked on HubSpot", a host's * own "calendar unavailable") — ONE shape, exported so a host renders its * fallback in the same box instead of re-typing the chrome. The box is the * FLOW's: a host names the flow it would have mounted and the height follows * from {@link SCHEDULER_FLOW_PRESETS}, so the pairing is never re-derived. */ export declare function SchedulerDegradedCard({ flow, className, message, action, children, }: { flow?: SchedulerFlow; className?: string; /** The one line the stage says, in the card's own type — so a host's fallback never picks a different scale. */ message?: string; /** The one way out (the escape hatch, or a host's own link). */ action?: ReactNode; children?: ReactNode; }): import("react").JSX.Element; export declare function HubSpotMeetingScheduler({ meetingId, apiBaseUrl, initialAvailability, title, description, hosts, displayTimezone, fallbackUrl, onBack, onBooked, className, flow, detailsForm: DetailsForm, detailsFormProps, }: HubSpotMeetingSchedulerProps): import("react").JSX.Element; export { BookingForm, BookingFormSkeleton, type BookingFormProps, type BookingFieldRow, type BookingFieldSlot, type BookingFormConsent, } from './booking-form'; export { MeetingSchedulerDirectory, MeetingSchedulerDirectoryRowSkeleton, type MeetingSchedulerDirectoryProps, } from './directory'; export type { MeetingAvailability, BookingConfirmation, MeetingBookingErrorCode, MeetingHost }; export type { SchedulingLink, SchedulingLinksPayload } from '../../schemas/meeting-booking-schema'; //# sourceMappingURL=index.d.ts.map