/** * Popover — a panel anchored to the thing that opened it. * * A dialog takes the screen and asks to be dealt with; a popover stays next to * its trigger and keeps the context around it visible. That difference is the * whole reason both exist, and it is why this one is positioned rather than * centred. * * ```tsx * * * * * * Export * Choose a format. * * * ``` * * Placement is a preference, not a promise. The trigger is measured in window * coordinates when it is pressed, the panel measures itself on its first * layout, and the two are reconciled against the safe area: a panel that would * run off the bottom flips above the trigger, and one that would run off the * side slides back inside. So `placement="bottom"` means *below, if below * fits* — which is the only behaviour that survives a trigger near an edge. * * The first frame is rendered transparent, because the panel's own size is not * known until it has laid out once. Without that it would appear at the origin * and jump into place. */ import { type ReactElement, type ReactNode } from 'react'; import { type ViewProps } from 'react-native'; import { type TextProps } from '../../primitives/text.js'; export type PopoverPlacement = 'top' | 'bottom' | 'left' | 'right'; export type PopoverAlign = 'start' | 'center' | 'end'; /** * The rectangle the panel is placed against, in window coordinates. * * Normally the trigger's own bounds. A zero-sized rect is meaningful too: it * anchors the panel to a single point, which is what a menu opened by a long * press on arbitrary content needs — there the interesting position is where * the finger landed, not the bounds of whatever it landed on. */ export interface PopoverAnchorRect { x: number; y: number; width: number; height: number; } export type PopoverPresentation = 'popover' | 'bottom-sheet'; export interface PopoverAnchorControls { open: boolean; setOpen: (open: boolean) => void; /** * Place the panel against an explicit rect rather than against a measured * trigger. Pass a zero-sized rect to anchor it to a point. */ anchorTo: (rect: PopoverAnchorRect) => void; } /** * For a trigger that opens the panel on something other than a plain press, or * anchors it to something other than its own bounds. * * `Popover.Trigger` covers the ordinary case — press the thing, measure the * thing, open next to it. A component built on this one may need neither half * of that: a context menu opens on a long press and belongs at the point the * finger landed. Rather than have it own a second copy of the placing, * flipping and edge-clamping this file already does, it borrows them by * setting the anchor itself. * * Only useful inside a `Popover`, which is the same rule every other part here * follows. */ export declare function usePopoverAnchor(component: string): PopoverAnchorControls; export interface PopoverProps { children: ReactNode; /** Controlled open state. */ open?: boolean; onOpenChange?: (open: boolean) => void; /** Initial state when uncontrolled. */ defaultOpen?: boolean; /** * `popover` is the anchored panel. `bottom-sheet` presents the content in a * draggable sheet instead — better on a small screen, or when the content is * a form rather than a menu. Placement, align and the arrow do not apply to a * sheet. */ presentation?: PopoverPresentation; /** * Present the platform's own popover instead of this one. Requires the * optional `@expo/ui`. * * **iOS only.** SwiftUI has a popover that anchors to a view and keeps its * anchored shape on a phone rather than becoming a sheet; Compose's nearest * relative is a dropdown menu, which is a different control with different * rules. Android and web keep the styled panel, as does an iOS device * without `@expo/ui` installed. * * **The platform draws the container, so theme tokens do not reach it.** The * panel's surface, its corner radius, its shadow and its arrow are the * system's; `className` on `Popover.Content` styles what is *inside* it. * `align`, `offset`, `alignOffset`, `scrim` and `blur` have no native * equivalent and are ignored; `placement` becomes the edge the arrow is * asked for. * * **Give the content a `width`.** The platform sizes its popover to what is * hosted in it, and a React Native subtree with no width of its own has * nothing to report — the same rule that governs every hosted view. * `Popover.Content` defaults to a sensible one under `native`, but a panel * whose rows need more room should say so. */ native?: boolean; } declare function PopoverRoot({ children, open, onOpenChange, defaultOpen, presentation, native, }: PopoverProps): import("react").JSX.Element; export interface PopoverTriggerProps { children: ReactElement<{ onPress?: (...args: unknown[]) => void; }>; } /** * Wraps its child and toggles the popover on press. * * The child is wrapped in a view rather than given a ref directly: the ref has * to survive whatever the child is — a Button, a plain Pressable, an icon — * and only a wrapper we own is guaranteed to be measurable. */ declare function PopoverTrigger({ children }: PopoverTriggerProps): import("react").JSX.Element; export interface PopoverContentProps extends ViewProps { className?: string; /** Preferred side of the trigger. Flipped when that side does not fit. */ placement?: PopoverPlacement; /** Where the panel sits along the trigger's other axis. */ align?: PopoverAlign; /** Gap between the trigger and the panel, in pixels. */ offset?: number; /** Nudge along the alignment axis, in pixels. */ alignOffset?: number; /** * `content-fit` sizes to the content, `trigger` matches the trigger's width, * `full` spans the safe area, and a number is that many pixels. */ width?: number | 'trigger' | 'full' | 'content-fit'; /** * Floor for the panel's width, in pixels. Worth setting with * `width="trigger"`, where a narrow trigger would otherwise squeeze the * content into a column. */ minWidth?: number; /** * Ceiling for the panel's height, in pixels. Always clamped to the room * inside the safe area, which is also the default — a panel is never * positioned so that part of it falls off the screen, because the part that * falls off cannot be scrolled back into view. */ maxHeight?: number; /** * Scroll the panel's body when it is taller than `maxHeight`. * * Off by default, because a popover is usually a paragraph or a short form * and a scroller around either one only adds a bounce. Worth turning on for * a list of unknown length, which is the case where the cap actually bites. * * The spacing between children moves to the scroller's content when this is * set; `className` still dresses the panel itself. */ scrollable?: boolean; /** * Drop the panel's own surface — its background, border, radius, padding and * shadow — and keep only its position and its size. For a caller that draws * the surface itself, so that something can be put *behind* the content * rather than layered on top of a background that is already painted. * * The panel is still clipped to a rounded rectangle, because a surface drawn * inside it has to have something to be clipped by. */ unstyled?: boolean; /** * A layer drawn inside the panel, behind its content — and, crucially, * outside its scroller, so that a surface does not scroll away with the rows * on top of it. Pair it with `unstyled` to own the panel's appearance. */ background?: ReactNode; /** Tap outside the panel closes it. Default true. */ dismissible?: boolean; /** * Frost the background behind the panel instead of dimming it. Uses * `expo-blur` when installed and falls back to the dimmed scrim when it is * not, so it is safe to pass either way. * * Someone who has Reduce Transparency switched on gets an opaque * backdrop instead, which is the whole point of the setting. */ blur?: boolean; /** * Dim the screen behind the panel. * * Off by default: a popover is a panel *beside* something, and dimming the * page says the thing behind it has stopped being available — which is a * dialog's claim, not a popover's. Worth turning on when the panel is the * only thing that matters while it is up, which is what a menu opened on the * content itself is. Ignored under `blur`, which draws its own dim. */ scrim?: boolean; /** The dim's classes, when `scrim` is set. */ scrimClassName?: string; children?: ReactNode; } declare function PopoverContent({ className, placement, align, offset, alignOffset, width, minWidth, maxHeight, scrollable, unstyled, background, dismissible, blur, scrim, scrimClassName, children, onLayout: onLayoutProp, style, ...props }: PopoverContentProps): import("react").JSX.Element | null; export interface PopoverArrowProps extends ViewProps { className?: string; } /** * A small square rotated into a diamond, half-buried under the panel so only * the point shows. It needs the panel to have a border for its own two visible * edges to line up with — without one it reads as a floating lozenge. * * It points at the trigger's centre, which the panel resolves and publishes: * when `align` shifts the panel off-centre, or a clamp slides it back on * screen, the arrow stays over the trigger rather than over the panel's middle. */ declare function PopoverArrow({ className, style, ...props }: PopoverArrowProps): import("react").JSX.Element | null; export interface PopoverCloseProps { children: ReactElement<{ onPress?: (...args: unknown[]) => void; }>; } /** Wraps its child and closes the popover on press. */ declare function PopoverClose({ children }: PopoverCloseProps): ReactElement<{ onPress?: (...args: unknown[]) => void; }, string | import("react").JSXElementConstructor>; export declare const Popover: typeof PopoverRoot & { Trigger: typeof PopoverTrigger; Content: typeof PopoverContent; Arrow: typeof PopoverArrow; Title: { ({ className, ...props }: TextProps): import("react").JSX.Element; displayName: string; }; Description: { ({ className, ...props }: TextProps): import("react").JSX.Element; displayName: string; }; Close: typeof PopoverClose; }; export {}; //# sourceMappingURL=index.d.ts.map