Files
unified-media-manager/frontend/src/components/ConfirmModal.tsx
2026-04-24 10:45:19 -07:00

85 lines
2.9 KiB
TypeScript

import { useEffect, useRef, type ReactNode } from 'react'
interface ConfirmModalProps {
open: boolean
title: string
message: string
onConfirm: () => void
onCancel: () => void
destructive?: boolean
confirmLabel?: string
children?: ReactNode
}
const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
export default function ConfirmModal({ open, title, message, onConfirm, onCancel, destructive, confirmLabel, children }: ConfirmModalProps) {
const dialogRef = useRef<HTMLDivElement>(null)
const cancelRef = useRef<HTMLButtonElement>(null)
useEffect(() => {
if (!open) return
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onCancel()
return
}
if (e.key === 'Tab' && dialogRef.current) {
const focusable = Array.from(dialogRef.current.querySelectorAll<HTMLElement>(FOCUSABLE))
if (!focusable.length) return
const first = focusable[0]
const last = focusable[focusable.length - 1]
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault()
last.focus()
}
} else {
if (document.activeElement === last) {
e.preventDefault()
first.focus()
}
}
}
}
document.addEventListener('keydown', handleKey)
cancelRef.current?.focus()
return () => document.removeEventListener('keydown', handleKey)
}, [open, onCancel])
if (!open) return null
return (
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50" onClick={onCancel} role="presentation">
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby="confirm-modal-title"
aria-describedby="confirm-modal-desc"
className="bg-gray-900 border border-gray-800 rounded-lg p-6 max-w-md w-full mx-4"
onClick={e => e.stopPropagation()}
>
<h3 id="confirm-modal-title" className="text-xl font-semibold mb-2 text-gray-100">{title}</h3>
<p id="confirm-modal-desc" className="text-gray-400 text-sm mb-4">{message}</p>
{children}
<div className="flex gap-3 justify-end">
<button ref={cancelRef} onClick={onCancel} className="px-4 py-2 text-sm bg-gray-700 hover:bg-gray-600 rounded text-gray-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400">
Cancel
</button>
<button
onClick={onConfirm}
className={`px-4 py-2 text-sm rounded font-semibold min-h-[36px] ${
destructive ? 'bg-red-600 hover:bg-red-500 text-white' : 'bg-indigo-500 hover:bg-indigo-400 text-white'
} focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400`}
>
{confirmLabel ?? 'Confirm'}
</button>
</div>
</div>
</div>
)
}