import { useState } from 'react'; import { Meta, StoryObj } from '@storybook/react-webpack5'; import { action } from 'storybook/actions'; import { ClockBorderless } from '@transferwise/icons'; import { lorem40 } from '../test-utils'; import { Sentiment, Status } from '../common'; import { Button, Field, SelectInput } from '..'; import Alert, { AlertArrowPosition, type AlertProps as FullAlertProps } from './Alert'; /** * > **DEPRECATED** Use [InfoPrompt](?path=/docs/prompts-infoprompt--docs). Run codemod to migrate: **`npx @wise/wds-codemods@latest info-prompt`**. */ export default { component: Alert, title: 'Prompts/Alert', tags: ['deprecated'], args: { type: Sentiment.POSITIVE, message: 'Payments sent to your bank details **today** might not arrive in time for the holidays.', }, argTypes: { arrow: { name: 'arrow (deprecated)', type: { name: 'enum', value: Object.keys(AlertArrowPosition), }, }, type: { type: { name: 'enum', value: [ Sentiment.POSITIVE, Sentiment.NEGATIVE, Sentiment.NEUTRAL, Sentiment.WARNING, Sentiment.PENDING, Status.PENDING, ], }, }, }, } satisfies Meta; type Story = StoryObj; export const Basic: Story = {}; export const Variants: Story = { render: ({ message }) => { const variants = [ { type: Sentiment.POSITIVE, title: 'Positive Title' }, { type: Sentiment.NEGATIVE, title: 'Negative Title' }, { type: Sentiment.NEUTRAL, title: 'Neutral Title' }, { type: Sentiment.WARNING, title: 'Warning Title' }, { type: Sentiment.PENDING, title: 'Pending Sentiment Title' }, { type: Status.PENDING, title: 'Pending Status Title' }, ]; return ( <> {variants.map((variant) => ( ))} ); }, args: { message: 'This message can be changed using controls', }, parameters: { controls: { exclude: /^(?!message$).*/ }, }, argTypes: { message: { control: 'text' }, }, }; export const WithLinkAction: Story = { args: { action: { href: '/', text: 'Read more', }, onDismiss: () => {}, }, }; export const WithButtonAction: Story = { args: { action: { onClick: action('alert action clicked'), text: 'Open', }, onDismiss: () => {}, }, }; export const WithArrow: Story = { args: { arrow: AlertArrowPosition.TOP_LEFT, onDismiss: () => {}, }, }; const CustomIcon = () => { return (
); }; export const WithCustomIcon: Story = { args: { icon: , onDismiss: () => {}, }, }; export const WithTitleAndButtonAction: Story = { args: { type: Sentiment.NEUTRAL, title: 'We’re reviewing your information.', message: 'Payments sent to your bank details **today** might not arrive in time for the holidays.', onDismiss: () => {}, action: { onClick: action('alert action clicked'), text: 'Open', }, }, }; export const WithTitleAndButtonActionAndLongBody: Story = { args: { type: Sentiment.NEUTRAL, title: 'We’re reviewing your information.', message: lorem40, onDismiss: () => {}, action: { onClick: action('alert action clicked'), text: 'Open', }, }, }; export const WithTitleAndLinkActionAndLongBody: Story = { args: { type: Sentiment.NEUTRAL, title: 'We’re reviewing your information.', message: lorem40, onDismiss: () => {}, action: { href: '/', text: 'Open', }, }, }; /** * For ARIA live region to function correctly with screen readers (SR), * the container with an appropriate ARIA role (in the case of this * component, it's `status` or `alert`) must be rendered first. * Once present in the accessibility tree (AT), its dynamic contents * will be announced correctly. * * It's not a problem if your page includes an Alert that is initially * visible at the very first render, but if it appears as a result of * user interaction or is async, e.g. BE-driven, then the screen reader * might not announce it correctly. It becomes even more tricky if an * Alert is triggerred by a component that causes refocusing or introduces * many modifications to the AT, such as MoneyInput, for example, as that * entirely hijacks screen-reader's attention and prevents it from * announcing live region changes. * * In order for this to work, we've introduced a simple mechanism which * overcomes all of the above: * 1. On the component mount, we render the live region wrapper. * 2. We also render the actual Alert but set `aria-hidden` on it. * 3. After 175ms, which has been confirmed as sufficient delay for * the SRs to become responsive after trigger's activity, we remove the * `aria-hidden` attribute, which, in turn, initiates the announcements. * * **NB:** While this approach has no visual side-effects (e.g. layout * shift), it makes the Alert hidden from the assistive tech for 175ms, * during which all nested [focusable components such as links and buttons * are disabled](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus?application=axeAPI). * It's our belief that such short pause between announcements is * negligible for the user. */ export const DynamicRender: Story = { render: function Render(args) { const [isOneActive, setIsOneActive] = useState(false); const [isTwoActive, setIsTwoActive] = useState(false); const [value, setValue] = useState(); return ( <> setIsTwoActive(value === 'two')} /> {isOneActive && This Alert has a simple trigger.} {isTwoActive && ( This Alert has a complex trigger. )} ); }, args: { onDismiss: () => {}, action: { href: '/', text: 'Open', }, }, parameters: { docs: { source: { code: ` function MyComponent(alertProps) { const [isOneActive, setIsOneActive] = useState(false); const [isTwoActive, setIsTwoActive] = useState(false); const [value, setValue] = useState(); return ( <> setIsTwoActive(value === 'two')} /> {isOneActive && ( )} {isTwoActive && ( )} ); }`, }, }, }, }; const SENTIMENT = [ Sentiment.POSITIVE, Sentiment.NEGATIVE, Sentiment.NEUTRAL, Sentiment.WARNING, ] as const; type AlertProps = Omit & { type: (typeof SENTIMENT)[number] }; const getAlerts = () => { const length = Math.ceil(2 + Math.random() * 3); return Array.from({ length }, (_, index) => { const type = SENTIMENT[Math.floor(Math.random() * SENTIMENT.length)]; return { type, title: `Title ${index + 1}`, children: `This is a ${type} content`, }; }); }; /** * Sometimes, especially when dealing with Backend for Frontend (BFF), * it's not just about conditional rendering but also the fact that * you don't know how many `Alert`s you'll need to render. * * If you have to inject multiple Alerts simultaneously into the page, * please note that we currently have no means of guaranteeing the order * in which they screen reader will read them out. */ export const MultipleDynamicAlerts: Story = { render: function Render() { const [isActive, setIsActive] = useState(false); const [alerts, setAlerts] = useState([]); return ( <> {alerts.map((props) => ( ))} ); }, parameters: { docs: { source: { code: ` function Render() { const [isActive, setIsActive] = useState(false); const [alerts, setAlerts] = useState([]); return ( <> {alerts.map(props => ( ))} ); },`, }, }, }, };