Add image lightbox for item thumbnails and kit parts

This commit is contained in:
2026-04-20 01:34:25 +02:00
parent 7d648c4313
commit 78a200c6ee
+35 -4
View File
@@ -25,6 +25,14 @@ export function ItemCard({ item, onEdit, onRemove }: Props) {
const [hoverImg, setHoverImg] = useState<string | null>(null) const [hoverImg, setHoverImg] = useState<string | null>(null)
const [mousePos, setMousePos] = useState({ x: 0, y: 0 }) const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
const [lightboxImg, setLightboxImg] = useState<string | null>(null)
useEffect(() => {
if (!lightboxImg) return
function onKey(e: KeyboardEvent) { if (e.key === 'Escape') setLightboxImg(null) }
document.addEventListener('keydown', onKey)
return () => document.removeEventListener('keydown', onKey)
}, [lightboxImg])
// Preload part images into memCache so resolveImageSync works on hover // Preload part images into memCache so resolveImageSync works on hover
useEffect(() => { useEffect(() => {
@@ -52,7 +60,12 @@ export function ItemCard({ item, onEdit, onRemove }: Props) {
> >
{/* Thumbnail */} {/* Thumbnail */}
{thumbnail ? ( {thumbnail ? (
<img src={thumbnail} alt={item.name} className="h-16 w-16 shrink-0 rounded object-cover" /> <img
src={thumbnail}
alt={item.name}
className="h-16 w-16 shrink-0 rounded object-cover cursor-zoom-in"
onClick={() => setLightboxImg(thumbnail)}
/>
) : ( ) : (
<div <div
className="flex h-16 w-16 shrink-0 items-center justify-center rounded text-2xl" className="flex h-16 w-16 shrink-0 items-center justify-center rounded text-2xl"
@@ -110,10 +123,11 @@ export function ItemCard({ item, onEdit, onRemove }: Props) {
<li <li
key={p.id} key={p.id}
className="flex items-center justify-between gap-2 text-xs" className="flex items-center justify-between gap-2 text-xs"
style={{ color: 'var(--color-grey)', cursor: p.image ? 'default' : undefined }} style={{ color: 'var(--color-grey)', cursor: p.image ? 'zoom-in' : undefined }}
onMouseEnter={p.image ? e => { setHoverImg(resolveImageSync(p.image) ?? null); setMousePos({ x: e.clientX, y: e.clientY }) } : undefined} onMouseEnter={p.image ? e => { setHoverImg(resolveImageSync(p.image) ?? null); setMousePos({ x: e.clientX, y: e.clientY }) } : undefined}
onMouseMove={p.image ? e => setMousePos({ x: e.clientX, y: e.clientY }) : undefined} onMouseMove={p.image ? e => setMousePos({ x: e.clientX, y: e.clientY }) : undefined}
onMouseLeave={p.image ? () => setHoverImg(null) : undefined} onMouseLeave={p.image ? () => setHoverImg(null) : undefined}
onClick={p.image ? () => { const r = resolveImageSync(p.image); if (r) setLightboxImg(r) } : undefined}
> >
<div className="flex items-center gap-1.5 min-w-0"> <div className="flex items-center gap-1.5 min-w-0">
<span className="shrink-0 rounded px-1.5 py-0.5" style={{ background: 'var(--color-bg2)', color: 'var(--color-grey)' }}> <span className="shrink-0 rounded px-1.5 py-0.5" style={{ background: 'var(--color-bg2)', color: 'var(--color-grey)' }}>
@@ -141,15 +155,32 @@ export function ItemCard({ item, onEdit, onRemove }: Props) {
{/* Hover image preview */} {/* Hover image preview */}
{hoverImg && ( {hoverImg && (
<div <div
className="pointer-events-none fixed z-50 rounded-lg border shadow-lg overflow-hidden" className="fixed z-50 rounded-lg border shadow-lg overflow-hidden cursor-zoom-in"
style={{ style={{
left: mousePos.x + 16, left: mousePos.x + 16,
top: mousePos.y + 16, top: mousePos.y + 16,
borderColor: 'var(--color-bg3)', borderColor: 'var(--color-bg3)',
background: 'var(--color-bg1)', background: 'var(--color-bg1)',
}} }}
onClick={() => setLightboxImg(hoverImg)}
> >
<img src={hoverImg} alt="" className="block h-48 w-48 object-cover" /> <img src={hoverImg} alt="" className="pointer-events-none block h-48 w-48 object-cover" />
</div>
)}
{/* Lightbox */}
{lightboxImg && (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 cursor-zoom-out"
style={{ background: 'rgba(0,0,0,0.8)' }}
onClick={() => setLightboxImg(null)}
>
<img
src={lightboxImg}
alt=""
className="max-h-full max-w-full rounded-lg object-contain shadow-2xl"
style={{ maxHeight: '90vh', maxWidth: '90vw' }}
/>
</div> </div>
)} )}
</div> </div>