@@ -21,6 +21,7 @@ import {
2121 useModalDialog ,
2222 useModalDialogIsOpen ,
2323} from '../Dialog' ;
24+ import { useAriaIdentifiers } from '../../hooks/useAriaIdentifiers' ;
2425
2526export type ModalCloseEvent =
2627 | KeyboardEvent
@@ -40,6 +41,8 @@ export type ModalProps = {
4041 'aria-labelledby' ?: string ;
4142 /** ID of the element that describes the modal dialog. */
4243 'aria-describedby' ?: string ;
44+ /** ARIA role for the modal dialog surface. */
45+ role ?: 'alertdialog' | 'dialog' ;
4346 /** If provided, the close button is rendered on overlay */
4447 CloseButtonOnOverlay ?: ComponentType < ComponentProps < 'button' > > ;
4548 /** Callback handler for closing of modal. */
@@ -58,13 +61,27 @@ export const GlobalModal = ({
5861 onClose,
5962 onCloseAttempt,
6063 open,
64+ role = 'dialog' ,
6165} : PropsWithChildren < ModalProps > ) => {
6266 const dialog = useModalDialog ( ) ;
6367 const isOpen = useModalDialogIsOpen ( ) ;
6468 const overlayRef = useRef < HTMLDivElement | null > ( null ) ;
6569 const closeButtonRef = useRef < HTMLButtonElement | null > ( null ) ;
6670 const closingRef = useRef ( false ) ;
67- const { theme } = useChatContext ( 'GlobalModal' ) ;
71+ const { theme } = useChatContext ( ) ;
72+ const dialogLabelingBaseId = dialog . id ;
73+ const {
74+ descriptionId : derivedAriaDescribedby = ariaDescribedby ,
75+ titleId : derivedAriaLabelledby = ariaLabelledby ,
76+ } = useAriaIdentifiers ( dialogLabelingBaseId ) ;
77+ const resolvedAriaLabelledby = ariaLabel
78+ ? ariaLabelledby
79+ : ( ariaLabelledby ?? derivedAriaLabelledby ) ;
80+ const resolvedAriaDescribedby =
81+ role === 'alertdialog'
82+ ? ( ariaDescribedby ?? derivedAriaDescribedby )
83+ : ariaDescribedby ;
84+ const resolvedAriaLabel = resolvedAriaLabelledby ? undefined : ariaLabel ;
6885
6986 const maybeClose = useCallback (
7087 ( source : ModalCloseSource , event : ModalCloseEvent ) => {
@@ -78,11 +95,12 @@ export const GlobalModal = ({
7895 [ dialog , onClose , onCloseAttempt ] ,
7996 ) ;
8097
81- const modalContextValue = useMemo < { close : ( ) => void } > (
98+ const modalContextValue = useMemo < { close : ( ) => void ; dialogId ?: string } > (
8299 ( ) => ( {
83100 close : ( ) => maybeClose ( 'button' , { } as ModalCloseEvent ) ,
101+ dialogId : dialogLabelingBaseId ,
84102 } ) ,
85- [ maybeClose ] ,
103+ [ dialogLabelingBaseId , maybeClose ] ,
86104 ) ;
87105
88106 const handleOverlayClick = ( event : React . MouseEvent < HTMLDivElement > ) => {
@@ -129,13 +147,13 @@ export const GlobalModal = ({
129147 >
130148 < FocusScope autoFocus contain >
131149 < div
132- aria-describedby = { ariaDescribedby }
133- aria-label = { ariaLabelledby ? undefined : ariaLabel }
134- aria-labelledby = { ariaLabelledby }
150+ aria-describedby = { resolvedAriaDescribedby }
151+ aria-label = { resolvedAriaLabel }
152+ aria-labelledby = { resolvedAriaLabelledby }
135153 aria-modal = 'true'
136154 className = 'str-chat__modal__dialog'
137155 onKeyDown = { handleDialogKeyDown }
138- role = 'dialog'
156+ role = { role }
139157 >
140158 { children }
141159 </ div >
0 commit comments