import type { Meta, StoryObj } from '@storybook/nextjs-vite' import { useState, useTransition } from 'react' import Switch from '.' import { SwitchSkeleton } from './skeleton' const meta = { title: 'Base/Data Entry/Switch', component: Switch, parameters: { layout: 'centered', docs: { description: { component: 'Toggle switch built on Base UI with CVA variants, Figma-aligned design tokens, loading spinner, and skeleton placeholder. Import `Switch` for the toggle and `SwitchSkeleton` from `./skeleton` for loading placeholders.', }, }, }, tags: ['autodocs'], args: { value: false, }, argTypes: { size: { control: 'select', options: ['xs', 'sm', 'md', 'lg'], description: 'Switch size', }, value: { control: 'boolean', description: 'Checked state (controlled)', }, disabled: { control: 'boolean', description: 'Disabled state', }, loading: { control: 'boolean', description: 'Loading state with spinner (md/lg only)', }, }, } satisfies Meta export default meta type Story = StoryObj const SwitchDemo = (args: any) => { const [enabled, setEnabled] = useState(args.value ?? false) return (
{enabled ? 'On' : 'Off'}
) } export const Default: Story = { render: args => , args: { size: 'md', value: false, disabled: false, }, } export const DefaultOn: Story = { render: args => , args: { size: 'md', value: true, disabled: false, }, } export const DisabledOff: Story = { render: args => , args: { size: 'md', value: false, disabled: true, }, } export const DisabledOn: Story = { render: args => , args: { size: 'md', value: true, disabled: true, }, } const AllStatesDemo = () => { const sizes = ['xs', 'sm', 'md', 'lg'] as const return (
{sizes.map(size => ( ))}
Size Default Disabled Loading Skeleton
{size}
{}} /> {}} />
) } export const AllStates: Story = { render: () => , parameters: { docs: { description: { story: 'Complete variant matrix: all sizes × all states, matching Figma design spec (node 2144:1210).', }, }, }, } const SizeComparisonDemo = () => { const [states, setStates] = useState({ xs: false, sm: false, md: true, lg: true, }) return (
setStates({ ...states, xs: v })} /> Extra Small (xs) — 14×10
setStates({ ...states, sm: v })} /> Small (sm) — 20×12
setStates({ ...states, md: v })} /> Regular (md) — 28×16
setStates({ ...states, lg: v })} /> Large (lg) — 36×20
) } export const SizeComparison: Story = { render: () => , } const LoadingDemo = () => { const [loading, setLoading] = useState(true) return (
Large unchecked
Large checked
Regular unchecked
Regular checked
Small (no spinner)
Extra Small (no spinner)
) } export const Loading: Story = { render: () => , parameters: { docs: { description: { story: 'Loading state disables interaction and shows a spinning icon (i-ri-loader-2-line) for md/lg sizes. Spinner position mirrors the knob: appears on the opposite side of the checked state.', }, }, }, } const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) const MutationLoadingDemo = () => { const [enabled, setEnabled] = useState(false) const [requestCount, setRequestCount] = useState(0) const [isPending, startTransition] = useTransition() const handleChange = (nextValue: boolean) => { if (isPending) return startTransition(async () => { setRequestCount(current => current + 1) await wait(1200) setEnabled(nextValue) }) } return (

Mutation Loading Guard

Click once to start a simulated mutate call. While the request is pending, the switch enters {' '} loading {' '} and rejects duplicate clicks.

Enable Auto Retry

{isPending ? 'Saving…' : enabled ? 'Saved as on' : 'Saved as off'}

Committed Value
{enabled ? 'On' : 'Off'}
Mutate Count
{requestCount}
) } export const MutationLoadingGuard: Story = { render: () => , parameters: { docs: { description: { story: 'Simulates a controlled switch backed by an async mutate call. The component keeps its previous committed value, sets `loading` during the request, and blocks duplicate clicks until the mutation resolves.', }, }, }, } const SkeletonDemo = () => (
Extra Small skeleton
Small skeleton
Regular skeleton
Large skeleton
) export const Skeleton: Story = { render: () => , parameters: { docs: { description: { story: '`SwitchSkeleton` renders a non-interactive placeholder with `bg-text-quaternary opacity-20`. Imported separately from `./skeleton`.', }, }, }, } export const Playground: Story = { render: args => , args: { size: 'md', value: false, disabled: false, loading: false, }, }