/** * ImageViewer — a picture that opens out of the page to fill the screen. * * Pressing a trigger lifts its image from where it sits, grows it to the whole * picture in the middle of the screen, and blurs the page behind it. From there * it can be pinched and double-tapped to zoom, swiped sideways to the next * image under the same root, and dragged away to put it back. * * ```tsx * * {photos.map((photo) => ( * * ))} * * ``` * * ## The image travels; nothing appears * * The viewer never fades a second copy of the picture in over the first. The * thumbnail is hidden and the same image is drawn in a frame that starts at the * thumbnail's exact rectangle and corner radius and ends at the fitted one. The * picture inside that frame is always drawn at the size that *covers* it, so the * crop a thumbnail shows opens out into the whole image as the frame grows — and * closes back into the crop on the way home. That continuity is the point: the * picture on screen is the one that was pressed, not a picture of it. * * The frame animates its bounds rather than a transform, because a transform * cannot change a crop. It is one view with one child, so the layout pass it * costs per frame is small. * * ## Every trigger under one root is a page * * Triggers register themselves with the root as they mount, in render order or * by an explicit `index`. Opening one opens the gallery at its page, and the * thumbnail hidden underneath follows the page — close on the third image and it * flies back into the third thumbnail. * * ## One gesture surface, decided at the start * * Pinching, panning a zoomed image, turning the page and dragging to dismiss all * begin as a one- or two-finger drag, and which one it is gets decided the * moment it starts: two fingers zoom, one finger on a zoomed image pans it, one * finger moving sideways across a gallery turns the page, and anything else * drags the image away. Deciding once keeps a drag from changing its mind * halfway, which is what makes a viewer feel loose. */ import { type ReactNode } from 'react'; import { type ImageSourcePropType, type PressableProps } from 'react-native'; export interface ImageViewerProps { children?: ReactNode; /** Controlled open state. */ open?: boolean; /** Initial open state when uncontrolled. */ defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; /** Controlled page — which trigger's image is showing, in page order. */ index?: number; /** Initial page when uncontrolled. */ defaultIndex?: number; onIndexChange?: (index: number) => void; /** * Blur the page behind the picture. Needs the optional `expo-blur`, and dims * instead without it. Reduce Transparency draws an opaque backdrop. */ blur?: boolean; /** How far a pinch can zoom in, as a multiple of the fitted size. */ maxScale?: number; /** Where a double tap zooms to, as a multiple of the fitted size. */ doubleTapScale?: number; /** Tick as a drag to dismiss begins. Needs the optional `expo-haptics`. */ haptics?: boolean; /** Draw the close button. Tapping outside the picture and dragging it away still close it. */ showClose?: boolean; /** Read out for the close button. */ closeLabel?: string; /** * Space kept between the open picture and the edges of the screen, in points. * Measured from the safe area at the top and bottom, and the larger of the * two is used for both, so the picture stays centred. `0` fills the screen. */ inset?: number; /** * Corner radius of the open picture, in points. The corners stay this size on * screen while the picture is zoomed or dragged. `0` squares them. */ cornerRadius?: number; } declare function ImageViewerRoot({ children, open, defaultOpen, onOpenChange, index, defaultIndex, onIndexChange, blur, maxScale, doubleTapScale, haptics, showClose, closeLabel, inset, cornerRadius, }: ImageViewerProps): import("react").JSX.Element; export interface ImageViewerTriggerProps extends Omit { /** * Classes on the pressable. Without `children` the image fills it, so give it * a size — `h-48 w-full`, `aspect-square flex-1`. */ className?: string; /** The picture. Shown in the page and, until `fullSource` loads, in the viewer. */ source: ImageSourcePropType; /** * A larger copy to show once the viewer is open. It loads when the viewer * opens and replaces `source` when it arrives, so a feed can carry thumbnails. */ fullSource?: ImageSourcePropType; /** Described for a screen reader, on the trigger and in the viewer. */ alt?: string; /** Shown under the picture while it is open. A string is set as text. */ caption?: ReactNode; /** Page order within the root. Render order when left out. */ index?: number; /** * The corner radius the picture has in the page, in points, so the flight * starts from the same shape. Match it to the `rounded-*` class you gave the * trigger — a class cannot be read back. */ radius?: number; /** * The image's own width and height, when known. Saves looking it up, and is * the only way the viewer knows the proportions before a remote image loads. */ width?: number; height?: number; /** Nothing opens, and the press is not reported. */ disabled?: boolean; /** * Draw the picture yourself — a `Post.Media`, a card. It should show `source` * cropped to fill, the way the viewer's own flight starts. Left out, the * trigger draws `source` to fill its box. */ children?: ReactNode; } /** * The thing pressed to open the viewer. * * It registers its picture with the root, so the gallery is simply every * trigger under that root. While its picture is out on the screen it is drawn * transparent — not removed, so the page does not reflow under the blur and the * flight home has somewhere to land. */ declare function ImageViewerTrigger({ className, source, fullSource, alt, caption, index, radius, width, height, disabled, children, onPress, onLayout, ...props }: ImageViewerTriggerProps): import("react").JSX.Element; export declare const ImageViewer: typeof ImageViewerRoot & { Trigger: typeof ImageViewerTrigger; }; export {}; //# sourceMappingURL=index.d.ts.map