diff --git a/src/components/Appointments/BookingModal.tsx b/src/components/Appointments/BookingModal.tsx index e92a5469..cc1fc7bd 100644 --- a/src/components/Appointments/BookingModal.tsx +++ b/src/components/Appointments/BookingModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { X } from 'lucide-react'; import { AppointmentType } from '@/types/appointments'; import { @@ -53,6 +53,8 @@ const TIME_OPTIONS = [ export default function BookingModal({ onClose }: BookingModalProps) { const { trigger } = useHaptic(); + const dialogRef = useRef(null); + const lastFocusedElementRef = useRef(null); const [formData, setFormData] = useState({ pet_id: '', vet_id: '', @@ -96,6 +98,68 @@ export default function BookingModal({ onClose }: BookingModalProps) { if (e.target === e.currentTarget) onClose(); }; + useEffect(() => { + const dialog = dialogRef.current; + if (!dialog) return; + + const focusableSelectors = [ + 'a[href]', + 'button:not([disabled])', + 'textarea:not([disabled])', + 'input:not([disabled])', + 'select:not([disabled])', + '[tabindex]:not([tabindex="-1"])', + ].join(','); + + const focusableElements = Array.from( + dialog.querySelectorAll(focusableSelectors) + ).filter((el) => !el.hasAttribute('disabled')); + + if (focusableElements.length > 0) { + focusableElements[0].focus(); + } + + lastFocusedElementRef.current = document.activeElement as HTMLElement; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + event.preventDefault(); + onClose(); + return; + } + + if (event.key !== 'Tab') return; + + if (focusableElements.length === 0) { + event.preventDefault(); + return; + } + + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + const activeElement = document.activeElement as HTMLElement; + + if (event.shiftKey) { + if (activeElement === firstElement || !dialog.contains(activeElement)) { + event.preventDefault(); + lastElement.focus(); + } + } else { + if (activeElement === lastElement || !dialog.contains(activeElement)) { + event.preventDefault(); + firstElement.focus(); + } + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + lastFocusedElementRef.current?.focus(); + }; + }, [onClose]); + return (
{/* Sheet slides up on mobile, centered modal on desktop */} -
+
{/* Header */}
{/* Drag handle (mobile) */}