feat: implement role-based access for settings route and add focus trap hook
- Wrapped the Settings component in RequireRole to enforce access control for admin and user roles. - Introduced a new custom hook `useFocusTrap` to manage focus within modal dialogs, enhancing accessibility. - Applied the focus trap in InviteModal, PermissionsModal, and UserDetailModal to prevent focus from leaving the dialog. - Updated PassthroughLanding to focus on the heading when the component mounts.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { useEffect, type RefObject } from 'react'
|
||||
|
||||
const FOCUSABLE_SELECTOR =
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
|
||||
export function useFocusTrap(
|
||||
dialogRef: RefObject<HTMLElement | null>,
|
||||
isOpen: boolean,
|
||||
onEscape?: () => void,
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && onEscape) {
|
||||
onEscape()
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'Tab' && dialogRef.current) {
|
||||
const focusable =
|
||||
dialogRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)
|
||||
if (focusable.length === 0) return
|
||||
|
||||
const first = focusable[0]
|
||||
const last = focusable[focusable.length - 1]
|
||||
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault()
|
||||
last.focus()
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault()
|
||||
first.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const first = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR)
|
||||
first?.focus()
|
||||
})
|
||||
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [isOpen, onEscape, dialogRef])
|
||||
}
|
||||
Reference in New Issue
Block a user