diff --git a/lib/components/Toast/Toast.stories.ts b/lib/components/Toast/Toast.stories.ts new file mode 100644 index 0000000..5dfe173 --- /dev/null +++ b/lib/components/Toast/Toast.stories.ts @@ -0,0 +1,93 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Toast } from './Toast'; + +const meta = { + title: 'Snackbars/Toast', + component: Toast, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +const url = 'https://www.figma.com/file/6M2LrpSCcB0thlFDaQAI2J/cx_jod_client?node-id=542%3A8376'; +const text = 'Lorem ipsum dolor'; + +export const Success: Story = { + parameters: { + design: { + type: 'figma', + url, + }, + docs: { + description: { + story: 'This is a success toast component for displaying a text.', + }, + }, + }, + args: { + text, + icon: 'check_circle', + iconAriaLabel: 'Check Circle', + }, +}; + +export const Warning: Story = { + parameters: { + design: { + type: 'figma', + url, + }, + docs: { + description: { + story: 'This is a warning toast component for displaying a text.', + }, + }, + }, + args: { + text, + icon: 'warning', + iconAriaLabel: 'Warning', + variant: 'warning', + }, +}; + +export const ErrorStory: Story = { + name: 'Error', + parameters: { + design: { + type: 'figma', + url, + }, + docs: { + description: { + story: 'This is an error toast component for displaying a text.', + }, + }, + }, + args: { + text, + icon: 'error', + iconAriaLabel: 'Error', + variant: 'error', + }, +}; + +export const Iconless: Story = { + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/6M2LrpSCcB0thlFDaQAI2J/cx_jod_client?node-id=542%3A8376', + }, + docs: { + description: { + story: 'This is a toast component for displaying a text without an icon.', + }, + }, + }, + args: { + text: 'Lorem ipsum dolor', + }, +}; diff --git a/lib/components/Toast/Toast.test.tsx b/lib/components/Toast/Toast.test.tsx new file mode 100644 index 0000000..2e68a38 --- /dev/null +++ b/lib/components/Toast/Toast.test.tsx @@ -0,0 +1,30 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { render, screen, cleanup } from '@testing-library/react'; +import '@testing-library/jest-dom/vitest'; + +import { Toast } from './Toast'; + +afterEach(() => { + cleanup(); +}); + +describe('Toast', () => { + it('renders the toast component with text', () => { + const { container } = render(); + const toastElement = screen.getByRole('alert'); + expect(toastElement).toBeInTheDocument(); + expect(toastElement).toHaveTextContent('This is a toast message'); + expect(container.firstChild).toMatchSnapshot(); + }); + + it('renders the toast component with icon', () => { + const { container } = render(); + const toastElement = screen.getByRole('alert'); + const iconElement = screen.getByLabelText('Mood'); + expect(toastElement).toBeInTheDocument(); + expect(toastElement).toHaveTextContent('This is a toast message'); + expect(iconElement).toBeInTheDocument(); + expect(iconElement).toHaveTextContent('mood'); + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/lib/components/Toast/Toast.tsx b/lib/components/Toast/Toast.tsx new file mode 100644 index 0000000..382e126 --- /dev/null +++ b/lib/components/Toast/Toast.tsx @@ -0,0 +1,36 @@ +export interface ToastProps { + /** Text shown on the toast */ + text: string; + /** Icon shown on the toast */ + icon?: string; + /** Aria label for the icon */ + iconAriaLabel?: string; + /** Variant of the toast */ + variant?: 'success' | 'warning' | 'error'; +} + +/** + * Toast component for displaying a text. + */ +export const Toast = ({ text, icon, iconAriaLabel, variant = 'success' }: ToastProps) => ( +
+ {icon && ( + + {icon} + + )} + {text} +
+); diff --git a/lib/components/Toast/__snapshots__/Toast.test.tsx.snap b/lib/components/Toast/__snapshots__/Toast.test.tsx.snap new file mode 100644 index 0000000..fb9e717 --- /dev/null +++ b/lib/components/Toast/__snapshots__/Toast.test.tsx.snap @@ -0,0 +1,30 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Toast > renders the toast component with icon 1`] = ` + +`; + +exports[`Toast > renders the toast component with text 1`] = ` + +`; diff --git a/lib/index.css b/lib/index.css index b5c61c9..b8c1c77 100644 --- a/lib/index.css +++ b/lib/index.css @@ -1,3 +1,5 @@ +@import 'symbols.css'; + @tailwind base; @tailwind components; @tailwind utilities; diff --git a/lib/symbols.css b/lib/symbols.css new file mode 100644 index 0000000..b4d0887 --- /dev/null +++ b/lib/symbols.css @@ -0,0 +1,45 @@ +/* From https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200 */ + +/* fallback */ +@font-face { + font-family: 'Material Symbols Outlined'; + font-style: normal; + font-weight: 100 700; + src: url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v170/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsLjBuVY.woff2) + format('woff2'); +} + +.material-symbols-outlined { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + direction: ltr; + display: inline-block; + font-family: 'Material Symbols Outlined'; + font-feature-settings: 'liga'; + font-size: 24px; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-height: 1; + text-transform: none; + white-space: nowrap; + word-wrap: normal; +} + +/* Recommended icon sizes */ +span.size-20 { + font-size: 20px; + font-variation-settings: 'OPSZ' 20; +} +span.size-24 { + font-size: 24px; + font-variation-settings: 'OPSZ' 24; +} +span.size-40 { + font-size: 40px; + font-variation-settings: 'OPSZ' 40; +} +span.size-48 { + font-size: 48px; + font-variation-settings: 'OPSZ' 48; +} diff --git a/tailwind.config.js b/tailwind.config.js index 1f666bf..bf26d00 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -35,7 +35,7 @@ export default { 'heading-5': ['16px', { fontWeight: '700', lineHeight: '110%' }], // Headline 5 'body-lg': ['18px', { fontWeight: '500', lineHeight: '140%' }], // Body L 'body-md': ['16px', { fontWeight: '500', lineHeight: '140%' }], // Body M - 'body-sm': ['14px', { fontWeight: '700', lineHeight: '140%' }], // Body S + 'body-sm': ['14px', { fontWeight: '500', lineHeight: '140%' }], // Body S 'body-xs': ['12px', { fontWeight: '700', lineHeight: '140%' }], // Body XS 'form-label': ['14px', { fontWeight: '700', lineHeight: '110%' }], // Form label help: ['12px', { fontWeight: '500', lineHeight: '110%' }], // Help, hint