import { StoryObj, Meta, StoryFn } from "@storybook/react-vite"; import { within, expect, userEvent, fn } from "storybook/test"; import Alert from "./alert"; import WithInstructions from "#decorators/instructions.jsx"; import React from "react"; const ButtonDecorator = (Story: StoryFn) => { const [isOpen, setIsOpen] = React.useState(false); return (
); }; const instructions = ( <>

We are testing the following interactions on the alert:

); const meta: Meta = { title: "FP.React Components/Alert", component: Alert, tags: ["beta"], parameters: { docs: { description: { component: `The Alert component is used to display important messages to users with support for multiple severity levels, variants, and accessibility features. ## CSS Variables Customize the Alert appearance using CSS custom properties: ### Layout & Structure - \`--alert-bg\`: Background color (default: \`whitesmoke\`) - \`--alert-color\`: Text color (default: \`currentColor\`) - \`--alert-border\`: Border style (default: \`thin solid currentColor\`) - \`--alert-radius\`: Border radius (default: \`var(--border-radius)\`) - \`--alert-padding\`: Internal padding (default: \`var(--spc-4)\`) - \`--alert-margin-block-end\`: Bottom margin (default: none) - \`--alert-gap\`: Gap between icon and content (default: \`var(--spc-4)\`) ### Severity Colors (Success) - \`--alert-success-bg\`: Success background (default: \`#e6f4ea\`) - \`--alert-success-border\`: Success border (default: \`#34a853\`) - \`--alert-success-text\`: Success text (default: \`#1e4620\`) ### Severity Colors (Error) - \`--alert-error-bg\`: Error background (default: \`#fdeded\`) - \`--alert-error-border\`: Error border (default: \`#d32f2f\`) - \`--alert-error-text\`: Error text (default: \`#5f2120\`) ### Severity Colors (Warning) - \`--alert-warning-bg\`: Warning background (default: \`#fff4e5\`) - \`--alert-warning-border\`: Warning border (default: \`#ff9800\`) - \`--alert-warning-text\`: Warning text (default: \`#663c00\`) ### Severity Colors (Info) - \`--alert-info-bg\`: Info background (default: \`#e5f6fd\`) - \`--alert-info-border\`: Info border (default: \`#0288d1\`) - \`--alert-info-text\`: Info text (default: \`#084154\`) ### Typography - \`--alert-title-fw\`: Title font weight (default: \`600\`) - \`--alert-title-fs\`: Title font size (default: \`inherit\`) ### Animation - \`--alert-transition-duration\`: Animation timing (default: \`0.3s\`) **Example:** \`\`\`css [role="alert"] { --alert-radius: 0.5rem; --alert-padding: 1.5rem; --alert-gap: 1rem; --alert-title-fw: 700; --alert-success-bg: #d4edda; --alert-success-border: #28a745; } \`\`\``, }, }, }, args: { title: "Alert Title", children: "The Alert component is used to display important messages to users.", dismissible: true, onDismiss: () => fn(), }, } as Meta; export default meta; type Story = StoryObj; export const DefaultAlert: Story = { args: { open: true, // Override meta.args.children so the assertion below has a stable // string to match rather than inheriting the long default description. children: "This is an alert message", }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByText(/this is an alert message/i)).toBeInTheDocument(); }, } as Story; export const SuccessAlert: Story = { args: { open: true, severity: "success", }, } as Story; export const WarningAlert: Story = { args: { open: true, severity: "warning", }, } as Story; export const ErrorAlert: Story = { args: { open: true, severity: "error", }, } as Story; export const InfoAlert: Story = { args: { open: true, severity: "info", }, } as Story; export const OpenAlert: Story = { decorators: [ButtonDecorator], } as Story; export const InteractionTest: Story = { args: { open: true, className: "my alert", severity: "info", }, decorators: [WithInstructions(instructions, "Alert interactions test:")], play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); const alert = canvas.getByRole("alert"); const dismissButton = canvas.getByRole("button", { name: /close alert/i }); await step( "Check that the alert and button are in the document", async () => { expect(alert).toBeInTheDocument(); expect(dismissButton).toBeInTheDocument(); } ); await step( "Tab through the alert and check if the button gets focused", async () => { await userEvent.tab({ delay: 500 }); expect(dismissButton).toHaveFocus(); } ); await step("Click the button to dismiss the alert", async () => { await userEvent.click(dismissButton, { delay: 500 }); expect(alert).not.toBeInTheDocument(); }); }, } as Story; export const AutoDismissAlert: Story = { args: { open: true, severity: "success", autoHideDuration: 5000, title: "Auto-Dismiss Alert", children: "This alert will automatically dismiss after 5 seconds", }, parameters: { docs: { description: { story: "Alert with automatic dismissal after 5 seconds. Useful for temporary success messages.", }, }, }, } as Story; export const WithActions: Story = { args: { open: true, severity: "warning", title: "Unsaved Changes", children: "You have unsaved changes. What would you like to do?", actions: ( <> ), }, parameters: { docs: { description: { story: "Alert with custom action buttons for user interactions.", }, }, }, } as Story; export const FilledVariant: Story = { args: { open: true, severity: "error", variant: "filled", title: "Error", children: "An error occurred while processing your request.", dismissible: true, }, parameters: { docs: { description: { story: "Alert with filled variant - solid colored background for high emphasis.", }, }, }, } as Story; export const SoftVariant: Story = { args: { open: true, severity: "info", variant: "soft", title: "Information", children: "This is an informational message with soft variant styling.", dismissible: true, }, parameters: { docs: { description: { story: "Alert with soft variant - subtle background without border.", }, }, }, } as Story; export const AutoFocusAlert: Story = { args: { open: true, severity: "error", autoFocus: true, title: "Critical Error", children: "This is a critical alert that automatically receives focus for screen readers.", dismissible: true, }, parameters: { docs: { description: { story: "Alert with autoFocus enabled - useful for critical alerts that need immediate attention.", }, }, }, } as Story; export const WithHeadingLevel: Story = { args: { open: true, severity: "info", title: "Section Alert", titleLevel: 2, children: "This alert uses an h2 heading for proper document structure. Use titleLevel to maintain heading hierarchy.", dismissible: true, }, parameters: { docs: { description: { story: "Alert with configurable heading level (WCAG 1.3.1). Use titleLevel prop (2-6) to maintain proper heading hierarchy in your page structure.", }, }, }, } as Story; export const WithPauseOnHover: Story = { args: { open: true, severity: "success", autoHideDuration: 5000, pauseOnHover: true, title: "Hover to Pause", children: "This alert will pause auto-dismiss when you hover over it or focus on it (WCAG 2.2.1).", dismissible: true, }, parameters: { docs: { description: { story: "Alert with pause on hover/focus enabled. Hovering over or focusing the alert pauses the auto-dismiss timer, allowing users more time to read the content.", }, }, }, } as Story; export const CustomIconSizes: Story = { render: () => (
Compact alert with smaller icon, useful for dense layouts or secondary messages. Standard alert with default icon size, balanced for most use cases. Prominent alert with larger icon for high-priority messages or larger viewports. Eye-catching alert with extra-large icon for critical success confirmations.
), parameters: { docs: { description: { story: "Demonstration of customizable icon sizes. Use the `iconSize` prop to adjust the severity icon size based on your design needs and context.", }, }, }, } as Story; export const AccessibilityShowcase: Story = { render: () => (
Screen readers will announce "Error:" before the message. This alert is automatically focused. This alert auto-dismisses in 8 seconds but pauses when hovered or focused. All dismiss buttons meet 44×44px touch target size (WCAG 2.5.5).
), parameters: { docs: { description: { story: "Showcase of WCAG 2.1 Level AA accessibility features: screen reader announcements, configurable heading levels, pause on interaction, proper focus indicators, and adequate touch targets.", }, }, }, } as Story; export const SimpleTextContent: Story = { args: { open: true, severity: "info", title: "Simple Text Alert", children: "This is a simple text message that will be wrapped in a paragraph tag automatically.", dismissible: true, }, parameters: { docs: { description: { story: "Default content type ('text') automatically wraps simple text content in a paragraph tag for proper semantic HTML.", }, }, }, } as Story; export const ComplexContentWithList: Story = { args: { open: true, severity: "warning", title: "Action Required", contentType: "node", children: ( <>

Please complete the following steps to secure your account:

), dismissible: true, }, parameters: { docs: { description: { story: "Using contentType='node' allows complex content structures like lists, multiple paragraphs, or custom layouts without automatic paragraph wrapping.", }, }, }, } as Story; export const CustomLayoutContent: Story = { args: { open: true, severity: "success", title: "Deployment Complete", contentType: "node", children: (

Your application has been successfully deployed to production.

Build #: 1234
Duration: 2m 34s
Environment: Production
), dismissible: true, }, parameters: { docs: { description: { story: "contentType='node' enables custom component layouts with complex structures, flexbox layouts, or styled components.", }, }, }, } as Story; export const MultiParagraphContent: Story = { args: { open: true, severity: "info", title: "Important Update", contentType: "node", children: ( <>

We've made significant improvements to our privacy policy to better protect your data and give you more control over your information.

Please take a moment to review the changes. The new policy will take effect on January 1, 2026.

), dismissible: true, }, parameters: { docs: { description: { story: "Multiple paragraphs can be rendered when using contentType='node', allowing for more detailed alert messages.", }, }, }, } as Story; export const ContentTypeComparison: Story = { render: () => (
This simple text is automatically wrapped in a paragraph tag.

This content uses node mode for custom structure.

  • Supports lists
  • Multiple elements
  • Custom layouts
), parameters: { docs: { description: { story: "Side-by-side comparison of text mode (default) vs node mode for rendering alert content.", }, }, }, } as Story;