Add Input, Checkbox, Radio, and Switch form components with semantic token layer

Build four form primitives from Figma references with brand-aligned creative
decisions: restrained press states (scale-95 instead of highlight splashes),
clean iconless Switch, and consistent error states with inline warning icons.

Introduce form-control semantic tokens (--color-control-*) in tokens.css so
all form components share a single source for borders, checked states, focus
rings, labels, and errors. Retrofit Input to use these tokens instead of
direct palette references.

Update CLAUDE.md and ARCHITECTURE.md with token layer documentation, token
discipline rule (no palette references in components), and component tier
decision criteria.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 14:00:56 +10:00
parent 0e1b06b376
commit 07be9d7314
18 changed files with 1523 additions and 57 deletions

View File

@@ -0,0 +1,85 @@
import { useState } from 'react'
import type { Meta, StoryObj } from '@storybook/react-vite'
import { Switch } from './Switch'
const meta: Meta<typeof Switch> = {
title: 'UI/Switch',
component: Switch,
tags: ['autodocs'],
argTypes: {
label: { control: 'text' },
description: { control: 'text' },
checked: { control: 'boolean' },
disabled: { control: 'boolean' },
},
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/design/mrabO6AtxN3ektGiTk0I9c/ResearchInsights?node-id=33-5337',
},
},
}
export default meta
type Story = StoryObj<typeof meta>
const ControlledSwitch = (props: React.ComponentProps<typeof Switch>) => {
const [checked, setChecked] = useState(props.checked ?? false)
return <Switch {...props} checked={checked} onChange={setChecked} />
}
export const Default: Story = {
render: () => <ControlledSwitch label="Enable notifications" />,
}
export const On: Story = {
render: () => <ControlledSwitch label="Enable notifications" checked />,
}
export const WithDescription: Story = {
render: () => (
<ControlledSwitch
label="Auto-save responses"
description="Automatically save participant responses as they are entered."
checked
/>
),
}
export const Disabled: Story = {
render: () => (
<div className="flex flex-col gap-4">
<Switch label="Disabled off" disabled />
<Switch label="Disabled on" disabled checked />
</div>
),
}
export const Standalone: Story = {
render: () => (
<div className="flex items-center gap-4">
<span className="text-body text-grey-01">Dark mode</span>
<ControlledSwitch aria-label="Toggle dark mode" />
</div>
),
}
export const AllStates: Story = {
render: () => (
<div className="flex flex-col gap-4">
<ControlledSwitch label="Off" />
<ControlledSwitch label="On" checked />
<ControlledSwitch
label="With description"
description="Additional context about this setting."
/>
<ControlledSwitch
label="On with description"
description="This feature is currently enabled."
checked
/>
<Switch label="Disabled off" disabled />
<Switch label="Disabled on" disabled checked />
</div>
),
}