@avament-pub/image-previewer
v0.2.1
Published
Full-screen image lightbox + thumbnail for Avament React apps
Readme
@avament-pub/image-previewer
Full-screen image lightbox (zoom, optional Edit / Delete / gallery nav) plus a small thumbnail button. Portable across Avament apps — no API client and no i18n library inside.
| | |
|---|---|
| Package | @avament-pub/image-previewer |
| React | ≥ 18 (peer) |
| Dependency | lucide-react |
| License | MIT |
Table of contents
- Install
- What you get
- Important: host resolves media URLs
- Minimal usage
- Gallery navigation
- Thumbnail
- Props API
- Exports
- Localization
- Theming / CSS
- Troubleshooting
1. Install
npm install @avament-pub/image-previeweryarn add @avament-pub/image-previewer
pnpm add @avament-pub/image-previewerRequirements
- React 18+ / React DOM 18+
- Client component in Next.js App Router (
"use client")
Optional:
transpilePackages: ["@avament-pub/image-previewer"];2. What you get
ImagePreviewer
- Full-viewport overlay portaled to
document.body - Zoom in / out / reset (buttons + mouse wheel)
- Escape closes; ← / → when
onPrevious/onNextare set - Optional Edit / Delete actions (buttons only appear if handlers are passed)
- Body scroll locked while open
ImagePreviewThumbnail
- Compact clickable thumb that calls
onOpen - Uses
<img>(no Next.jsImagecoupling)
3. Important: host resolves media URLs
Pass a browser-displayable URL, or pass resolveSrc so the package resolves for you:
<ImagePreviewer
open={open}
imageUrl={apiPathOrUrl}
resolveSrc={async (url) => {
// Host: authenticated fetch → blob URL (package will revoke it)
const blob = await apiClient.getBlob(url);
return URL.createObjectURL(blob);
}}
onClose={onClose}
/>Without resolveSrc, imageUrl must already be public https, blob:, or data:.
4. Minimal usage
"use client";
import { useState } from "react";
import { ImagePreviewer } from "@avament-pub/image-previewer";
export function PhotoPreview({ url, alt }: { url: string; alt: string }) {
const [open, setOpen] = useState(false);
return (
<>
<button type="button" onClick={() => setOpen(true)}>
Preview
</button>
<ImagePreviewer
open={open}
imageUrl={url}
imageAlt={alt}
onClose={() => setOpen(false)}
/>
</>
);
}With edit / delete:
<ImagePreviewer
open={open}
imageUrl={url}
imageAlt={alt}
onClose={() => setOpen(false)}
onEdit={() => router.push(`/edit?src=${encodeURIComponent(url)}`)}
onDelete={async () => {
await api.deletePhoto(id);
setOpen(false);
}}
/>5. Gallery navigation
"use client";
import { useState } from "react";
import { ImagePreviewer } from "@avament-pub/image-previewer";
export function Gallery({ items }: { items: { url: string; alt: string }[] }) {
const [index, setIndex] = useState<number | null>(null);
const current = index !== null ? items[index] : null;
return (
<>
{items.map((item, i) => (
<button key={item.url} type="button" onClick={() => setIndex(i)}>
Open {i + 1}
</button>
))}
<ImagePreviewer
open={index !== null}
imageUrl={current?.url}
imageAlt={current?.alt}
onClose={() => setIndex(null)}
onPrevious={
index !== null && index > 0 ? () => setIndex(index - 1) : undefined
}
onNext={
index !== null && index < items.length - 1
? () => setIndex(index + 1)
: undefined
}
/>
</>
);
}Both onPrevious and onNext must be functions for chevrons + arrow keys to appear. Zoom resets when imageUrl changes.
6. Thumbnail
"use client";
import { ImagePreviewThumbnail, ImagePreviewer } from "@avament-pub/image-previewer";
import { useState } from "react";
export function AttachmentThumb({
image,
displaySrc,
}: {
image: { id: string; fileName: string; url: string; thumbnailUrl?: string | null };
displaySrc?: string | null; // host-resolved blob/https
}) {
const [open, setOpen] = useState(false);
return (
<>
<ImagePreviewThumbnail
image={image}
src={displaySrc}
onOpen={() => setOpen(true)}
/>
<ImagePreviewer
open={open}
imageUrl={displaySrc ?? image.url}
imageAlt={image.fileName}
onClose={() => setOpen(false)}
/>
</>
);
}7. Props API
ImagePreviewer
| Prop | Type | Description |
|---|---|---|
| open | boolean | When false, renders nothing |
| imageUrl | string \| null | Display URL (already resolved) |
| imageAlt | string | <img alt> / dialog label fallback |
| onClose | () => void | Required |
| onEdit | () => void | Shows Edit when set |
| onDelete | () => void \| Promise<void> | Shows Delete when set and imageUrl present |
| onPrevious / onNext | () => void | Gallery chevrons + keys |
| loading | boolean | Shows loading label when no URL yet |
| labels | ImagePreviewerLabels | UI copy |
| zIndexClassName | string | Default z-[320] |
| className | string | Root dialog classes |
ImagePreviewThumbnail
| Prop | Type | Description |
|---|---|---|
| image | { id, fileName, url, thumbnailUrl? } | Metadata |
| onOpen | () => void | Click handler |
| src | string \| null | Preferred display URL |
| loading | boolean | aria-busy |
| className | string | |
ImagePreviewerLabels
| Key | Default |
|---|---|
| previewAriaLabel | Image preview |
| loading | Loading… |
| unavailable | Image unavailable |
| zoomOut / zoomIn / resetZoom | Zoom out / Zoom in / Reset zoom |
| edit / delete / close | Edit / Delete / Close |
| previous / next | Previous image / Next image |
8. Exports
import {
ImagePreviewer,
ImagePreviewThumbnail,
useImagePreviewer,
clampImagePreviewZoom,
DEFAULT_IMAGE_PREVIEWER_LABELS,
mergeImagePreviewerLabels,
} from "@avament-pub/image-previewer";
import type {
ImagePreviewerProps,
ImagePreviewThumbnailProps,
ImagePreviewerLabels,
ImagePreviewSession,
} from "@avament-pub/image-previewer";useImagePreviewer is the zoom / keyboard / scroll-lock hook used internally; export it if you build a custom chrome around the same behavior.
9. Localization
<ImagePreviewer
open={open}
imageUrl={url}
onClose={onClose}
labels={{
close: "Schließen",
zoomIn: "Vergrößern",
zoomOut: "Verkleinern",
edit: "Bearbeiten",
delete: "Löschen",
loading: "Laden…",
unavailable: "Bild nicht verfügbar",
}}
/>Or merge defaults:
import { mergeImagePreviewerLabels } from "@avament-pub/image-previewer";
const labels = mergeImagePreviewerLabels({ close: "Schließen" });10. Theming / CSS
Self-contained inline styles — no host Tailwind required. Use zIndex for stacking.
11. Troubleshooting
| Issue | Fix |
|---|---|
| Broken private images | Pass resolveSrc that returns a blob/https URL |
| Blank overlay | Ensure open={true} and portal can mount |
| No chevrons | Pass both onPrevious and onNext |
| Edit/Delete missing | Pass onEdit / onDelete |
| Behind another modal | Increase zIndex |
| Next.js SSR | "use client" on the consumer |
Versioning & publish
Bump package.json version before republishing README updates to npm.
