import { type UseFloatingReturn } from "@floating-ui/react"; import { type CloseReason, type DismissOptions, type UseOverlayDismissibleProps } from "@trackunit/react-components"; import type { DrawerPosition } from "../../types"; /** * Semantic variant controlling the drawer's dismiss behavior, backdrop, and dialog ARIA. * - `"default"` — non-overlay, non-focus-trapping panel; the surrounding page stays * interactive. Panel gets `role="complementary"` so it participates in the page's * landmark tree. ESC still calls `onClose` when provided. * - `"modal"` — dimming backdrop, `role="dialog"` + `aria-modal`, focus trap (via * `FloatingFocusManager`) that returns focus to the trigger on close, and * outside-press dismiss. */ export type DrawerVariant = "default" | "modal"; /** * Dismiss options honored by Drawer. Reuses the shared {@link DismissOptions} shape * for API parity with `useSheet` / `useModal`; the `gesture` option has no effect * on Drawer (which has no swipe gesture) and is ignored. */ export type DrawerDismissOptions = Pick; /** * Floating UI wiring produced by `useDrawer` and consumed by `Drawer` for its * focus manager and dismiss interaction bindings. Mirrors `useSheet`'s * `floatingUi` shape. */ export type DrawerFloatingUiProps = { readonly context: UseFloatingReturn["context"]; readonly refs: UseFloatingReturn["refs"]; readonly getFloatingProps: (userProps?: Record) => Record; }; /** Props for the {@link useDrawer} hook. */ export type UseDrawerProps = UseOverlayDismissibleProps & { /** * Semantic variant controlling backdrop, focus trap, and dismiss behavior. * * - `"default"` — no backdrop, no focus trap, `role="complementary"`. The * surrounding page stays interactive. ESC still calls `onClose` when * provided; outside-press does not close the drawer. * - `"modal"` — dimming backdrop, `role="dialog"` + `aria-modal`, focus trap * that returns focus to the trigger on close, and outside-press dismiss. * * @default "modal" */ readonly variant?: DrawerVariant; /** * Opt out of the focus trap when `variant="modal"`. Use this only when a * parent component already manages focus for the drawer's subtree. Ignored * when `variant="default"` — the default variant never traps focus. * * @default true */ readonly trapFocus?: boolean; /** * The position of the drawer. * * @default "left" */ readonly position?: DrawerPosition; }; /** Return value of the {@link useDrawer} hook. */ export type UseDrawerReturnValue = { /** * Current open state. Spread onto `Drawer` — the component reads this to drive * its enter / exit animation and to decide whether to render the overlay. */ readonly isOpen: boolean; /** * Open the drawer imperatively. Wire to a trigger, e.g. `onClick={drawer.open}`. * Fires `onOpen` and `onOpenChange(true)` and flips `isOpen` to `true`. Bypasses * the `onBeforeClose` guard (which only runs on close attempts). */ readonly open: () => void; /** * Close the drawer imperatively. Equivalent to `requestClose(undefined, "programmatic")` * — runs the `onBeforeClose` guard (when provided) and only fires `onClose` / * flips state if the guard resolves to `true`. */ readonly close: () => void; /** * Toggle open state. Convenience wrapper — calls `open()` when closed and * `close()` when open. */ readonly toggle: () => void; /** * Close the drawer with a specific reason. Runs `onBeforeClose` first * (when provided) and only fires `onClose` / flips state if the guard * resolves to `true`. */ readonly requestClose: (event: Event | undefined, reason: CloseReason) => void; /** Resolved variant, spread onto `Drawer`. */ readonly variant: DrawerVariant; /** Resolved focus-trap opt-in, spread onto `Drawer`. */ readonly trapFocus: boolean; /** Resolved position, spread onto `Drawer`. */ readonly position: DrawerPosition; /** * Floating UI wiring for the drawer's focus manager and dismiss bindings. * `Drawer` merges `refs.setFloating` onto the panel and uses `context` for * its `FloatingFocusManager`. */ readonly floatingUi: DrawerFloatingUiProps; /** * Whether the drawer is currently in its close→fully-exited cycle. `false` * initially and whenever the drawer has never been opened. * * Derived from the resolved `isOpen` value's own transitions (the same value * used everywhere else in the hook — `controlledIsOpen ?? internalIsOpen`), * not merely from the imperative `open()` / `close()` call sites. Flips to * `true` the moment `isOpen` transitions `true -> false` — whether via * `close()` / `requestClose()` committing (after any `onBeforeClose` guard * has resolved `true`) or a controlled consumer flipping its `isOpen` prop * directly — and back to `false` the moment `isOpen` transitions * `false -> true` again (an interrupted exit releases any hold immediately, * since the interrupted exit's `transitionend` resolves as an enter-settle * and never signals exit-complete), or when the panel has genuinely finished * animating off-screen (wired internally via `onExitComplete`, below). * * Useful for content that wants to survive the exit animation — e.g. via the * shared `useHold(value, isExiting)` primitive — by holding its last non-null * value while `isExiting` is `true`. */ readonly isExiting: boolean; /** * Internal signal spread onto `Drawer`'s `onExitComplete` prop. `Drawer` calls * this exactly once per genuine close→fully-exited cycle, which flips * `isExiting` back to `false`. Not intended for direct use by consumers. */ readonly onExitComplete: () => void; }; /** * Hook for managing Drawer open/close state, dismiss handling, and floating UI wiring. * * Aligns with `useSheet` and `useModal`: consumers use `useDrawer()` to own the * drawer's state and callbacks, then spread the return value onto `Drawer`. * * Supports controlled (`isOpen`) and uncontrolled (`defaultOpen`) modes, stable * `open` / `close` / `toggle` identities (latest-ref pattern for callbacks), and * an `onBeforeClose` guard that can be sync or async — return `false` (or a * `Promise`) to keep the drawer open in response to a close attempt. * * Owns ESC and outside-press dismiss via Floating UI's `useDismiss`. Outside-press * is only active when `variant === "modal"` (the only variant with a backdrop). * The `gesture` field of `DismissOptions` is accepted for API parity with Sheet * but has no effect — Drawer has no swipe gesture. * * @example Controlled * ```tsx * const drawer = useDrawer({ isOpen, onClose: () => setOpen(false), position: "right" }); * return ...; * ``` * @example Uncontrolled with a beforeClose guard * ```tsx * const drawer = useDrawer({ * variant: "modal", * onBeforeClose: async () => (await confirmDiscard()) === "discard", * }); * return ( * <> * * * * * * ); * ``` */ export declare const useDrawer: (props?: UseDrawerProps) => UseDrawerReturnValue;