import React from "react"; import type { Attributes, ClassName } from "@reshaped/headless"; import type { TrapMode, Coordinates } from "@reshaped/headless/internal"; /** * Utility */ type XSide = "start" | "end"; type YSide = "top" | "bottom"; export type Side = XSide | YSide; export type CloseReason = "escape-key" | "outside-click" /** * Keeping the item selection type here since DropdownMenu items use Flyout context * and not a separate DropdownMenu context */ | "item-selection" | "close-button"; export type Position = `${YSide}` | `${YSide}-${XSide}` | `${XSide}` | `${XSide}-${YSide}`; export type Width = "trigger" | string; export type Options = Pick & { position: Position; fallbackPositions?: Position[]; container?: HTMLElement | null; rtl: boolean; lastUsedPosition: Position; onPositionChoose: (position: Position) => void; }; export type State = { position?: Position; status: "idle" | "rendered" | "visible" | "hidden"; }; export type FlyoutData = { position: Position; }; export type UseFlyoutData = Pick & { updatePosition: (args?: { sync?: boolean; fallback?: boolean; }) => void; render: () => void; hide: () => void; remove: () => void; show: () => void; }; /** * Component */ export type Instance = { /** Open the flyout content */ open: () => void; /** Close the flyout content */ close: () => void; /** Sync the flyout content position with the trigger element position */ updatePosition: () => void; } | null; type WithUncontrolled = { /** Control the content visibility, enables controlled mode */ active?: never; /** Control the content default visibility, enables uncontrolled mode */ defaultActive?: boolean; }; type WithControlled = { /** Control the content visibility, enables controlled mode */ active: boolean; /** Control the content default visibility, enables uncontrolled mode */ defaultActive?: never; }; export type TriggerAttributes = { ref: React.RefObject | any; onBlur?: (e: React.FocusEvent) => void; onFocus?: () => void; onMouseDown?: () => void; onMouseEnter?: (e: React.MouseEvent) => void; onMouseLeave?: (e: React.MouseEvent) => void; onTouchStart?: () => void; onClick?: () => void; "aria-describedby"?: string; "aria-haspopup"?: "dialog" | "menu" | "listbox"; "aria-autocomplete"?: "list"; "aria-expanded"?: boolean; "aria-controls"?: string; "data-rs-flyout-active"?: boolean; }; type BaseProps = { /** Unique id for the flyout content and trigger */ id?: string; /** Event used for displaying the content */ triggerType?: "hover" | "click" | "focus"; /** Removes the content display delay if another flyout is already active */ groupTimeouts?: boolean; /** Content position relative to the trigger element */ position?: Position; /** * @deprecated Use fallbackPosition={false} instead, will be removed in v4 */ forcePosition?: boolean; /** Fallback positions for the content when it doesn't fit into the viewport or container */ fallbackPositions?: Position[] | false; /** Adjust the content size and shift its position to fit into the container when none of the fallback positions work */ fallbackAdjustLayout?: boolean; /** @deprecated Minimum width for the content when fallbackAdjustLayout is true */ fallbackMinWidth?: string; /** Minimum height for the content when fallbackAdjustLayout is true */ fallbackMinHeight?: string; /** Change component trap focus keyboard behavior and shortcuts */ trapFocusMode?: TrapMode | false; /** Disable the flyout content interactivity */ disabled?: boolean; /** Disable the flyout content hide animation */ disableHideAnimation?: boolean; /** Ignore the content hover events and hide it if the triggerType is hover */ disableContentHover?: boolean; /** Disable the flyout content close on outside click */ disableCloseOnOutsideClick?: boolean; /** Automatically focus the first focusable element in the content, when false the content container will be focused instead * @default true */ autoFocus?: boolean; /** Origin coordinates for the content when there is no trigger element */ originCoordinates?: Coordinates; /** Node for inserting children */ children?: React.ReactNode; /** Callback when the content is opened */ onOpen?: () => void; /** Callback when the content is closed */ onClose?: (args: { reason?: CloseReason; }) => void; /** Content width, literal css value or unit token multiplier */ width?: Width; /** Gap between the content and the trigger element */ contentGap?: number; /** Shift the content on the secondary axis, relative to its original position */ contentShift?: number; /** Maximum height for the content */ contentMaxHeight?: string; /** Maximum width for the content */ contentMaxWidth?: string; /** Z-index for the content element */ contentZIndex?: number; /** Additional classname for the content element */ contentClassName?: string; /** Additional attributes for the content element */ contentAttributes?: Attributes<"div">; /** Ref accessor for the flyout methods */ instanceRef?: React.Ref; /** Container to render the content in using a portal, position is calculated based on the container bounds * @default document.body */ containerRef?: React.RefObject; /** Element used to calculate the position of the content against */ positionRef?: React.RefObject; /** Element to focus when the content is opened */ initialFocusRef?: React.RefObject; }; export type DefaultProps = Required<{ position: BaseProps["position"]; trigger: BaseProps["triggerType"]; }>; export type UncontrolledProps = BaseProps & WithUncontrolled; export type ControlledProps = BaseProps & WithControlled; export type Props = ControlledProps | UncontrolledProps; export type TriggerProps = { /** Node for inserting children, provides attributes for the trigger element */ children: (attributes: TriggerAttributes) => React.ReactNode; }; export type ContentProps = { /** Node for inserting children */ children?: React.ReactNode; /** Additional classname for the content element */ className?: ClassName; /** Additional attributes for the content element */ attributes?: Attributes<"div">; }; export type ContextProps = { id: string; flyout: UseFlyoutData; width?: Width; triggerElRef?: React.RefObject; flyoutElRef: React.RefObject; handleClose: (options: { closeParents?: boolean; reason?: CloseReason; }) => void; handleOpen: () => void; handleTriggerMouseEnter: (e: React.MouseEvent) => void; handleContentMouseEnter: (e: React.MouseEvent) => void; handleMouseLeave: (e: React.MouseEvent) => void; handleTransitionEnd: (e: React.TransitionEvent) => void; handleClick: () => void; handleBlur: (e: React.FocusEvent) => void; handleFocus: () => void; handleTouchStart: () => void; handleContentMouseDown: () => void; handleContentMouseUp: () => void; isSubmenu: boolean; } & Pick; export type TriggerContextProps = { elRef?: ContextProps["triggerElRef"]; }; export type ContentContextProps = { elRef?: ContextProps["flyoutElRef"]; }; export {};