import React , { useCallback , useEffect , useState } from 'react' ; import { animated , useSpring } from 'react-spring' ; import { VisuallyHidden } from '@react-aria/visually-hidden' ; import 'hds-core' ; import classNames from '../../utils/classNames' ; import styles from './Notification.module.css' ; import { IconInfoCircleFill , IconErrorFill , IconAlertCircleFill , IconCheckCircleFill , IconCross } from '../../icons' ; export type NotificationType = 'info' | 'error' | 'alert' | 'success' ; export type NotificationSizeInline = 'default' | 'small' | 'large' ; export type NotificationSizeToast = Exclude < NotificationSizeInline , 'large' > ; export type NotificationPosition = 'inline' | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' ; type CommonProps = React . PropsWithChildren < { default autoClose ? : boolean ; default autoCloseDuration ? : number ; /**
     * Additional class names to apply to the notification
     */ className ? : string ; default closeAnimationDuration ? : number ; /**
     * Value for the data-testid attribute that is applied to the root component.
     */ dataTestId ? : string ; default displayAutoCloseProgress ? : boolean ; default invisible ? : boolean ; /**
     * The label of the notification.
     * Note: Labels are not displayed visually for small notifications, but they are still accessible to assistive technology. This could be used to help screen reader users to better understand the context of the notification.
     */ label ? : string | React . ReactNode ; /**
     * Callback fired when the notification is closed
     */ onClose ? : ( ) => void ; /**
     * Override or extend the styles applied to the component
     */ style ? : React . CSSProperties ; default type ? : NotificationType ; } > ; type PositionAndSize = { position ? : 'inline' ; size ? : NotificationSizeInline ; } | { /**
     * The position of the notification
     */ position ? : NotificationPosition ; /**
     * The size of the notification
     */ size ? : NotificationSizeToast ; } ; type Dismissible = { dismissible ? : false ; closeButtonLabelText ? : string ; } | { default dismissible ? : boolean ; /**
     * The aria-label and title for the close button
     */ closeButtonLabelText : string ; } ; export type NotificationProps = CommonProps & PositionAndSize & Dismissible ; const icons = { info : IconInfoCircleFill , success : IconCheckCircleFill , error : IconErrorFill , alert : IconAlertCircleFill , } ; param position const getOpenTransition = ( position ) => { const centerPositioned = position . includes ( 'center' ) ; const animateFromTop = position . includes ( 'top' ) ; return { from : { transform : `translate3d(${ centerPositioned ? '-50%' : '0' }, ${ animateFromTop ? '-' : '' }32px, 0)` , opacity : 0.66 , } , to : { transform : `translate3d(${ centerPositioned ? '-50%' : '0' }, 0, 0)` , opacity : 1 , } , config : { friction : 30 , tension : 300 , } , } ; } ; param duration const getCloseTransition = ( duration ) => ( { from : { opacity : 1 } , to : { opacity : 0 } , config : { duration } , } ) ; param duration const getAutoCloseTransition = ( duration ) => ( { from : { transform : 'translate3d(-100%, 0, 0)' } , to : { transform : 'translate3d(0%, 0, 0)' } , config : { duration , } , } ) ; param visuallyHidden param children constructor const ConditionalVisuallyHidden = ( { visuallyHidden , children } ) => visuallyHidden ? < VisuallyHidden > { children } < /VisuallyHidden> : children; export const Notification = React . forwardRef < HTMLDivElement , NotificationProps > ( ( { autoClose = false , autoCloseDuration = 6000 , children , className = '' , closeAnimationDuration = 85 , closeButtonLabelText , dataTestId , dismissible = false , displayAutoCloseProgress = true , invisible = false , label , position = 'inline' , onClose = ( ) => null , size = 'default' , style , type = 'info' , } , ref ) => { if ( position !== 'inline' && size === 'large' ) { console . warn ( `Size '${ size }' is only allowed for inline positioned notifications` ) ; size = 'default' ; } if ( position === 'inline' && autoClose ) { console . warn ( `The 'autoClose' property is not allowed for inline positioned notifications` ) ; autoClose = false ; } const [ open , setOpen ] = useState ( true ) ; const handleClose = useCallback ( ( ) => { setOpen ( false ) ; setTimeout ( ( ) => onClose ( ) , closeAnimationDuration ) ; } , [ onClose , closeAnimationDuration ] ) ; useEffect ( ( ) => { const interval = setTimeout ( ( ) => { if ( autoClose ) handleClose ( ) ; } , autoCloseDuration ) ; return ( ) => clearTimeout ( interval ) ; } , [ autoClose , autoCloseDuration , handleClose ] ) ; const Icon = icons [ type ] ; const openTransitionProps = position !== 'inline' ? getOpenTransition ( position ) : { } ; const closeTransitionProps = getCloseTransition ( closeAnimationDuration ) ; const autoCloseTransitionProps = displayAutoCloseProgress ? getAutoCloseTransition ( autoCloseDuration ) : { } ; const notificationTransition = useSpring ( open ? openTransitionProps : closeTransitionProps ) ; const autoCloseTransition = useSpring ( autoCloseTransitionProps ) ; const role = position !== 'inline' ? 'alert' : null ; return ( < ConditionalVisuallyHidden visuallyHidden = { invisible } > < animated . section style = { { ... notificationTransition , ... ( style as any ) } } className = { classNames ( styles [ position ] , styles . notification , styles [ size ] , styles [ type ] , autoClose && styles . noBorder , className ) } aria - label = "Notification" aria - atomic = "true" data - testid = { dataTestId } > { autoClose && < animated . div style = { autoCloseTransition } className = { styles . autoClose } / > } < div className = { styles . content } role = { role } ref = { ref } > { label && ( < div className = { styles . label } role = "heading" aria - level = { 2 } > < Icon className = { styles . icon } aria - hidden / > < ConditionalVisuallyHidden visuallyHidden = { size === 'small' } > { label } < /ConditionalVisuallyHidden> < /div>)} { children && < div className = { styles . body } > { children } < /div>} < /div> { dismissible && ( < button className = { classNames ( styles . close , styles [ type ] ) } type = "button" title = { closeButtonLabelText } aria - label = { closeButtonLabelText } onClick = { handleClose } > < IconCross aria - hidden / > < /button>)} < /animated.section> < /ConditionalVisuallyHidden>); } ) ;