import { useEffect, useRef, useState } from 'react' import { imgUrl } from '../api' const BASE_SHORTCUTS = [ ['← / →', 'previous / next photo'], ['Space', 'next photo (Shift+Space back)'], ['?', 'show / hide shortcuts'], ['Esc', 'close'], ] // `actions` defines the page's shortcuts as one table — display and dispatch // come from the same entry, so the help overlay can't drift from behavior: // { keys: ['p'], help: ['P', 'accept…'], run: (photo, key) => … }. // Keys fire only outside text inputs and while the help overlay is closed. export default function Lightbox({ photos, index, onClose, onNav, footer, actions }) { const photo = photos[index] const [showHelp, setShowHelp] = useState(false) // Handlers and view state live in a ref, updated every render, so the // window listener is attached once yet always dispatches against current // values — re-subscribing per render leaves a gap until effects re-run in // which a fast second keystroke hits a stale closure (and e.g. re-votes // the previous photo). const live = useRef({}) live.current = { index, count: photos.length, photo, onClose, onNav, actions, showHelp } useEffect(() => { // Only text entry captures keys (Escape leaves the field); focus on a // checkbox or button must not disable lightbox navigation. const isTyping = (el) => el.tagName === 'TEXTAREA' || el.isContentEditable || (el.tagName === 'INPUT' && !['checkbox', 'radio', 'button'].includes(el.type)) const handler = (e) => { const s = live.current if (isTyping(e.target)) { if (e.key === 'Escape') e.target.blur() return } if (e.metaKey || e.ctrlKey || e.altKey) return if (e.key === 'Escape') { if (s.showHelp) setShowHelp(false) else s.onClose() return } if (e.key === '?') { setShowHelp((h) => !h) return } // With the help overlay up, keys must not act on the photo behind it. if (s.showHelp) return if (e.key === 'ArrowRight' || (e.key === ' ' && !e.shiftKey)) { e.preventDefault() if (s.index < s.count - 1) s.onNav(s.index + 1) return } if (e.key === 'ArrowLeft' || (e.key === ' ' && e.shiftKey)) { e.preventDefault() if (s.index > 0) s.onNav(s.index - 1) return } const key = e.key.toLowerCase() const action = s.actions?.find((a) => a.keys.includes(key)) if (action && s.photo) action.run(s.photo, key) } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, []) useEffect(() => { document.body.style.overflow = 'hidden' return () => { document.body.style.overflow = '' } }, []) if (!photo) return null return (