/**
* 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