Skip to content

Commit

Permalink
feat: add typings for <Toast>
Browse files Browse the repository at this point in the history
  • Loading branch information
PKulkoRaccoonGang committed Feb 18, 2025
1 parent b0187dd commit fd9fdb1
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 90 deletions.
8 changes: 4 additions & 4 deletions src/Toast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ components:
- Toast
categories:
- Overlays
status: 'New'
status: 'Stable'
designStatus: 'Done'
devStatus: 'Done'
notes: ''
Expand Down Expand Up @@ -39,7 +39,7 @@ notes: ''
Example of a basic Toast.
</Toast>

<Button variant="primary" onClick={() => setShow(true)}>Show Toast</Button>
<Button onClick={() => setShow(true)}>Show Toast</Button>
</>
);
}
Expand All @@ -64,7 +64,7 @@ notes: ''
Success! Example of a Toast with a button.
</Toast>

<Button variant="primary" onClick={() => setShow(true)}>Show Toast</Button>
<Button onClick={() => setShow(true)}>Show Toast</Button>
</>
);
}
Expand All @@ -89,7 +89,7 @@ notes: ''
Success! Example of a Toast with a link.
</Toast>

<Button variant="primary" onClick={() => setShow(true)}>Show Toast</Button>
<Button onClick={() => setShow(true)}>Show Toast</Button>
</>
);
}
Expand Down
48 changes: 15 additions & 33 deletions src/Toast/Toast.test.jsx → src/Toast/Toast.test.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import userEvent from '@testing-library/user-event';

import Toast from '.';

/* eslint-disable-next-line react/prop-types */
function ToastWrapper({ children, ...props }) {
function ToastWrapper({ children, ...props }: React.ComponentProps<typeof Toast>) {
return (
<IntlProvider locale="en">
<Toast {...props}>
{children}
</Toast>
<Toast {...props}>{children}</Toast>
</IntlProvider>
);
}

describe('<Toast />', () => {
const onCloseHandler = () => {};
const onCloseHandler = jest.fn();
const props = {
onClose: onCloseHandler,
show: true,
};

it('renders optional action as link', () => {
render(
<ToastWrapper
Expand All @@ -34,17 +30,17 @@ describe('<Toast />', () => {
Success message.
</ToastWrapper>,
);

const toastLink = screen.getByRole('button', { name: 'Optional action' });
expect(toastLink).toBeTruthy();
});

it('renders optional action as button', () => {
render(
<ToastWrapper
{...props}
action={{
label: 'Optional action',
onClick: () => {},
onClick: jest.fn(),
}}
>
Success message.
Expand All @@ -53,40 +49,26 @@ describe('<Toast />', () => {
const toastButton = screen.getByRole('button', { name: 'Optional action' });
expect(toastButton.className).toContain('btn');
});

it('autohide is set to false on onMouseOver and true on onMouseLeave', async () => {
render(
<ToastWrapper data-testid="toast" {...props}>
Success message.
</ToastWrapper>,
);
const toast = screen.getByTestId('toast');
render(<ToastWrapper {...props}>Success message.</ToastWrapper>);
const toast = screen.getByRole('alert');
await userEvent.hover(toast);
setTimeout(() => {
expect(screen.getByText('Success message.')).toEqual(true);
expect(toast).toHaveLength(1);
}, 6000);
expect(screen.getByText('Success message.')).toBeTruthy();
await userEvent.unhover(toast);
setTimeout(() => {
expect(screen.getByText('Success message.')).toEqual(false);
expect(toast).toHaveLength(1);
}, 6000);
expect(screen.getByText('Success message.')).toBeTruthy();
});

it('autohide is set to false onFocus and true onBlur', async () => {
render(
<ToastWrapper data-testid="toast" {...props}>
Success message.
</ToastWrapper>,
);
const toast = screen.getByTestId('toast');
const toast = screen.getByRole('alert');
toast.focus();
setTimeout(() => {
expect(screen.getByText('Success message.')).toEqual(true);
expect(toast).toHaveLength(1);
}, 6000);
expect(screen.getByText('Success message.')).toBeTruthy();
await userEvent.tab();
setTimeout(() => {
expect(screen.getByText('Success message.')).toEqual(false);
expect(toast).toHaveLength(1);
}, 6000);
expect(screen.getByText('Success message.')).toBeTruthy();
});
});
40 changes: 0 additions & 40 deletions src/Toast/ToastContainer.jsx

This file was deleted.

3 changes: 2 additions & 1 deletion src/Toast/ToastContainer.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use "sass:map";
@import "variables";

.toast-container {
Expand All @@ -11,7 +12,7 @@
left: 0;
}

@media only screen and (width <= 768px) {
@media (max-width: map.get($grid-breakpoints, "md")) {
bottom: $toast-container-gutter-sm;
right: $toast-container-gutter-sm;
left: $toast-container-gutter-sm;
Expand Down
32 changes: 32 additions & 0 deletions src/Toast/ToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ReactNode, useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

interface ToastContainerProps {
children: ReactNode;
}

const TOAST_ROOT_ID = 'toast-root';

function ToastContainer({ children }: ToastContainerProps) {
const [rootElement, setRootElement] = useState<HTMLElement | null>(null);

useEffect(() => {
if (typeof document !== 'undefined') {
let existingElement = document.getElementById(TOAST_ROOT_ID);

if (!existingElement) {
existingElement = document.createElement('div');
existingElement.id = TOAST_ROOT_ID;
existingElement.className = 'toast-container';
existingElement.setAttribute('aria-live', 'polite');
existingElement.setAttribute('aria-atomic', 'true');
document.body.appendChild(existingElement);
}
setRootElement(existingElement);
}
}, []);

return rootElement ? ReactDOM.createPortal(children, rootElement) : null;
}

export default ToastContainer;
9 changes: 5 additions & 4 deletions src/Toast/index.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
@use "sass:map";
@import "variables";
@import "~bootstrap/scss/toasts";

.toast {
background-color: $toast-background-color;
box-shadow: $toast-box-shadow;
margin: 0;
padding: 1rem;
padding: $spacer;
position: relative;
border-radius: $toast-border-radius;
z-index: 2;
Expand Down Expand Up @@ -38,15 +39,15 @@
}

& + .btn {
margin-top: 1rem;
margin-top: $spacer;
}
}

@media only screen and (width <= 768px) {
@media (max-width: map.get($grid-breakpoints, "md")) {
max-width: 100%;
}

@media only screen and (width >= 768px) {
@media (min-width: map.get($grid-breakpoints, "md")) {
min-width: $toast-max-width;
max-width: $toast-max-width;
}
Expand Down
37 changes: 29 additions & 8 deletions src/Toast/index.jsx → src/Toast/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';

import BaseToast from 'react-bootstrap/Toast';
import classNames from 'classnames';
import { useIntl } from 'react-intl';
import BaseToast from 'react-bootstrap/Toast';

import { Close } from '../../icons';
import ToastContainer from './ToastContainer';
Expand All @@ -14,16 +13,40 @@ import IconButton from '../IconButton';
export const TOAST_CLOSE_LABEL_TEXT = 'Close';
export const TOAST_DELAY = 5000;

interface ToastAction {
label: string;
href?: string;
onClick?: () => void;
}

interface ToastProps {
children: string;
onClose: () => void;
show: boolean;
action?: ToastAction;
closeLabel?: string;
delay?: number;
className?: string;
}

function Toast({
action, children, className, closeLabel, onClose, show, ...rest
}) {
action,
children,
className,
closeLabel,
onClose,
show,
...rest
}: ToastProps) {
const intl = useIntl();
const [autoHide, setAutoHide] = useState(true);

const intlCloseLabel = closeLabel || intl.formatMessage({
id: 'pgn.Toast.closeLabel',
defaultMessage: 'Close',
description: 'Close label for Toast component',
});

return (
<ToastContainer>
<BaseToast
Expand All @@ -37,9 +60,7 @@ function Toast({
show={show}
{...rest}
>
<div
className="toast-header"
>
<div className="toast-header">
<p className="small">{children}</p>
<div className="toast-header-btn-container">
<IconButton
Expand Down

0 comments on commit fd9fdb1

Please sign in to comment.