diff --git a/src/features/library/components/CoverPickerModal.tsx b/src/features/library/components/CoverPickerModal.tsx index 5430cde..20b03f4 100644 --- a/src/features/library/components/CoverPickerModal.tsx +++ b/src/features/library/components/CoverPickerModal.tsx @@ -159,6 +159,17 @@ export function CoverPickerModal({ } }, []) + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key !== 'Escape') return + event.preventDefault() + onClose() + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [onClose]) + useEffect(() => { customUrlRef.current = customUrl }, [customUrl]) @@ -381,6 +392,9 @@ export function CoverPickerModal({ animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }} transition={{ type: 'spring', damping: 25, stiffness: 250 }} + role="dialog" + aria-modal="true" + aria-label="Choose Cover Image" className="fixed inset-4 z-[61] mx-auto my-auto flex max-h-[min(85vh,700px)] max-w-2xl flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-2xl dark:border-slate-700 dark:bg-slate-900" > {/* Header */} diff --git a/src/features/library/components/MatchPreviewModal.tsx b/src/features/library/components/MatchPreviewModal.tsx index 78f0d0d..9978691 100644 --- a/src/features/library/components/MatchPreviewModal.tsx +++ b/src/features/library/components/MatchPreviewModal.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Check, Loader2, X } from 'lucide-react' import { cx } from '../lib/cx' import type { MatchPreview, MatchResult, MetadataCandidate } from '../../../lib/types' @@ -83,6 +83,17 @@ export function MatchPreviewModal({ preview, onConfirm, onClose, onConfirmed }: const selected = candidates[selectedIdx] ?? null const noCandidates = candidates.length === 0 + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key !== 'Escape') return + event.preventDefault() + onClose() + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [onClose]) + async function handleApprove() { if (!selected) return setIsApplying(true) @@ -117,7 +128,12 @@ export function MatchPreviewModal({ preview, onConfirm, onClose, onConfirmed }:
{/* Modal */} -
+
{/* Header */}
diff --git a/src/features/library/components/RescanMetadataModal.tsx b/src/features/library/components/RescanMetadataModal.tsx index dd75d63..8f087d1 100644 --- a/src/features/library/components/RescanMetadataModal.tsx +++ b/src/features/library/components/RescanMetadataModal.tsx @@ -243,6 +243,17 @@ export function RescanMetadataModal({ } }, []) + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key !== 'Escape') return + event.preventDefault() + onClose() + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [onClose]) + /* ---------- search ---------- */ const doSearch = useCallback(async () => { @@ -389,6 +400,9 @@ export function RescanMetadataModal({ animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }} transition={{ type: 'spring', damping: 25, stiffness: 250 }} + role="dialog" + aria-modal="true" + aria-label="Rescan Metadata" className={cx( 'fixed inset-4 z-[61] mx-auto my-auto flex max-h-[min(90vh,800px)] flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-2xl dark:border-slate-700 dark:bg-slate-900', step === 'fine-tune' ? 'max-w-[1400px]' : 'max-w-[900px]',