Add focus trap to Modal component (#28754)

* Add focus-trap-react npm package

* Trap the focus for the Modal

* In some cases, the focus will not return to the trigger element

* If there are no tabbable elements, the focus should fallback

* Add explanations for focusTrapOptions props and extend test

* Auto generate package-lock.json - Add focus-trap-react npm package

GitOrigin-RevId: 488a05d5e95dd369c69bedcfaf7c1fd5e456e302
This commit is contained in:
Rebeka Dekany
2025-11-17 09:05:45 +00:00
committed by Copybot
parent 198a2fc943
commit b773ac2715
6 changed files with 206 additions and 9 deletions
@@ -0,0 +1,31 @@
import { useRef } from 'react'
import {
FocusTrap as FocusTrapReact,
FocusTrapProps as FocusTrapReactProps,
} from 'focus-trap-react'
export type FocusTrapProps = {
active: FocusTrapReactProps['active']
children: React.ReactNode
focusTrapOptions?: FocusTrapReactProps['focusTrapOptions']
}
export default function FocusTrap({
active,
focusTrapOptions,
children,
}: FocusTrapProps) {
const containerRef = useRef<HTMLDivElement>(null)
return (
<FocusTrapReact
active={active}
focusTrapOptions={{
...focusTrapOptions,
fallbackFocus: () => containerRef.current as HTMLElement,
}}
>
<div ref={containerRef}>{children}</div>
</FocusTrapReact>
)
}
@@ -1,3 +1,4 @@
import FocusTrap from '../focus-trap'
import {
Modal,
ModalProps,
@@ -6,18 +7,44 @@ import {
ModalFooterProps,
} from 'react-bootstrap'
import { ModalBodyProps } from 'react-bootstrap/ModalBody'
import type { Options as FocusTrapOptions } from 'focus-trap'
type OLModalProps = ModalProps & {
size?: 'sm' | 'lg'
onHide: () => void
}
show?: boolean
} & Pick<
FocusTrapOptions,
'escapeDeactivates' | 'clickOutsideDeactivates' | 'returnFocusOnDeactivate'
>
type OLModalHeaderProps = ModalHeaderProps & {
closeButton?: boolean
}
export function OLModal({ children, ...props }: OLModalProps) {
return <Modal {...props}>{children}</Modal>
export function OLModal({
children,
show = false,
onHide,
returnFocusOnDeactivate = true, // Return focus to trigger element when modal closes
escapeDeactivates = false, // Let React-Bootstrap Modal handle Escape key to avoid double Escape key handling
clickOutsideDeactivates = true, // Allow focus trap to deactivate on outside click and let React-Bootstrap Modal handle it
...props
}: OLModalProps) {
return (
<Modal show={show} onHide={onHide} {...props}>
<FocusTrap
active={show}
focusTrapOptions={{
escapeDeactivates,
clickOutsideDeactivates,
returnFocusOnDeactivate,
}}
>
{children}
</FocusTrap>
</Modal>
)
}
export function OLModalHeader({