DKMediaViewer
diklein/dk-media-viewerFreeComponent
A minimalist, performant, and elegant photo and video viewer for React. Masonry grid, fly-in lightbox, a crossfading carousel, captions and camera EXIF data, light and dark modes, reduced-motion support, keyboard navigation, and one easter egg.
Install it
npx shadcn@latest add https://diklein.com/r/dk-media-viewer.jsonSource
1'use client'23/* DKMediaViewer — the masonry grid + the lightbox it opens.4 *5 * <DKMediaViewer items={items} />6 *7 * Items are plain data (see types.ts) — hand-written, CMS-mapped, or generated with8 * `npx @diklein/dkmediaviewer scan ./public/photos`. Photos render as lazy <img>s (or through your own9 * renderImage slot, e.g. next/image); videos autoplay muted in the grid and hand off10 * frame-accurately to the lightbox player when clicked. */1112import { useState, useEffect, useRef } from 'react'13import type { DKMediaItem } from './types'14import { useDKLightbox } from './dk-lightbox'1516export interface DKMediaViewerProps {17 items: DKMediaItem[]18 /** Extra classes on the outer grid wrapper. */19 className?: string20 /** A sharper large variant for the lightbox to fade in over the base image (and to prefetch21 * on hover and around the open item) — e.g. a CDN resize URL. Default: none; the lightbox22 * shows item.src, which for local files is already the full image. */23 getHiResSrc?: (item: DKMediaItem) => string24 /** Render the grid image yourself (e.g. with next/image). Apply ctx.className to the visible25 * image element and honor ctx.sizes/ctx.priority; the lightbox still works untouched — it26 * reads the rendered <img>'s decoded file at click time. */27 renderImage?: (28 item: DKMediaItem,29 ctx: { index: number; priority: boolean; sizes: string; className: string },30 ) => React.ReactNode31 /** The 1px photo edge outline in the lightbox. Default true (photographs). */32 showOutline?: boolean33 /** Reserve the lightbox caption/exif rail. Default: on when any item carries one. */34 hasCaptions?: boolean35 /** Open from `?photo=<index>` on mount, for shareable deep links. Default off. */36 deepLink?: boolean37}3839const GRID_SIZES = '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw'40const IMG_CLASS = 'dk-img-outline w-full h-auto transition-opacity duration-200 group-hover:opacity-85'4142export function DKMediaViewer({43 items,44 className,45 getHiResSrc,46 renderImage,47 showOutline = true,48 hasCaptions,49 deepLink = false,50}: DKMediaViewerProps) {51 const [numCols, setNumCols] = useState(3)52 const prefetched = useRef<Set<string>>(new Set())53 // Thumbnail registry: close hands focus back to the item the viewer ended on, and deep links54 // need a zoom origin — both resolved through here.55 const thumbEls = useRef<Map<number, HTMLButtonElement>>(new Map())5657 const { open, lightbox } = useDKLightbox(items, {58// … truncated
What it pulls in
npm packages
Files it writes
More from diklein
All 3 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| DKBezelerdk-bezeler | diklein | Components | Free | no deps · 2 files | |
| DKProductCarddk-product-card | diklein | Components | Free | 1 dep · 2 files |
