diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index ac0f7526..20dd1482 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -9,6 +9,7 @@ import AnimateItems from './AnimateItems'; import { PATH_ROOT } from '@/site/paths'; import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion'; import useMetaThemeColor from '@/site/useMetaThemeColor'; +import useEscapeHandler from '@/photo/useEscapeHandler'; export default function Modal({ onClosePath, @@ -55,6 +56,8 @@ export default function Modal({ }, }); + useEscapeHandler(onClose, true); + return ( , @@ -13,7 +13,7 @@ export default function useImageZoomControls( ) { const viewerRef = useRef(null); - const { isCommandKOpen, setShouldRespondToKeyboardCommands } = useAppState(); + const { setShouldRespondToKeyboardCommands } = useAppState(); useEffect(() => { if (imageRef.current && isEnabled) { @@ -67,21 +67,12 @@ export default function useImageZoomControls( }, [imageRef, onHide]); // On 'F' keydown, toggle fullscreen - const handleKeyDown = useCallback((e: KeyboardEvent) => { - if ( - shouldExpandOnFKeydown && - !isCommandKOpen && - e.key.toUpperCase() === 'F' - ) { + const handleKeyDown = useCallback(() => { + if (shouldExpandOnFKeydown) { viewerRef.current?.show(); } - }, [shouldExpandOnFKeydown, isCommandKOpen]); - useEffect(() => { - document.addEventListener(EVENT_KEYDOWN, handleKeyDown); - return () => { - document.removeEventListener(EVENT_KEYDOWN, handleKeyDown); - }; - }, [handleKeyDown]); + }, [shouldExpandOnFKeydown]); + useKeydownHandler(handleKeyDown, ['F']); return { open, diff --git a/src/photo/PhotoEscapeHandler.tsx b/src/photo/PhotoEscapeHandler.tsx index b8da15c9..afaf1019 100644 --- a/src/photo/PhotoEscapeHandler.tsx +++ b/src/photo/PhotoEscapeHandler.tsx @@ -1,32 +1,22 @@ 'use client'; import { getEscapePath } from '@/site/paths'; -import { useAppState } from '@/state/AppState'; import { useRouter, usePathname } from 'next/navigation'; -import { useEffect } from 'react'; - -const LISTENER_KEYUP = 'keyup'; +import { useCallback } from 'react'; +import useEscapeHandler from './useEscapeHandler'; export default function PhotoEscapeHandler() { const router = useRouter(); const pathname = usePathname(); - const { shouldRespondToKeyboardCommands } = useAppState(); - const escapePath = getEscapePath(pathname); - useEffect(() => { - if (shouldRespondToKeyboardCommands) { - const onKeyUp = (e: KeyboardEvent) => { - if (e.key?.toUpperCase() === 'ESCAPE' && escapePath) { - router.push(escapePath, { scroll: false }); - }; - }; - window.addEventListener(LISTENER_KEYUP, onKeyUp); - return () => window.removeEventListener(LISTENER_KEYUP, onKeyUp); - } - }, [shouldRespondToKeyboardCommands, router, escapePath]); + const escapeHandler = useCallback(() => { + if (escapePath) { router.push(escapePath, { scroll: false }); } + }, [escapePath, router]); + + useEscapeHandler(escapeHandler); return null; } diff --git a/src/photo/useEscapeHandler.ts b/src/photo/useEscapeHandler.ts new file mode 100644 index 00000000..2218f0d5 --- /dev/null +++ b/src/photo/useEscapeHandler.ts @@ -0,0 +1,12 @@ +import useKeydownHandler from '@/utility/useKeydownHandler'; + +export default function useEscapeHandler( + onEscape?: () => void, + ignoreShouldRespondToKeyboardCommands?: boolean, +) { + useKeydownHandler( + onEscape, + ['ESCAPE'], + ignoreShouldRespondToKeyboardCommands, + ); +} diff --git a/src/share/ShareModal.tsx b/src/share/ShareModal.tsx index 97d42c92..1b4b0379 100644 --- a/src/share/ShareModal.tsx +++ b/src/share/ShareModal.tsx @@ -4,7 +4,7 @@ import Modal from '@/components/Modal'; import { TbPhotoShare } from 'react-icons/tb'; import { clsx } from 'clsx/lite'; import { BiCopy } from 'react-icons/bi'; -import { JSX, ReactNode } from 'react'; +import { JSX, ReactNode, useEffect } from 'react'; import { shortenUrl } from '@/utility/url'; import { toastSuccess } from '@/toast'; import { PiXLogo } from 'react-icons/pi'; @@ -24,7 +24,15 @@ export default function ShareModal({ socialText: string children: ReactNode }) { - const { setShareModalProps } = useAppState(); + const { + setShareModalProps, + setShouldRespondToKeyboardCommands, + } = useAppState(); + + useEffect(() => { + setShouldRespondToKeyboardCommands?.(false); + return () => setShouldRespondToKeyboardCommands?.(true); + }, [setShouldRespondToKeyboardCommands]); const renderIcon = ( icon: JSX.Element, diff --git a/src/utility/useKeydownHandler.ts b/src/utility/useKeydownHandler.ts new file mode 100644 index 00000000..b6f2af40 --- /dev/null +++ b/src/utility/useKeydownHandler.ts @@ -0,0 +1,32 @@ +import { useAppState } from '@/state/AppState'; +import { useCallback, useEffect } from 'react'; + +const LISTENER_KEYDOWN = 'keydown'; + +export default function useKeydownHandler( + onKeydown?: (e: KeyboardEvent) => void, + keys: string[] = [], + ignoreShouldRespondToKeyboardCommands?: boolean, +) { + const { shouldRespondToKeyboardCommands } = useAppState(); + + const onKeyUp = useCallback((e: KeyboardEvent) => { + if (keys.some(key => key.toUpperCase() === e.key?.toUpperCase())) { + onKeydown?.(e); + } + }, [onKeydown, keys]); + + useEffect(() => { + if ( + shouldRespondToKeyboardCommands || + ignoreShouldRespondToKeyboardCommands + ) { + window.addEventListener(LISTENER_KEYDOWN, onKeyUp); + return () => window.removeEventListener(LISTENER_KEYDOWN, onKeyUp); + } + }, [ + shouldRespondToKeyboardCommands, + ignoreShouldRespondToKeyboardCommands, + onKeyUp, + ]); +}