import type * as React from 'react'; import type { DrawerPlacement } from '../drawer/types.js'; import type { DialogsStore } from '../utils/dialogs.js'; import type { AutoFocusEvent } from '../utils/dismissal.js'; import type { PassThroughProps } from '../utils/passThrough.js'; import type { PortalProps } from '../utils/portal.js'; import type { PrimitivePropsWithRef } from '../utils/primitive.js'; import type { FocusOutsideEvent, InteractOutsideEvent } from '../utils/useDismiss.js'; /** * Which dismissal channels may close a {@link Dialog} — `'any'` both, `'escape'` Escape only, `'outside'` outside interaction only, `'none'` neither; a policy over dismissal, not over the events, which fire regardless. */ export type DialogDismiss = 'any' | 'escape' | 'outside' | 'none'; /** * The panel's role — `'dialog'` for the ordinary panel, `'alertdialog'` for the interruptive confirmation that is always modal, never closed by an outside interaction, and focused on its Cancel button first. */ export type DialogRole = 'dialog' | 'alertdialog'; /** @internal Which family a part belongs to, deciding its class stem and `data-part` namespace. Not part of the public API. */ export type DialogFamily = 'dialog' | 'drawer'; /** @internal Shared state distributed from {@link Dialog} to its parts via context. Not part of the public API. */ export interface DialogContextValue { /** Which family provided this state, so one part implementation serves both. */ family: DialogFamily; triggerRef: React.RefObject; contentRef: React.RefObject; contentId: string; titleId: string; descriptionId: string; /** Whether a {@link DialogTitle} is mounted, so Content can withhold a dangling `aria-labelledby`. */ hasTitle: boolean; setHasTitle: (present: boolean) => void; /** Whether a {@link DialogDescription} is mounted, gating `aria-describedby` the same way. */ hasDescription: boolean; setHasDescription: (present: boolean) => void; open: boolean; onOpenChange: (open: boolean) => void; modal: boolean; /** * Static in-flow preview without native modal or focus effects. * @default false */ inline?: boolean | undefined; /** The resolved dismissal policy, read by `DialogContent` to gate each channel and to emit `data-dismiss`. */ dismiss: DialogDismiss; /** The resolved panel role, read by `DialogContent` for its `role` attribute. */ role: DialogRole; /** The severity rung `DialogContent` and `DialogAction` write as `data-status`. */ status: string | undefined; /** The drawer family's viewport edge; the dialog family renders no placement of its own. */ placement: DrawerPlacement; } /** * Props for the {@link Dialog} root. Standard attributes ({@link * PassThroughProps}) land on the derived `DialogContent` panel. */ export interface DialogProps extends PassThroughProps { /** * Static in-flow preview. No portal, modal behavior, automatic focus or outside dismissal. * @default false */ inline?: boolean | undefined; /** * The trigger and portal, typically — the dialog's own DOM is rendered by {@link DialogPortal} elsewhere. * @default undefined — renders nothing if omitted. */ children?: React.ReactNode | undefined; /** * Whether the dialog is open, for controlled usage. Pair with `onOpenChange`. * @default undefined — uncontrolled, tracks its own open state internally. */ open?: boolean | undefined; /** * Whether the dialog is initially open, for uncontrolled usage. * @default false */ defaultOpen?: boolean | undefined; /** * Fires with the new open state whenever the dialog opens or closes, controlled or not. * @default undefined — no-op if omitted. */ onOpenChange?: ((open: boolean) => void) | undefined; /** * Fires with the new open state once the change has finished on screen, * after `DialogContent`'s exit/enter animation (or immediately with none). * @default undefined — no-op if omitted. */ onOpenChangeComplete?: ((open: boolean) => void) | undefined; /** * Whether the dialog traps focus and blocks interaction with the rest of the page; `false` leaves the page interactive. * @default true */ modal?: boolean | undefined; /** * Which dismissal channels close the dialog — `'any'`, `'escape'`, `'outside'`, or `'none'` — reflected as `data-dismiss` on `DialogContent`. * @default 'any' */ dismiss?: DialogDismiss | undefined; /** * The derived panel's title, rendered as a `DialogTitle`; only read when `children` is plain body content. * @default undefined — no derived title. */ title?: string | undefined; /** * The derived panel's description, rendered as a `DialogDescription`; only read when `children` is plain body content. * @default undefined — no derived description. */ description?: string | undefined; /** * Content rendered after the body in the derived panel — typically the action buttons; only read when `children` is plain body content. * @default undefined — no footer. */ footer?: React.ReactNode | undefined; /** * Whether the derived panel can be dismissed at all — `false` forces `dismiss="none"` and withholds the derived close button. * @default true */ dismissible?: boolean | undefined; /** * Panel width rung the skin reads, fed to the derived `DialogContent`. * @default undefined — the skin's own default width. */ size?: 'sm' | 'md' | 'lg' | (string & {}) | undefined; /** * The panel's role. `'alertdialog'` forces `modal`, resolves the outside dismissal channels away, focuses Cancel first, withholds the close button, and always derives an Action and a Cancel button. * @default 'dialog' */ role?: DialogRole | undefined; /** * Severity rung the skin reads, written as `data-status` on the panel and on the derived action button; `'danger'` is the destructive confirmation. * @default undefined — the skin's own neutral appearance. */ status?: 'info' | 'success' | 'warning' | 'danger' | (string & {}) | undefined; /** * Label of the derived action button, which renders only when this is given — or always under `role="alertdialog"`, where it defaults to `'Confirm'`. * @default undefined — no derived action button, unless the role is `'alertdialog'`. */ confirmLabel?: string | undefined; /** * Label of the derived cancel button, which renders only when this is given — or always under `role="alertdialog"`, where it defaults to `'Cancel'`. * @default undefined — no derived cancel button, unless the role is `'alertdialog'`. */ cancelLabel?: string | undefined; /** * Fires when the derived action button is clicked, before the close. Call `event.preventDefault()` to hold the panel open while the confirmed request is in flight. * @default undefined — no-op if omitted. */ onConfirm?: ((event: React.MouseEvent) => void) | undefined; /** * Fires when the derived cancel button is clicked, before the close. Call `event.preventDefault()` to keep the panel open. * @default undefined — no-op if omitted. */ onCancel?: ((event: React.MouseEvent) => void) | 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 DialogTrigger}, the button that opens the dialog. */ export interface DialogTriggerProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link DialogPortal}, which teleports its children to `document.body`. */ export interface DialogPortalProps extends PortalProps { } /** Props for {@link DialogOverlay}, the backdrop rendered behind `DialogContent`. */ export interface DialogOverlayProps extends PrimitivePropsWithRef<'div'> { /** * Keep this overlay mounted even while the dialog is closed. Useful for * animation libraries that need the outgoing overlay present in the DOM * during an exit transition. * @default false */ forceMount?: boolean | undefined; } /** Props for {@link DialogContent}, the dialog's panel — a native ``, so it takes no `asChild`. */ export interface DialogContentProps extends React.ComponentPropsWithRef<'dialog'> { /** * Panel width rung the skin reads, mirrored to `data-size` on the panel. * @default undefined — the skin's own default width. */ size?: 'sm' | 'md' | 'lg' | (string & {}) | undefined; /** * CSS selector for the element to focus when the dialog opens, resolved inside this panel; falls back to the first tabbable element with a console warning on a miss. * @default undefined — focus moves to the first tabbable element. */ initialFocus?: string | undefined; /** * CSS selector for the element to focus when the dialog closes, resolved at document level since the panel is unmounting; falls back to the previously focused element with a console warning on a miss. * @default undefined — focus returns to the element focused before the dialog opened. */ finalFocus?: string | undefined; /** * Fires with the native `cancel` event when Escape is pressed while the dialog is open. Call `event.preventDefault()` to stop the dialog from closing. * @default undefined — no-op if omitted. */ onEscapeKeyDown?: ((event: Event) => void) | undefined; /** * Fires on pointer-down outside the content. Call `event.preventDefault()` to stop the dialog from closing (only applies when `modal` is true). * @default undefined — no-op if omitted. */ onPointerDownOutside?: ((event: PointerEvent) => void) | undefined; /** * Fires when focus moves to an element outside the content. Call `event.preventDefault()` to stop the dialog from closing (only applies when `modal` is true). Receives a cancelable stand-in carrying the original `focusin` as its `detail`, since `focusin` itself cannot be canceled. * @default undefined — no-op if omitted. */ onFocusOutside?: ((event: FocusOutsideEvent) => void) | undefined; /** * Fires for any outside interaction (pointer-down or focus) just before the specific handler, with the same event — one `event.preventDefault()` here vetoes dismissal regardless of trigger type. * @default undefined — no-op if omitted. */ onInteractOutside?: ((event: InteractOutsideEvent) => void) | undefined; /** * Fires before focus moves into the content on open, modal dialogs only; call `event.preventDefault()` to place focus yourself. * @default undefined — focus moves to the first tabbable element. */ onOpenAutoFocus?: ((event: AutoFocusEvent) => void) | undefined; /** * Fires before focus returns to the previously focused element on close, modal dialogs only; call `event.preventDefault()` to suppress the return. * @default undefined — focus returns to the element focused before the dialog opened. */ onCloseAutoFocus?: ((event: AutoFocusEvent) => void) | undefined; } /** Props for {@link DialogTitle}, the dialog's accessible name. */ export interface DialogTitleProps extends PrimitivePropsWithRef<'h2'> { } /** Props for {@link DialogDescription}, the dialog's accessible description. */ export interface DialogDescriptionProps extends PrimitivePropsWithRef<'p'> { } /** Props for {@link DialogClose}, a button that closes the dialog. */ export interface DialogCloseProps extends PrimitivePropsWithRef<'button'> { } /** Props for {@link DialogAction}, the button that carries out the confirmed action. */ export interface DialogActionProps extends PrimitivePropsWithRef<'button'> { /** * Sets the native disabled attribute so the button neither confirms nor closes the dialog. * @default false */ disabled?: boolean | undefined; } /** Props for {@link DialogCancel}, the button that abandons the action and takes initial focus in an alert dialog. */ export interface DialogCancelProps extends PrimitivePropsWithRef<'button'> { /** * Sets the native disabled attribute so the button neither cancels nor closes the dialog. * @default false */ disabled?: boolean | undefined; } /** Props for the `Dialogs` host that renders the imperative alert / confirm / prompt queue. */ export interface DialogsProps { /** The queue to render, from `createDialogs`. */ dialogs: DialogsStore; }