diff --git a/packages/react-component-library/src/components/Toast/Toast.stories.tsx b/packages/react-component-library/src/components/Toast/Toast.stories.tsx index 822369f6d1..5cada79a86 100644 --- a/packages/react-component-library/src/components/Toast/Toast.stories.tsx +++ b/packages/react-component-library/src/components/Toast/Toast.stories.tsx @@ -1,8 +1,9 @@ import React, { useEffect } from 'react' import { Meta, StoryFn } from '@storybook/react' -import { TOAST_APPEARANCE, Toast, ToastProps, showToast } from '.' +import { TOAST_APPEARANCE, Toast, ToastProps, showToast, dismissToast } from '.' import { Button } from '../Button' +import { Group } from '../Group' export default { component: Toast, @@ -31,13 +32,22 @@ const ToastButton = (props: ToastProps) => { return (
- + + + +
) } diff --git a/packages/react-component-library/src/components/Toast/Toast.test.tsx b/packages/react-component-library/src/components/Toast/Toast.test.tsx index f9690d6939..a4b16df6a8 100644 --- a/packages/react-component-library/src/components/Toast/Toast.test.tsx +++ b/packages/react-component-library/src/components/Toast/Toast.test.tsx @@ -1,8 +1,8 @@ import React from 'react' import { color } from '@royalnavy/design-tokens' -import { render, screen, waitFor, within } from '@testing-library/react' +import { render, screen, within, waitFor } from '@testing-library/react' -import { TOAST_APPEARANCE, Toast, showToast } from '.' +import { TOAST_APPEARANCE, Toast, dismissToast, showToast } from '.' const LABEL = 'Example label' const MESSAGE = 'This is an example toast message' @@ -19,12 +19,20 @@ function setup() { /* Added this function to resolve an issue when running tests for * the whole of this file and more than one toast is shown */ -async function getLastToast(lastToastLabel: string) { - await waitFor(() => { - expect(screen.getByText(lastToastLabel)).toBeInTheDocument() - }) - - return screen.getAllByRole('status')[0] +async function getLastToast(label?: string) { + try { + const toasts = await screen.findAllByRole('status', { + name: label ? new RegExp(label, 'i') : undefined, + }) + + return toasts[0] + } catch (error) { + if (error instanceof Error && error.message.includes('Unable to find')) { + return null + } + + throw error + } } it('renders the toast', async () => { @@ -41,11 +49,11 @@ it('renders the toast', async () => { const contentId = screen.getByText(MESSAGE).getAttribute('id') expect(lastToast).toHaveAttribute('aria-describedby', contentId) - expect(within(lastToast).getByTestId('icon')).toHaveAttribute( + expect(within(lastToast!).getByTestId('icon')).toHaveAttribute( 'aria-hidden', 'true' ) - expect(within(lastToast).getByTestId('icon')).toHaveStyle({ + expect(within(lastToast!).getByTestId('icon')).toHaveStyle({ color: color('action', '500'), }) @@ -66,9 +74,9 @@ it('sets new props when `showToast` is called with new props', async () => { const lastToast = await getLastToast(expectedNewLabel) - expect(within(lastToast).getByText(expectedNewLabel)).toBeInTheDocument() - expect(within(lastToast).getByText(expectedNewMessage)).toBeInTheDocument() - expect(within(lastToast).getByTestId('icon')).toHaveStyle({ + expect(within(lastToast!).getByText(expectedNewLabel)).toBeInTheDocument() + expect(within(lastToast!).getByText(expectedNewMessage)).toBeInTheDocument() + expect(within(lastToast!).getByTestId('icon')).toHaveStyle({ color: color('danger', '500'), }) }) @@ -91,8 +99,8 @@ it('sets the message when the message is JSX', async () => { const lastToast = await getLastToast(expectedNewLabel) - expect(within(lastToast).getByText(expectedNewLabel)).toBeInTheDocument() - expect(within(lastToast).getAllByRole('paragraph')).toHaveLength(2) + expect(within(lastToast!).getByText(expectedNewLabel)).toBeInTheDocument() + expect(within(lastToast!).getAllByRole('paragraph')).toHaveLength(2) }) it('sets unique IDs when there are multiple toasts', async () => { @@ -121,3 +129,18 @@ it('sets an accessible name when there is no message', async () => { expect(toast).toHaveAccessibleName(new RegExp(label)) }) + +it('dismisses all toasts when `dismissToast` is called', async () => { + setup() + + showToast(MESSAGE) + showToast(MESSAGE) + + expect(screen.queryAllByRole('status').length).toBeGreaterThan(0) + + dismissToast() + + await waitFor(() => { + expect(screen.queryAllByRole('status')).toHaveLength(0) + }) +}) diff --git a/packages/react-component-library/src/components/Toast/Toast.tsx b/packages/react-component-library/src/components/Toast/Toast.tsx index 2ff6cbc603..930ec20b36 100644 --- a/packages/react-component-library/src/components/Toast/Toast.tsx +++ b/packages/react-component-library/src/components/Toast/Toast.tsx @@ -64,7 +64,7 @@ export const Toast = (props: ToastProps) => { const { dateTime, label, appearance = TOAST_APPEARANCE.INFO, ...rest } = props const { toasts, handlers } = useToaster() - const { startPause, endPause, updateHeight, calculateOffset } = handlers + const { startPause, endPause, updateHeight } = handlers const [time] = useState( (dateTime || new Date()).toLocaleTimeString('en-GB', { @@ -85,10 +85,12 @@ export const Toast = (props: ToastProps) => { onMouseLeave={endPause} style={{ position: 'fixed', - top: 0, - right: 0, - padding: spacing('4'), + top: spacing('4'), + right: spacing('4'), zIndex: zIndex('overlay', 999), + display: 'flex', + flexDirection: 'column', + gap: spacing('4'), }} > {toasts.map((item: HotToast & ToastProps) => { @@ -98,11 +100,6 @@ export const Toast = (props: ToastProps) => { const toastTitleId = `${titleId}-${item.id}` const toastDescriptionId = `${descriptionId}-${item.id}` - const offset = calculateOffset(item, { - reverseOrder: true, - gutter: 1, - }) - const ref = (el: HTMLDivElement) => { if (el && typeof height !== 'number') { updateHeight(id, el.getBoundingClientRect().height) @@ -115,7 +112,6 @@ export const Toast = (props: ToastProps) => { style={{ transition: 'all 0.5s ease-out', opacity: visible ? 1 : 0, - transform: `translateY(${offset - height}px)`, }} ref={ref} > @@ -183,3 +179,7 @@ export const showToast = ( }) } } + +export const dismissToast = (id?: string) => { + toast.dismiss(id) +} diff --git a/packages/react-component-library/src/components/Toast/partials/StyledToast.tsx b/packages/react-component-library/src/components/Toast/partials/StyledToast.tsx index ffb69478c5..f82de03a11 100644 --- a/packages/react-component-library/src/components/Toast/partials/StyledToast.tsx +++ b/packages/react-component-library/src/components/Toast/partials/StyledToast.tsx @@ -1,5 +1,5 @@ import styled, { css } from 'styled-components' -import { color, shadow, spacing } from '@royalnavy/design-tokens' +import { color, shadow } from '@royalnavy/design-tokens' import { StyledLabel } from './StyledLabel' import { Appearance } from '../Toast' @@ -22,7 +22,6 @@ export const StyledToast = styled.div` border: 1px solid ${color('neutral', '100')}; border-radius: 4px; width: 340px; - margin-bottom: ${spacing('6')}; background-color: ${color('neutral', 'white')}; ${({ $appearance }) => css`