(props: ToastContainerProps)
| 355 | * at the root of the app. |
| 356 | */ |
| 357 | export function ToastContainer(props: ToastContainerProps): ReactNode { |
| 358 | let {placement = 'bottom'} = props; |
| 359 | let queue = getGlobalToastQueue(); |
| 360 | let align = 'center'; |
| 361 | [placement, align = 'center'] = placement.split(' ') as any; |
| 362 | let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2'); |
| 363 | let regionRef = useRef<HTMLDivElement | null>(null); |
| 364 | |
| 365 | let state = useOverlayTriggerState({}); |
| 366 | let {isOpen: isExpanded, close, toggle} = state; |
| 367 | let ctx = useMemo( |
| 368 | () => ({ |
| 369 | isExpanded, |
| 370 | toggleExpanded() { |
| 371 | if (!isExpanded && queue.visibleToasts.length <= 1) { |
| 372 | return; |
| 373 | } |
| 374 | |
| 375 | startViewTransition(() => toggle(), isExpanded ? 'toast-collapse' : 'toast-expand'); |
| 376 | } |
| 377 | }), |
| 378 | [isExpanded, toggle, queue] |
| 379 | ); |
| 380 | |
| 381 | // Set the state to collapsed whenever the queue is emptied. |
| 382 | useEffect(() => { |
| 383 | return queue.subscribe(() => { |
| 384 | if (queue.visibleToasts.length === 0 && isExpanded) { |
| 385 | close(); |
| 386 | } |
| 387 | }); |
| 388 | }, [queue, isExpanded, close]); |
| 389 | |
| 390 | let collapse = () => { |
| 391 | regionRef.current?.focus(); |
| 392 | ctx.toggleExpanded(); |
| 393 | }; |
| 394 | |
| 395 | // Prevent scroll, aria hide outside, and contain focus when expanded, since we take over the whole screen. |
| 396 | // Attach event handler to the ref since ToastRegion doesn't pass through onKeyDown. |
| 397 | useModalOverlay({}, state, regionRef); |
| 398 | useEvent( |
| 399 | regionRef, |
| 400 | 'keydown', |
| 401 | isExpanded |
| 402 | ? e => { |
| 403 | if (e.key === 'Escape') { |
| 404 | collapse(); |
| 405 | } |
| 406 | } |
| 407 | : undefined |
| 408 | ); |
| 409 | |
| 410 | let prefersReducedMotion = useMediaQuery('(prefers-reduced-motion)'); |
| 411 | let reduceMotion = props['PRIVATE_forceReducedMotion'] ?? prefersReducedMotion; |
| 412 | useEffect(() => { |
| 413 | let oldGlobalReduceMotion = globalReduceMotion; |
| 414 | globalReduceMotion = reduceMotion; |
nothing calls this directly
no test coverage detected