import { clsx } from 'clsx'; import { useState, useRef, useEffect } from 'react'; import Body from '../body/Body'; import { CloseButton, Sentiment, Size, Status, Typography, WDS_LIVE_REGION_DELAY_MS, } from '../common'; import StatusIcon from '../statusIcon'; import Title from '../title/Title'; import { logActionRequired } from '../utilities'; import InlineMarkdown from './inlineMarkdown'; import Button from '../button'; import Link from '../link'; /** * @deprecated `Alert` component is being replaced by the `InfoPrompt` component */ export type AlertAction = { 'aria-label'?: string; href?: string; target?: string; text: React.ReactNode; onClick?: () => void; /** Controls the rendered element: `'link'` (default) renders a Link, `'button'` renders a Button. When `'button'` is used with `href`, renders an anchor styled as a button. */ as?: 'button' | 'link'; }; /** @deprecated Use `"top" | "bottom"` instead. */ type AlertTypeDeprecated = `${Sentiment.SUCCESS | Sentiment.INFO | Sentiment.ERROR}`; type AlertTypeResolved = `${ | Sentiment.POSITIVE | Sentiment.NEUTRAL | Sentiment.WARNING | Sentiment.NEGATIVE // remove when all instances of Sentiment.PENDING have been updated to Status.PENDING | Sentiment.PENDING | Status.PENDING}`; /** * @deprecated `Alert` component is being replaced by the `InfoPrompt` component */ export type AlertType = AlertTypeResolved | AlertTypeDeprecated; /** * @deprecated `Alert` component is being replaced by the `InfoPrompt` component */ export enum AlertArrowPosition { TOP_LEFT = 'up-left', TOP = 'up-center', TOP_RIGHT = 'up-right', BOTTOM_LEFT = 'down-left', BOTTOM = 'down-center', BOTTOM_RIGHT = 'down-right', } /** * @deprecated `Alert` component is being replaced by the `InfoPrompt` component */ export interface AlertProps { /** An optional call to action to sit under the main body of the alert. If your label is short, use aria-label to provide more context */ action?: AlertAction; className?: string; /** An optional icon. If not provided, we will default the icon to something appropriate for the type */ icon?: React.ReactNode; /** * Override for [StatusIcon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs) * announced by the screen readers * */ statusIconLabel?: string; /** Title for the alert component */ title?: string; /** The main body of the alert. Accepts plain text and bold words specified with **double stars */ message?: string; /** The presence of the onDismiss handler will trigger the visibility of the close button */ onDismiss?: React.MouseEventHandler; /** * The type dictates which icon and colour will be used * @default 'neutral' */ type?: AlertType; /** @deprecated Use `InlinePrompt` instead. */ arrow?: `${AlertArrowPosition}`; /** @deprecated Use `message` instead. Be aware `message` only accepts plain text or text with **bold** markdown. */ children?: React.ReactNode; /** @deprecated Use `onDismiss` instead. */ dismissible?: boolean; /** @deprecated Alert component doesn't support `size` anymore, please remove this prop. */ size?: `${Size}`; } function resolveType(type: AlertType): AlertTypeResolved { switch (type) { case 'success': return 'positive'; case 'info': return 'neutral'; case 'error': return 'negative'; default: return type; } } /** * @deprecated Use [`InfoPrompt`](https://storybook.wise.design/?path=/docs/prompts-infoprompt--docs) component instead. Run codemod to migrate: **`npx @wise/wds-codemods@latest info-prompt`** */ export default function Alert({ action, className, icon, statusIconLabel, onDismiss, message, title, type = 'neutral', arrow, children, size, dismissible, }: AlertProps) { useEffect(() => { if (arrow !== undefined) { logActionRequired( "Alert component doesn't support 'arrow' anymore, use 'InlinePrompt' instead.", ); } }, [arrow]); useEffect(() => { if (children !== undefined) { logActionRequired( "Alert component doesn't support 'children' anymore, use 'message' instead.", ); } }, [children]); useEffect(() => { if (dismissible !== undefined) { logActionRequired( "Alert component doesn't support 'dismissible' anymore, use 'onDismiss' instead.", ); } }, [dismissible]); useEffect(() => { if (size !== undefined) { logActionRequired("Alert component doesn't support 'size' anymore, please remove that prop."); } }, [size]); const resolvedType = resolveType(type); useEffect(() => { if (resolvedType !== type) { logActionRequired( `Alert component has deprecated '${type}' value for the 'type' prop. Please use '${resolvedType}' instead.`, ); } }, [resolvedType, type]); const [shouldFire, setShouldFire] = useState(); const [shouldAnnounce, setShouldAnnounce] = useState(false); useEffect(() => { const timeoutId = setTimeout(() => { setShouldAnnounce(true); }, WDS_LIVE_REGION_DELAY_MS); return () => clearTimeout(timeoutId); }, []); const closeButtonReference = useRef(null); /** * All focusable elements must be disabled until we announce the alert. * @see https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus?application=axeAPI */ const accessibleTabIndex = shouldAnnounce ? undefined : -1; return (
setShouldFire(true)} onTouchEnd={(event) => { if ( shouldFire && action?.href && // Check if current event is triggered from closeButton event.target instanceof Node && closeButtonReference.current && !closeButtonReference.current.contains(event.target) ) { if (action.target === '_blank') { window.top?.open(action.href); } else { window.top?.location.assign(action.href); } } setShouldFire(false); }} onTouchMove={() => setShouldFire(false)} >
{icon || }
{title && ( {title} )} {children || {message}}
{action && ('href' in action && action.as !== 'button' ? ( {action.text} ) : ( ))}
{onDismiss && (
)}
); } function alertArrowClassNames(arrow: `${AlertArrowPosition}`) { switch (arrow) { case 'down-center': return 'arrow arrow-bottom arrow-center'; case 'down-left': return 'arrow arrow-bottom arrow-left'; case 'down-right': return 'arrow arrow-bottom arrow-right'; case 'up-center': return 'arrow arrow-center'; case 'up-right': return 'arrow arrow-right'; case 'up-left': default: return 'arrow'; } }