From 8143a026cabda0d7581aab517b5bd077d4edd826 Mon Sep 17 00:00:00 2001 From: bapplejax Date: Fri, 20 Dec 2024 13:39:28 -0600 Subject: [PATCH 1/7] add-toast-component-to-extras: First push to add toast component. --- packages/comet-extras/src/components/index.ts | 1 + .../src/components/toast/index.ts | 1 + .../src/components/toast/toast.stories.tsx | 49 +++++ .../src/components/toast/toast.tsx | 171 ++++++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 packages/comet-extras/src/components/toast/index.ts create mode 100644 packages/comet-extras/src/components/toast/toast.stories.tsx create mode 100644 packages/comet-extras/src/components/toast/toast.tsx diff --git a/packages/comet-extras/src/components/index.ts b/packages/comet-extras/src/components/index.ts index 144b2e2e..97ebea28 100644 --- a/packages/comet-extras/src/components/index.ts +++ b/packages/comet-extras/src/components/index.ts @@ -2,3 +2,4 @@ export { default as DataTable } from './data-table'; export { default as Spinner } from './spinner'; export { default as Tabs, TabPanel } from './tabs'; export { default as Toggle } from './toggle'; +export { default as Toast } from './toast'; diff --git a/packages/comet-extras/src/components/toast/index.ts b/packages/comet-extras/src/components/toast/index.ts new file mode 100644 index 00000000..8fde7bbc --- /dev/null +++ b/packages/comet-extras/src/components/toast/index.ts @@ -0,0 +1 @@ +export { default } from './toast'; diff --git a/packages/comet-extras/src/components/toast/toast.stories.tsx b/packages/comet-extras/src/components/toast/toast.stories.tsx new file mode 100644 index 00000000..bbe46892 --- /dev/null +++ b/packages/comet-extras/src/components/toast/toast.stories.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import Toast, { ToastProps } from './toast'; + +export default { + title: 'Toast Notification', + component: Toast, + argTypes: { + id: { control: 'text' }, + message: { control: 'text' }, + duration: { control: 'number' }, + type: { control: 'text' }, + onClose: { action: 'close' }, + }, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Info = Template.bind({}); +Info.args = { + id: 'toast-info', + message: 'Toast info notification bar', + duration: 3000, + type: 'info', +}; + +export const Success = Template.bind({}); +Success.args = { + id: 'toast-success', + message: 'Toast success notification bar', + duration: 3000, + type: 'success', +}; + +export const Error = Template.bind({}); +Error.args = { + id: 'toast-error', + message: 'Toast error notification bar', + duration: 3000, + type: 'error', +}; + +export const Warning = Template.bind({}); +Warning.args = { + id: 'toast-warning', + message: 'Toast warning notification bar', + duration: 3000, + type: 'warning', +}; diff --git a/packages/comet-extras/src/components/toast/toast.tsx b/packages/comet-extras/src/components/toast/toast.tsx new file mode 100644 index 00000000..2c83f18f --- /dev/null +++ b/packages/comet-extras/src/components/toast/toast.tsx @@ -0,0 +1,171 @@ +import React, { useState, useEffect } from 'react'; + +// Props interface for the Toast component +export interface ToastProps { + /** + * The unique identifier for this component + */ + id: string; + /** + * The message to display in the toast + * */ + message: string; + /** + * Duration in milliseconds to show the toast. Set to 0 for no auto-dismiss + * */ + duration?: number; + /** + * The type of toast which determines its color scheme + * */ + type?: 'success' | 'error' | 'warning' | 'info'; + /** + * Callback function when toast is closed either manually or automatically + * */ + onClose?: () => void; +} + +const styles = { + toastContainer: { + position: 'fixed', + top: '20px', + right: '20px', + zIndex: 1000, + display: 'flex', + flexDirection: 'column', + gap: '10px', + }, + toast: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + minWidth: '300px', + maxWidth: '400px', + padding: '12px 16px', + borderRadius: '4px', + color: 'white', + boxShadow: '0 2px 5px rgba(0,0,0,0.2)', + animation: 'slideIn 0.3s ease-out forwards', + }, + leaving: { + animation: 'slideOut 0.3s ease-in forwards', + }, + closeButton: { + background: 'none', + border: 'none', + color: 'white', + cursor: 'pointer', + padding: '4px', + marginLeft: '12px', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'background-color 0.2s', + }, + message: { + margin: 0, + flexGrow: 1, + }, + '@keyframes slideIn': { + from: { + transform: 'translateX(100%)', + opacity: 0, + }, + to: { + transform: 'translateX(0)', + opacity: 1, + }, + }, + '@keyframes slideOut': { + from: { + transform: 'translateX(0)', + opacity: 1, + }, + to: { + transform: 'translateX(100%)', + opacity: 0, + }, + }, +}; + +const typeStyles = { + success: { background: '#4caf50' }, + error: { background: '#f44336' }, + warning: { background: '#ff9800' }, + info: { background: '#2196f3' }, +}; + +// Add keyframes to document +const styleSheet = document.createElement('style'); +styleSheet.textContent = ` + @keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + @keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } + } +`; +document.head.appendChild(styleSheet); + +export const Toast = ({ + id, + message = 'This is a toast notification', + duration = 3000, + type = 'info', + onClose = () => {}, +}: ToastProps): React.ReactElement => { + const [isVisible, setIsVisible] = useState(true); + const [isLeaving, setIsLeaving] = useState(false); + + const dismissToast = () => { + setIsLeaving(true); + setTimeout(() => { + setIsVisible(false); + onClose(); + }, 300); + }; + + useEffect(() => { + if (duration > 0) { + const timer = setTimeout(() => { + dismissToast(); + }, duration); + + return () => clearTimeout(timer); + } + }, [duration]); + + if (!isVisible) return <>; + + return ( +
+

{message}

+ +
+ ); +}; + +export default Toast; From 72ac159f881701c5483dbc95521d9dbf7c213db4 Mon Sep 17 00:00:00 2001 From: bapplejax Date: Fri, 20 Dec 2024 14:54:46 -0600 Subject: [PATCH 2/7] Refactoring to use stylesheet and uswds styles. --- .../src/components/toast/toast.stories.tsx | 16 ++- .../src/components/toast/toast.style.css | 87 ++++++++++++ .../src/components/toast/toast.tsx | 125 +++--------------- 3 files changed, 116 insertions(+), 112 deletions(-) create mode 100644 packages/comet-extras/src/components/toast/toast.style.css diff --git a/packages/comet-extras/src/components/toast/toast.stories.tsx b/packages/comet-extras/src/components/toast/toast.stories.tsx index bbe46892..5b8e167d 100644 --- a/packages/comet-extras/src/components/toast/toast.stories.tsx +++ b/packages/comet-extras/src/components/toast/toast.stories.tsx @@ -1,9 +1,8 @@ -import React from 'react'; import { Meta, StoryFn } from '@storybook/react'; import Toast, { ToastProps } from './toast'; -export default { - title: 'Toast Notification', +const meta: Meta = { + title: 'Extras/Toast', component: Toast, argTypes: { id: { control: 'text' }, @@ -12,7 +11,8 @@ export default { type: { control: 'text' }, onClose: { action: 'close' }, }, -} as Meta; +}; +export default meta; const Template: StoryFn = (args) => ; @@ -47,3 +47,11 @@ Warning.args = { duration: 3000, type: 'warning', }; + +export const Emergency = Template.bind({}); +Emergency.args = { + id: 'toast-emergency', + message: 'Toast emergency notification bar', + duration: 3000, + type: 'emergency', +}; diff --git a/packages/comet-extras/src/components/toast/toast.style.css b/packages/comet-extras/src/components/toast/toast.style.css new file mode 100644 index 00000000..c3bb678d --- /dev/null +++ b/packages/comet-extras/src/components/toast/toast.style.css @@ -0,0 +1,87 @@ +.toast { + border-left: 0.5rem solid #adadad; + display: flex; + align-items: center; + justify-content: space-between; + min-width: 300px; + max-width: 400px; + padding: 12px 16px; + color: #1b1b1b; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + animation: slideIn 0.3s ease-out forward; +} + +.toast--info { + background-color: #e7f6f8; + border-left-color: #00bde3; +} +.toast--warning { + background-color: #faf3d1; + border-left-color: #ffbe2e; +} +.toast--success { + background-color: #ecf3ec; + border-left-color: #00a91c; +} +.toast--error { + background-color: #f4e3db; + border-left-color: #d54309; +} +.toast--emergency { + background-color: #9c3d10; + border-left-color: #9c3d10; +} + +.toast--emergency *, +.toast--emergency .toast__close-button { + color: white; +} + +.toast__message { + margin: 0; + flex-grow: 1; +} + +.toast__close-button { + background: none; + border: none; + color: #1b1b1b; + cursor: pointer; + padding: 4px; + margin-left: 12px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s; +} + +.toast--isLeaving { + animation: slideOut 0.3s ease-in forwards; +} + +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.toast--isLeaving { + animation: slideOut 0.3s ease-in forwards; +} + +@keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} diff --git a/packages/comet-extras/src/components/toast/toast.tsx b/packages/comet-extras/src/components/toast/toast.tsx index 2c83f18f..123fba06 100644 --- a/packages/comet-extras/src/components/toast/toast.tsx +++ b/packages/comet-extras/src/components/toast/toast.tsx @@ -1,4 +1,6 @@ import React, { useState, useEffect } from 'react'; +import classnames from 'classnames'; +import './toast.style.css'; // Props interface for the Toast component export interface ToastProps { @@ -17,120 +19,30 @@ export interface ToastProps { /** * The type of toast which determines its color scheme * */ - type?: 'success' | 'error' | 'warning' | 'info'; + type?: 'success' | 'error' | 'warning' | 'info' | 'emergency'; /** * Callback function when toast is closed either manually or automatically * */ onClose?: () => void; + /** + * A custom class to apply to the component + */ + className?: string; } -const styles = { - toastContainer: { - position: 'fixed', - top: '20px', - right: '20px', - zIndex: 1000, - display: 'flex', - flexDirection: 'column', - gap: '10px', - }, - toast: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - minWidth: '300px', - maxWidth: '400px', - padding: '12px 16px', - borderRadius: '4px', - color: 'white', - boxShadow: '0 2px 5px rgba(0,0,0,0.2)', - animation: 'slideIn 0.3s ease-out forwards', - }, - leaving: { - animation: 'slideOut 0.3s ease-in forwards', - }, - closeButton: { - background: 'none', - border: 'none', - color: 'white', - cursor: 'pointer', - padding: '4px', - marginLeft: '12px', - borderRadius: '50%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - transition: 'background-color 0.2s', - }, - message: { - margin: 0, - flexGrow: 1, - }, - '@keyframes slideIn': { - from: { - transform: 'translateX(100%)', - opacity: 0, - }, - to: { - transform: 'translateX(0)', - opacity: 1, - }, - }, - '@keyframes slideOut': { - from: { - transform: 'translateX(0)', - opacity: 1, - }, - to: { - transform: 'translateX(100%)', - opacity: 0, - }, - }, -}; - -const typeStyles = { - success: { background: '#4caf50' }, - error: { background: '#f44336' }, - warning: { background: '#ff9800' }, - info: { background: '#2196f3' }, -}; - -// Add keyframes to document -const styleSheet = document.createElement('style'); -styleSheet.textContent = ` - @keyframes slideIn { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } - } - @keyframes slideOut { - from { - transform: translateX(0); - opacity: 1; - } - to { - transform: translateX(100%); - opacity: 0; - } - } -`; -document.head.appendChild(styleSheet); - export const Toast = ({ id, message = 'This is a toast notification', duration = 3000, type = 'info', + className = 'toast', onClose = () => {}, }: ToastProps): React.ReactElement => { const [isVisible, setIsVisible] = useState(true); const [isLeaving, setIsLeaving] = useState(false); + const classes = classnames(`toast--${type}`, className, `${isLeaving ? 'toast--isLeaving' : ''}`); + const dismissToast = () => { setIsLeaving(true); setTimeout(() => { @@ -152,16 +64,13 @@ export const Toast = ({ if (!isVisible) return <>; return ( -
-

{message}

-
From 5229c7979c9b836a48314ff6d668eede3c5b5cf9 Mon Sep 17 00:00:00 2001 From: bapplejax Date: Fri, 20 Dec 2024 15:23:32 -0600 Subject: [PATCH 3/7] Adding minimal testing --- .../src/components/toast/toast.test.tsx | 52 +++++++++++++++++++ .../src/components/toast/toast.tsx | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 packages/comet-extras/src/components/toast/toast.test.tsx diff --git a/packages/comet-extras/src/components/toast/toast.test.tsx b/packages/comet-extras/src/components/toast/toast.test.tsx new file mode 100644 index 00000000..f3290d9d --- /dev/null +++ b/packages/comet-extras/src/components/toast/toast.test.tsx @@ -0,0 +1,52 @@ +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; +import { Toast } from './toast'; // Assuming the Toast component is in the same directory + +describe('Toast Component Tests', () => { + test('should render with no accessibility violations', async () => { + const { container } = render( + , + ); + expect(await axe(container)).toHaveNoViolations(); + }); + + test('should render an info Toast notification', () => { + const { container } = render( + , + ); + expect(container.querySelector('#test-toast')).toBeTruthy(); + expect(container.querySelector('#test-toast')).toHaveClass('toast--info'); + }); + + test('should render a warning Toast notification', () => { + const { container } = render( + , + ); + expect(container.querySelector('#test-toast')).toBeTruthy(); + expect(container.querySelector('#test-toast')).toHaveClass('toast--warning'); + }); + + test('should render a success Toast notification', () => { + const { container } = render( + , + ); + expect(container.querySelector('#test-toast')).toBeTruthy(); + expect(container.querySelector('#test-toast')).toHaveClass('toast--success'); + }); + + test('should render an error Toast notification', () => { + const { container } = render( + , + ); + expect(container.querySelector('#test-toast')).toBeTruthy(); + expect(container.querySelector('#test-toast')).toHaveClass('toast--error'); + }); + + test('should render an emergency Toast notification', () => { + const { container } = render( + , + ); + expect(container.querySelector('#test-toast')).toBeTruthy(); + expect(container.querySelector('#test-toast')).toHaveClass('toast--emergency'); + }); +}); diff --git a/packages/comet-extras/src/components/toast/toast.tsx b/packages/comet-extras/src/components/toast/toast.tsx index 123fba06..513361e4 100644 --- a/packages/comet-extras/src/components/toast/toast.tsx +++ b/packages/comet-extras/src/components/toast/toast.tsx @@ -11,7 +11,7 @@ export interface ToastProps { /** * The message to display in the toast * */ - message: string; + message?: string; /** * Duration in milliseconds to show the toast. Set to 0 for no auto-dismiss * */ From 1bf5e453052814b355e072d7494bb41adf556484 Mon Sep 17 00:00:00 2001 From: bapplejax Date: Mon, 23 Dec 2024 14:37:44 -0600 Subject: [PATCH 4/7] Altering toast component and testing. --- .../src/components/toast/toast.stories.tsx | 92 +++++++++++-------- .../src/components/toast/toast.style.css | 8 +- .../src/components/toast/toast.test.tsx | 40 +++++++- .../src/components/toast/toast.tsx | 32 +++++-- 4 files changed, 123 insertions(+), 49 deletions(-) diff --git a/packages/comet-extras/src/components/toast/toast.stories.tsx b/packages/comet-extras/src/components/toast/toast.stories.tsx index 5b8e167d..ad7496ca 100644 --- a/packages/comet-extras/src/components/toast/toast.stories.tsx +++ b/packages/comet-extras/src/components/toast/toast.stories.tsx @@ -1,57 +1,73 @@ +import React, { useState } from 'react'; import { Meta, StoryFn } from '@storybook/react'; import Toast, { ToastProps } from './toast'; +import Button from '../../../../comet-uswds/src/components/button/button'; +import ButtonGroup from '../../../../comet-uswds/src/components/button-group/button-group'; const meta: Meta = { title: 'Extras/Toast', component: Toast, argTypes: { - id: { control: 'text' }, + id: { control: false }, message: { control: 'text' }, duration: { control: 'number' }, - type: { control: 'text' }, + type: { control: false }, onClose: { action: 'close' }, + allowClose: { control: 'boolean' }, + className: { control: false }, }, }; export default meta; -const Template: StoryFn = (args) => ; +const Template: StoryFn = (args: ToastProps) => { + const [toasts, setToasts] = useState([]); -export const Info = Template.bind({}); -Info.args = { - id: 'toast-info', - message: 'Toast info notification bar', - duration: 3000, - type: 'info', -}; + const addToast = (type: string) => { + const newToast = { + key: args.id, + id: `toast-${type}`, + message: `${!args.message ? 'Default toast notification for ' + type : args.message}`, + type: `${type}`, + duration: `${!args.duration ? 3000 : args.duration}`, + allowClose: args.allowClose, + }; + setToasts((prev) => [...prev, newToast]); + }; -export const Success = Template.bind({}); -Success.args = { - id: 'toast-success', - message: 'Toast success notification bar', - duration: 3000, - type: 'success', -}; + return ( +
+ + + + + + + -export const Error = Template.bind({}); -Error.args = { - id: 'toast-error', - message: 'Toast error notification bar', - duration: 3000, - type: 'error', +
+ {toasts.map((toast) => ( + + ))} +
+
+ ); }; -export const Warning = Template.bind({}); -Warning.args = { - id: 'toast-warning', - message: 'Toast warning notification bar', - duration: 3000, - type: 'warning', -}; - -export const Emergency = Template.bind({}); -Emergency.args = { - id: 'toast-emergency', - message: 'Toast emergency notification bar', - duration: 3000, - type: 'emergency', -}; +export const Standard = Template.bind({}); diff --git a/packages/comet-extras/src/components/toast/toast.style.css b/packages/comet-extras/src/components/toast/toast.style.css index c3bb678d..e524ea69 100644 --- a/packages/comet-extras/src/components/toast/toast.style.css +++ b/packages/comet-extras/src/components/toast/toast.style.css @@ -8,7 +8,13 @@ padding: 12px 16px; color: #1b1b1b; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); - animation: slideIn 0.3s ease-out forward; + animation: slideIn 0.3s ease-out forwards; + position: fixed; +} + +.toast--bottom-right { + bottom: 20px; + right: 20px; } .toast--info { diff --git a/packages/comet-extras/src/components/toast/toast.test.tsx b/packages/comet-extras/src/components/toast/toast.test.tsx index f3290d9d..f288c09d 100644 --- a/packages/comet-extras/src/components/toast/toast.test.tsx +++ b/packages/comet-extras/src/components/toast/toast.test.tsx @@ -1,4 +1,5 @@ -import { render } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, test, expect, vi } from 'vitest'; import { axe } from 'jest-axe'; import { Toast } from './toast'; // Assuming the Toast component is in the same directory @@ -49,4 +50,41 @@ describe('Toast Component Tests', () => { expect(container.querySelector('#test-toast')).toBeTruthy(); expect(container.querySelector('#test-toast')).toHaveClass('toast--emergency'); }); + + test('toast closes automatically after duration', () => { + vi.useFakeTimers(); + const handleClose = vi.fn(); + const duration = 3000; + + render( + , + ); + + // Advance timers by duration plus animation time + vi.advanceTimersByTime(duration + 300); + + expect(handleClose).toHaveBeenCalled(); + vi.useRealTimers(); + }); + + test('clicking close button triggers onClose', async () => { + vi.useFakeTimers(); + const handleClose = vi.fn(); + + render(); + + const closeButton = screen.getByRole('button'); + await fireEvent.click(closeButton); + + // Wait for the animation timeout (300ms in the component) + vi.advanceTimersByTime(300); + + expect(handleClose).toHaveBeenCalled(); + vi.useRealTimers(); + }); }); diff --git a/packages/comet-extras/src/components/toast/toast.tsx b/packages/comet-extras/src/components/toast/toast.tsx index 513361e4..8897f471 100644 --- a/packages/comet-extras/src/components/toast/toast.tsx +++ b/packages/comet-extras/src/components/toast/toast.tsx @@ -28,6 +28,10 @@ export interface ToastProps { * A custom class to apply to the component */ className?: string; + /** + * Whether or not to display the close button + */ + allowClose?: boolean; } export const Toast = ({ @@ -35,13 +39,20 @@ export const Toast = ({ message = 'This is a toast notification', duration = 3000, type = 'info', - className = 'toast', + className = '', onClose = () => {}, + allowClose = true, }: ToastProps): React.ReactElement => { const [isVisible, setIsVisible] = useState(true); const [isLeaving, setIsLeaving] = useState(false); - const classes = classnames(`toast--${type}`, className, `${isLeaving ? 'toast--isLeaving' : ''}`); + const classes = classnames( + 'toast', + `toast--${type}`, + 'toast--bottom-right', + className, + `${isLeaving ? 'toast--isLeaving' : ''}`, + ); const dismissToast = () => { setIsLeaving(true); @@ -66,13 +77,16 @@ export const Toast = ({ return (

{message}

- + {allowClose && ( + + )}
); }; From 593da2cbdf1a3bedafa77ec35feb7b0bc5216e71 Mon Sep 17 00:00:00 2001 From: bapplejax Date: Tue, 24 Dec 2024 09:52:05 -0600 Subject: [PATCH 5/7] Updating stories, default style, and testing. --- .../src/components/toast/toast.stories.tsx | 42 ++++++++----------- .../src/components/toast/toast.style.css | 5 +-- .../src/components/toast/toast.test.tsx | 33 +++++---------- .../src/components/toast/toast.tsx | 1 - 4 files changed, 29 insertions(+), 52 deletions(-) diff --git a/packages/comet-extras/src/components/toast/toast.stories.tsx b/packages/comet-extras/src/components/toast/toast.stories.tsx index ad7496ca..ad89b992 100644 --- a/packages/comet-extras/src/components/toast/toast.stories.tsx +++ b/packages/comet-extras/src/components/toast/toast.stories.tsx @@ -2,16 +2,15 @@ import React, { useState } from 'react'; import { Meta, StoryFn } from '@storybook/react'; import Toast, { ToastProps } from './toast'; import Button from '../../../../comet-uswds/src/components/button/button'; -import ButtonGroup from '../../../../comet-uswds/src/components/button-group/button-group'; const meta: Meta = { title: 'Extras/Toast', component: Toast, argTypes: { - id: { control: false }, + id: { control: 'text' }, message: { control: 'text' }, duration: { control: 'number' }, - type: { control: false }, + type: { control: 'select', required: true }, onClose: { action: 'close' }, allowClose: { control: 'boolean' }, className: { control: false }, @@ -22,12 +21,12 @@ export default meta; const Template: StoryFn = (args: ToastProps) => { const [toasts, setToasts] = useState([]); - const addToast = (type: string) => { + const addToast = () => { const newToast = { key: args.id, - id: `toast-${type}`, - message: `${!args.message ? 'Default toast notification for ' + type : args.message}`, - type: `${type}`, + id: `toast-${args.type}`, + message: `${!args.message ? 'Default toast notification for ' + args.type : args.message}`, + type: `${args.type}`, duration: `${!args.duration ? 3000 : args.duration}`, allowClose: args.allowClose, }; @@ -36,23 +35,9 @@ const Template: StoryFn = (args: ToastProps) => { return (
- - - - - - - +
{toasts.map((toast) => ( @@ -70,4 +55,11 @@ const Template: StoryFn = (args: ToastProps) => { ); }; -export const Standard = Template.bind({}); +export const Default = Template.bind({}); +Default.args = { + id: 'toast-info', + message: 'This is a toast notification', + type: 'info', + duration: 3000, + allowClose: true, +}; diff --git a/packages/comet-extras/src/components/toast/toast.style.css b/packages/comet-extras/src/components/toast/toast.style.css index e524ea69..a13ee888 100644 --- a/packages/comet-extras/src/components/toast/toast.style.css +++ b/packages/comet-extras/src/components/toast/toast.style.css @@ -10,10 +10,7 @@ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease-out forwards; position: fixed; -} - -.toast--bottom-right { - bottom: 20px; + top: 20px; right: 20px; } diff --git a/packages/comet-extras/src/components/toast/toast.test.tsx b/packages/comet-extras/src/components/toast/toast.test.tsx index f288c09d..157906da 100644 --- a/packages/comet-extras/src/components/toast/toast.test.tsx +++ b/packages/comet-extras/src/components/toast/toast.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, test, expect, vi } from 'vitest'; import { axe } from 'jest-axe'; import { Toast } from './toast'; // Assuming the Toast component is in the same directory @@ -51,27 +51,6 @@ describe('Toast Component Tests', () => { expect(container.querySelector('#test-toast')).toHaveClass('toast--emergency'); }); - test('toast closes automatically after duration', () => { - vi.useFakeTimers(); - const handleClose = vi.fn(); - const duration = 3000; - - render( - , - ); - - // Advance timers by duration plus animation time - vi.advanceTimersByTime(duration + 300); - - expect(handleClose).toHaveBeenCalled(); - vi.useRealTimers(); - }); - test('clicking close button triggers onClose', async () => { vi.useFakeTimers(); const handleClose = vi.fn(); @@ -87,4 +66,14 @@ describe('Toast Component Tests', () => { expect(handleClose).toHaveBeenCalled(); vi.useRealTimers(); }); + + test('should wait for toast to disappear', async () => { + const { container } = render( + , + ); + expect(container.querySelector('#test-toast')).toBeTruthy(); + await waitFor(async () => { + expect(container.querySelector('#test-toast')).not.toBeTruthy(); + }); + }); }); diff --git a/packages/comet-extras/src/components/toast/toast.tsx b/packages/comet-extras/src/components/toast/toast.tsx index 8897f471..1e78951e 100644 --- a/packages/comet-extras/src/components/toast/toast.tsx +++ b/packages/comet-extras/src/components/toast/toast.tsx @@ -49,7 +49,6 @@ export const Toast = ({ const classes = classnames( 'toast', `toast--${type}`, - 'toast--bottom-right', className, `${isLeaving ? 'toast--isLeaving' : ''}`, ); From 6efa4372bf27855c9eba60ca938177af779f4ef7 Mon Sep 17 00:00:00 2001 From: bapplejax Date: Fri, 27 Dec 2024 12:49:40 -0600 Subject: [PATCH 6/7] Adding additional storybook helpers. --- .../comet-extras/src/components/toast/toast.stories.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/comet-extras/src/components/toast/toast.stories.tsx b/packages/comet-extras/src/components/toast/toast.stories.tsx index ad89b992..25c47b24 100644 --- a/packages/comet-extras/src/components/toast/toast.stories.tsx +++ b/packages/comet-extras/src/components/toast/toast.stories.tsx @@ -15,6 +15,13 @@ const meta: Meta = { allowClose: { control: 'boolean' }, className: { control: false }, }, + parameters: { + docs: { + source: { + type: 'code', + }, + }, + }, }; export default meta; From beb57628a237aeef0f3887eecd7f53329b305e28 Mon Sep 17 00:00:00 2001 From: bapplejax Date: Fri, 27 Dec 2024 12:51:11 -0600 Subject: [PATCH 7/7] Switching to dot notation. --- .../src/components/toast/toast.stories.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/comet-extras/src/components/toast/toast.stories.tsx b/packages/comet-extras/src/components/toast/toast.stories.tsx index 25c47b24..ce09e397 100644 --- a/packages/comet-extras/src/components/toast/toast.stories.tsx +++ b/packages/comet-extras/src/components/toast/toast.stories.tsx @@ -49,12 +49,12 @@ const Template: StoryFn = (args: ToastProps) => {
{toasts.map((toast) => ( ))}