import type * as React from 'react'; import type { AutoFocusEvent } from '../utils/dismissal.js'; import type { PassThroughProps } from '../utils/passThrough.js'; import type { Align, PopperContentProps, Side } from '../utils/popper.js'; import type { PortalProps } from '../utils/portal.js'; import type { PrimitivePropsWithRef } from '../utils/primitive.js'; import type { FocusOutsideEvent, InteractOutsideEvent } from '../utils/useDismiss.js'; /** How step presents: `tooltip` / `floating` anchor to target, `dialog` centres. */ export type TourStepType = 'tooltip' | 'dialog' | 'floating'; /** Payload of {@link TourProps.onStepChange}: the new index and the step's `id`. */ export interface TourStepChange { /** Zero-based index of the step now active. */ index: number; /** The active step's `id`, when it declares one. */ id?: string | undefined; } /** One step of a {@link Tour}. Stepper are data, not markup — the whole list is passed to the root. */ export interface TourStep { /** * Stable id for step; persist instead of index (which shifts on add/remove). * @default undefined — `onStepChange` reports the index with no id. */ id?: string | undefined; /** * Element step points at (CSS selector or ref); no target renders centred. * @default undefined — centred with no spotlight. */ target?: string | React.RefObject | undefined; /** * Short name announced with step position (render visible heading with `TourTitle`). * @default undefined — position only ("Step 2 of 5"). */ title?: string | undefined; /** * Body text rendered as `TourDescription`. * @default undefined — no description. */ description?: string | undefined; /** * Side of target content is placed against (fed to popper). * @default undefined — falls back to `TourContent`'s `side`. */ placement?: Side | undefined; /** * Alignment along `placement`. * @default undefined — falls back to `TourContent`'s `align`. */ align?: Align | undefined; /** * How step presents (`dialog` forces centre even with target). * @default undefined — anchored if target exists, otherwise centred. */ type?: TourStepType | undefined; /** * Whether step dims page (false renders no backdrop, drops `data-backdrop`). * @default true */ backdrop?: boolean | undefined; } /** @internal Shared state distributed from {@link Tour} to its parts via context; not public API. */ export interface TourContextValue { steps: TourStep[]; step: number; total: number; activeStep: TourStep | undefined; open: boolean; /** True when content is positioned against target (not centred). */ anchored: boolean; /** Whether active step keeps dimming layer. */ hasBackdrop: boolean; /** Whether root binds ArrowRight / ArrowLeft navigation on Content. */ keyboardNavigation: boolean; target: HTMLElement | null; spotlightStyle: React.CSSProperties; contentRef: React.RefObject; contentId: string; titleId: string; descriptionId: string; /** Whether `TourTitle` currently renders (withhold dangling `aria-labelledby`). */ hasTitle: boolean; setHasTitle: (present: boolean) => void; /** Whether `TourDescription` currently renders (withhold dangling `aria-describedby`). */ hasDescription: boolean; setHasDescription: (present: boolean) => void; closeOnEscape: boolean; closeOnInteractOutside: boolean; preventInteraction: boolean; setOpen: (open: boolean) => void; goTo: (index: number) => void; next: () => void; prev: () => void; skip: () => void; complete: () => void; } /** Props for the {@link Tour} root. */ export interface TourProps extends PassThroughProps { /** * Manually-composed parts for full control; given children, derived anatomy is not rendered. * @default undefined — the tour derives its anatomy from `steps`. */ children?: React.ReactNode | undefined; /** * Ordered steps whose length counts against the total in step numbering. */ steps: TourStep[]; /** * Whether tour is running for controlled usage; Pair with `onOpenChange`. * @default undefined — uncontrolled. */ open?: boolean | undefined; /** * Whether tour runs on mount for uncontrolled usage. * @default false — Set true to start on mount. */ defaultOpen?: boolean | undefined; /** * Fires with new open state when tour starts or ends. * @default undefined — no-op if omitted. */ onOpenChange?: ((open: boolean) => void) | undefined; /** * Fires when open-state change finishes on screen `onOpenChange`. * @default undefined — no-op if omitted. */ onOpenChangeComplete?: ((open: boolean) => void) | undefined; /** * Active step index for controlled usage; Pair with `onStepChange`. * @default undefined — uncontrolled. */ step?: number | undefined; /** * Index the tour starts on, for uncontrolled usage, useful for resuming an abandoned tour. * @default 0 */ defaultStep?: number | undefined; /** * Fires with `{ index, id }` when the step changes; persist the id, not the index. * @default undefined — no-op if omitted. */ onStepChange?: ((change: TourStepChange) => void) | undefined; /** * Fires when the user advances past the last step; the tour closes either way regardless. * @default undefined — the tour still closes, it just tells nobody. */ onComplete?: (() => void) | undefined; /** * Fires when the tour ends early — the skip or close trigger, or Escape. * @default undefined — the tour still closes, it just tells nobody. */ onSkip?: (() => void) | undefined; /** * Whether pressing Escape ends the tour the same way skipping does. * @default true — set `false` only when the tour cannot be abandoned, and then render a {@link TourSkipTrigger} or {@link TourCloseTrigger} inside {@link TourContent}. */ closeOnEscape?: boolean | undefined; /** * Whether the page behind the tour is marked `inert` and Content declares `aria-modal="true"`. * @default false — a tour points at the page, so the page stays readable and operable. */ preventInteraction?: boolean | undefined; /** * Whether a pointer-down outside the content skips the tour. * @default false — an outside click during a tour is usually a misclick on the UI being explained. */ closeOnInteractOutside?: boolean | undefined; /** * Pixels of breathing room added around the target on every side when the spotlight geometry is published. * @default 8 — clears the target's own focus ring instead of clipping it. */ spotlightPadding?: number | undefined; /** * Corner radius of the spotlight cutout in pixels, published as `--skdx-tour-radius`. * @default 0 */ spotlightRadius?: number | undefined; /** * Whether and how a step's target is scrolled into view; `behavior` forces `auto` when `prefers-reduced-motion: reduce` matches. * @default { block: 'center', inline: 'center' } */ scrollIntoView?: boolean | ScrollIntoViewOptions | undefined; /** * How long, in milliseconds, to keep polling for a step's target before * giving up — covers targets that mount late, like a lazy route. * @default 2000 — long enough for a lazy chunk to land, short enough that a genuinely absent target does not strand the tour. */ targetTimeout?: number | undefined; /** * Fires when a step's target never appeared within `targetTimeout`; the tour then skips forward rather than rendering unanchored. * @default undefined — the step is still skipped, it just tells nobody. */ onStepTargetMissing?: ((detail: { index: number; step: TourStep; }) => void) | undefined; /** * Whether ArrowRight/ArrowLeft navigate the tour when TourContent holds focus, mirrored under RTL; keys in `` are never taken. * @default false — arrow keys belong to whatever the step is pointing at. */ keyboardNavigation?: boolean | undefined; /** * Builds the string announced to screen readers on every step change. * @default undefined — announces the step's `title` (when set) followed by "Step {n} of {total}", in English. */ getAnnouncement?: ((step: TourStep, index: number, total: number) => string) | undefined; /** * Whether the derived anatomy renders a {@link TourProgressText}. Ignored when `children` is given. * @default true */ showProgress?: boolean | undefined; /** * Whether the derived anatomy renders a {@link TourSkipTrigger}. Ignored when `children` is given. * @default true */ showSkip?: boolean | undefined; /** * Whether the derived anatomy renders a {@link TourSpotlight} over the active step's target. Ignored when `children` is given. * @default true */ spotlight?: boolean | undefined; /** * DOM node the derived Portal renders the overlay into, so it inherits that subtree's theme and stacking context (e.g. a `[data-skdx-theme]` wrapper). * @default undefined — `document.body`. */ portalContainer?: Element | DocumentFragment | null | undefined; } /** Props for {@link TourPortal}, which teleports the tour's layer to `document.body`. */ export interface TourPortalProps extends PortalProps { /** * Render the portal even while the tour is closed, instead of unmounting it. * @default false */ forceMount?: boolean | undefined; } /** Props for {@link TourBackdrop}, the dimming layer with the spotlight cutout. */ export interface TourBackdropProps extends PrimitivePropsWithRef<'div'> { /** * Keep the backdrop mounted even while the tour is closed. * @default false */ forceMount?: boolean | undefined; } /** Props for {@link TourSpotlight}, the highlight box over the active step's target. */ export interface TourSpotlightProps extends PrimitivePropsWithRef<'div'> { /** * Keep the spotlight mounted even while the tour is closed; still renders nothing when the active step has no target. * @default false */ forceMount?: boolean | undefined; } /** Props for {@link TourContent}, extending the shared popper positioning props each step's `placement`/`align` can override. */ export interface TourContentProps extends PopperContentProps { /** * Keep the panel mounted even while the tour is closed. * @default false */ forceMount?: boolean | undefined; /** * Fires when Escape is pressed while the tour is open; call `event.preventDefault()` to stop it from skipping the tour. * @default undefined — no-op if omitted. */ onEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined; /** * Fires on pointer-down outside the content; call `event.preventDefault()` to stop it from skipping the tour. * @default undefined — no-op if omitted. */ onPointerDownOutside?: ((event: PointerEvent) => void) | undefined; /** * Fires when focus moves outside the content; reported only, since a tour never ends on focus alone. * @default undefined — no-op if omitted. */ onFocusOutside?: ((event: FocusOutsideEvent) => void) | undefined; /** * Fires for either kind of outside interaction, immediately before the specific handler above and with the same event. * @default undefined — no-op if omitted. */ onInteractOutside?: ((event: InteractOutsideEvent) => void) | undefined; /** * Fires before focus moves into the panel; call preventDefault to place focus yourself, once per tour or per-step. * @default undefined — no-op if omitted. */ onOpenAutoFocus?: ((event: AutoFocusEvent) => void) | undefined; /** * Fires before focus returns to the previously focused element; call preventDefault to suppress that return. * @default undefined — no-op if omitted. */ onCloseAutoFocus?: ((event: AutoFocusEvent) => void) | undefined; } /** Props for {@link TourTrigger}, the button that starts the tour. */ export interface TourTriggerProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link TourArrow}, the arrow pointing from the content back at the target. */ export interface TourArrowProps extends PrimitivePropsWithRef<'span'> { } /** Props for {@link TourTitle}, the active step's accessible name. */ export interface TourTitleProps extends PrimitivePropsWithRef<'h2'> { } /** Props for {@link TourDescription}, the active step's accessible description. */ export interface TourDescriptionProps extends PrimitivePropsWithRef<'p'> { } /** Props for {@link TourProgressText}, the visible "Step 2 of 5" line. */ export interface TourProgressTextProps extends PrimitivePropsWithRef<'p'> { /** * Builds the visible progress string from the zero-based index and the total. * @default undefined — renders "Step {index + 1} of {total}" in English. */ format?: ((index: number, total: number) => string) | undefined; } /** Props for {@link TourActions}, the row the navigation triggers sit in. */ export interface TourActionsProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link TourNextTrigger}, which advances the tour. */ export interface TourNextTriggerProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link TourPrevTrigger}, which steps the tour back. */ export interface TourPrevTriggerProps extends PrimitivePropsWithRef<'button'> { /** * Disables this button; automatically set on the first step. * @default false */ disabled?: boolean | undefined; } /** Props for {@link TourSkipTrigger}, which ends the tour early. */ export interface TourSkipTriggerProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link TourCloseTrigger}, the dismiss affordance. */ export interface TourCloseTriggerProps extends PrimitivePropsWithRef<'button'> { }