Add image lightbox for item thumbnails and kit parts
This commit is contained in:
@@ -25,6 +25,14 @@ export function ItemCard({ item, onEdit, onRemove }: Props) {
|
||||
|
||||
const [hoverImg, setHoverImg] = useState<string | null>(null)
|
||||
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
|
||||
useEffect(() => {
|
||||
@@ -52,7 +60,12 @@ export function ItemCard({ item, onEdit, onRemove }: Props) {
|
||||
>
|
||||
{/* 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
|
||||
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
|
||||
key={p.id}
|
||||
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}
|
||||
onMouseMove={p.image ? e => setMousePos({ x: e.clientX, y: e.clientY }) : 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">
|
||||
<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 */}
|
||||
{hoverImg && (
|
||||
<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={{
|
||||
left: mousePos.x + 16,
|
||||
top: mousePos.y + 16,
|
||||
borderColor: 'var(--color-bg3)',
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user