Files
photos/frontend/src/components/Lightbox.jsx
T
nilsandClaude 16d2a56a78
ci / docker (push) Successful in 13s
Initial release: self-hosted client photo gallery
Rust (axum + sqlx) API and worker sharing a Postgres-backed job queue
(SKIP LOCKED, heartbeat, reaper, typed statuses), S3 storage with derived
keys and a fully private bucket, OIDC photographer login with per-request
allowlist checks, client share links with argon2 passwords and lockout,
cookie-based image authorization with sliding expiry, hand-rolled
spec-compliant streaming ZIP downloads with exact Content-Length,
React + Vite gallery frontend, single Docker image, Helm chart for
external S3 + Postgres, and Gitea CI.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 13:12:42 +02:00

73 lines
1.9 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect } from 'react'
import { imgUrl } from '../api'
export default function Lightbox({ photos, index, onClose, onNav, footer }) {
const photo = photos[index]
useEffect(() => {
const onKey = (e) => {
if (e.key === 'Escape') onClose()
if (e.key === 'ArrowRight' && index < photos.length - 1) onNav(index + 1)
if (e.key === 'ArrowLeft' && index > 0) onNav(index - 1)
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [index, photos.length, onClose, onNav])
useEffect(() => {
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = ''
}
}, [])
if (!photo) return null
return (
<div className="lightbox" onClick={onClose}>
<div className="lb-top" onClick={(e) => e.stopPropagation()}>
<span className="lb-name">{photo.filename}</span>
<span className="lb-count">
{index + 1} / {photos.length}
</span>
<button className="lb-btn" onClick={onClose} title="Close (Esc)">
</button>
</div>
<button
className="lb-nav lb-prev"
disabled={index === 0}
onClick={(e) => {
e.stopPropagation()
onNav(index - 1)
}}
>
</button>
<div className="lb-stage">
<img
className="lb-img"
src={imgUrl(photo, 'preview')}
alt={photo.filename}
onClick={(e) => e.stopPropagation()}
/>
</div>
<button
className="lb-nav lb-next"
disabled={index === photos.length - 1}
onClick={(e) => {
e.stopPropagation()
onNav(index + 1)
}}
>
</button>
{footer && (
<div className="lb-footer" onClick={(e) => e.stopPropagation()}>
{footer(photo)}
</div>
)}
</div>
)
}