Image VieweriOS
Full-screen lightbox with pinch-to-zoom, double-tap zoom, pan, horizontal paging with a counter, and swipe-down to dismiss
$ npx lumioui add image-viewer
import { ImageViewer } from "@/components/ui/image-viewer/image-viewer";
Files installed into your repo
components/ui/image-viewer/image-viewer.tsx
Registry dependencies (installed automatically)
Native packages required
@lumioui/core@latest@lumioui/ui@latestreact-native-reanimated@^4.0.0react-native-gesture-handler@^2.20.0react-native-safe-area-context@^4.0.0expo-haptics@*
Props10
| open | boolean | |
| onOpenChange | (open: boolean) => void | |
| images | ImageSourcePropType[] | Remote or local images, paged left-to-right. |
| index? | number | Controlled index of the visible image. Omit for uncontrolled. |
| defaultIndex? | number | Uncontrolled initial image. Default 0. |
| onIndexChange? | (index: number) => void | |
| showCounter? | boolean | "N / M" counter pill. Default true (shown only with 2+ images). |
| showClose? | boolean | Close control, top right. Default true. |
| tapToToggleChrome? | boolean | Single tap shows/hides the chrome. Default true. |
| className? | string | |
Image Viewergallery.tsx
Live preview offline the relay is unreachable — check back shortly
image-viewer/variants/gallery.tsx open Full-screen lightbox with pinch-to-zoom, double-tap zoom, pan, horizontal paging with a counter, and swipe-down to dismiss
import { ImageViewerGallery } from "@/components/ui/image-viewer/variants/gallery";
export function Example() {
return (
<ImageViewerGallery />
);
}
import * as React from "react";
import { Pressable, View } from "react-native";
import { ImageViewer } from "../image-viewer";
import { Ambient, Glass, Image, Text } from "@lumioui/ui";
// Pinned Unsplash IDs — picsum seeds served arbitrary subjects against a
// "Lisbon trip" caption.
const PHOTOS = [
"photo-1767357583916-583f50641618", // Lisbon tram 28
"photo-1768042298555-d9b893fcf31b", // Lisbon rooftops at dusk
"photo-1569718212165-3a8278d5f624", // ramen lunch
"photo-1585208798174-6cedd86e019a", // rooftops
"photo-1518638150340-f706e86654de", // Oaxaca market
"photo-1493976040374-85c8e12f0c0e", // Kyoto torii stopover
"photo-1507525428034-b723cf961d3e", // coast day
"photo-1477959858617-67f85cf4f1df", // dusk skyline
].map((id) => ({
uri: `https://images.unsplash.com/${id}?w=800&h=600&fit=crop&q=70&auto=format`,
}));
/** ImageViewerGallery — thumbnail grid that opens the paged lightbox. */
export function ImageViewerGallery() {
const [open, setOpen] = React.useState(false);
const [index, setIndex] = React.useState(0);
return (
<View className="relative w-full overflow-hidden rounded-3xl">
<Ambient seed="image-viewer-gallery" />
<View className="w-full gap-3 p-4">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<Text variant="small" className="font-semibold">Lisbon trip — 8 photos</Text>
<View className="w-full flex-row flex-wrap gap-2">
{PHOTOS.map((p, i) => (
<Pressable
key={i}
onPress={() => {
setIndex(i);
setOpen(true);
}}
accessibilityRole="imagebutton"
accessibilityLabel={`Open photo ${i + 1} of ${PHOTOS.length}`}
>
<Image
source={p}
rounded="lg"
className="h-20 w-20"
alt={`Trip photo ${i + 1}`}
/>
</Pressable>
))}
</View>
<ImageViewer
open={open}
onOpenChange={setOpen}
images={PHOTOS}
index={index}
onIndexChange={setIndex}
/>
<Text variant="muted">Shared with Amara · 2m ago</Text>
</Glass>
</View>
</View>
);
}