import { StoryObj, Meta } from "@storybook/react-vite"; import { within, expect, userEvent } from "storybook/test"; import { Fieldset } from "./landmarks"; const meta: Meta = { title: "FP.React Components/Layout/Fieldset", component: Fieldset, parameters: { docs: { description: { component: `Fieldset component for semantic content grouping with optional legend. ## Features - **Semantic HTML** - Uses native \`
\` and \`\` elements - **Accessible** - Automatic \`role="group"\` for screen readers - **Optional Legend** - Flexible content grouping with or without visible label - **CSS Custom Properties** - Full theming control - **Variants** - Inline legend and grouped emphasis styles ## CSS Variables ### Fieldset - \`--fieldset-border\`: Border style (default: 1px solid #ccc) - \`--fieldset-border-radius\`: Border radius (default: 0.5rem) - \`--fieldset-padding\`: General padding (default: 1rem) - \`--fieldset-padding-inline\`: Horizontal padding (default: 1.5rem) - \`--fieldset-padding-block\`: Vertical padding (default: 1rem) - \`--fieldset-margin-block\`: Vertical margin (default: 2rem) - \`--fieldset-bg\`: Background color (default: transparent) ### Legend - \`--legend-fs\`: Font size (default: 1rem) - \`--legend-fw\`: Font weight (default: 600) - \`--legend-padding-inline\`: Horizontal padding (default: 0.5rem) - \`--legend-color\`: Text color (default: currentColor) ## Variants ### Inline Legend (\`data-legend="inline"\`) Compact layout with legend displayed inline: - Removes border and padding - Floats legend to inline-start - Adds horizontal margin after legend ### Grouped (\`data-fieldset="grouped"\`) Emphasized styling for important sections: - Subtle background color - Thicker accent border - Increased padding and font weight `, }, }, }, args: { children: "Default Fieldset Content", }, tags: ["stable"], } as Meta; export default meta; type Story = StoryObj; // Basic fieldset export const BasicFieldset: Story = { args: { legend: "Personal Information", children: ( <>

Name: John Doe

Email: john@example.com

), }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByRole("group")).toBeInTheDocument(); expect(canvas.getByText("Personal Information")).toBeInTheDocument(); }, }; // Inline legend export const InlineLegendFieldset: Story = { args: { legend: "Status:", "data-legend": "inline", children:

Active

, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByRole("group")).toHaveAttribute("data-legend", "inline"); }, }; // Grouped variant export const GroupedFieldset: Story = { args: { legend: "Account Settings", "data-fieldset": "grouped", children: ( <>

Username: johndoe

Role: Admin

), }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByRole("group")).toHaveAttribute( "data-fieldset", "grouped" ); }, }; // No legend example export const FieldsetNoLegend: Story = { args: { children:

Grouped content without visible legend

, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByRole("group")).toBeInTheDocument(); expect(canvas.queryAllByRole("legend")).toHaveLength(0); }, }; // Custom styled fieldset export const CustomStyledFieldset: Story = { args: { legend: "Custom Styled", children: ( <>

This fieldset demonstrates custom CSS variable overrides.

Border, background, and typography are customized.

), styles: { "--fieldset-border": "2px dashed #666", "--fieldset-bg": "#f0f0f0", "--legend-color": "#0066cc", "--legend-fs": "1.25rem", }, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByRole("group")).toBeInTheDocument(); expect(canvas.getByText("Custom Styled")).toBeInTheDocument(); }, }; // Accessibility Test - WCAG 2.1 Level AA Compliance export const AccessibilityTest: Story = { args: { id: "contact-fieldset", legend: "Contact Information", description: "Please provide your contact details for follow-up", children: ( <> ), }, play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); await step("Verify group role", async () => { const fieldset = canvas.getByRole("group"); expect(fieldset).toBeInTheDocument(); }); await step("Verify aria-describedby", async () => { const fieldset = canvas.getByRole("group"); expect(fieldset).toHaveAttribute("aria-describedby"); expect(fieldset.getAttribute("aria-describedby")).toBe( "contact-fieldset-desc" ); }); await step("Verify description is present", async () => { expect( canvas.getByText(/provide your contact details/i) ).toBeInTheDocument(); }); await step("Test keyboard navigation", async () => { const nameInput = canvas.getByLabelText(/name/i); await userEvent.tab(); expect(nameInput).toHaveFocus(); const emailInput = canvas.getByLabelText(/email/i); await userEvent.tab(); expect(emailInput).toHaveFocus(); const phoneInput = canvas.getByLabelText(/phone/i); await userEvent.tab(); expect(phoneInput).toHaveFocus(); }); await step("Verify required fields marked", async () => { const requiredIndicators = canvas.getAllByLabelText(/required/i); expect(requiredIndicators).toHaveLength(2); }); }, }; // Disabled Fieldset export const DisabledFieldset: Story = { args: { id: "disabled-fieldset", legend: "Disabled Fieldset", description: "This fieldset and all its controls are disabled", disabled: true, children: ( <> ), }, play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); await step("Verify fieldset is disabled", async () => { expect(canvas.getByRole("group")).toBeDisabled(); }); await step("Verify inputs are disabled", async () => { expect(canvas.getByLabelText(/input/i)).toBeDisabled(); expect(canvas.getByLabelText(/select/i)).toBeDisabled(); }); }, }; // With Form Controls - Real world example export const WithFormControls: Story = { args: { id: "shipping-address", legend: "Shipping Address", description: "This address will be used for delivery", "data-fieldset": "grouped", children: (
), }, play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); await step("Verify fieldset structure", async () => { const fieldset = canvas.getByRole("group"); expect(fieldset).toBeInTheDocument(); expect(fieldset).toHaveAttribute("data-fieldset", "grouped"); }); await step("Verify all form fields are accessible", async () => { expect(canvas.getByLabelText(/street address/i)).toBeInTheDocument(); expect(canvas.getByLabelText(/city/i)).toBeInTheDocument(); expect(canvas.getByLabelText(/state/i)).toBeInTheDocument(); expect(canvas.getByLabelText(/zip code/i)).toBeInTheDocument(); }); await step("Test form navigation", async () => { const streetInput = canvas.getByLabelText(/street address/i); await userEvent.tab(); expect(streetInput).toHaveFocus(); await userEvent.type(streetInput, "123 Main St"); expect(streetInput).toHaveValue("123 Main St"); }); }, }; // Multiple Fieldsets - Complex form example export const MultipleFieldsets: Story = { render: () => ( <>
), play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); await step("Verify multiple fieldsets rendered", async () => { const fieldsets = canvas.getAllByRole("group"); expect(fieldsets).toHaveLength(2); }); await step("Verify each has aria-describedby", async () => { const fieldsets = canvas.getAllByRole("group"); fieldsets.forEach((fieldset) => { expect(fieldset).toHaveAttribute("aria-describedby"); }); }); }, };