From c10a5e4e1cc8742a702534baa75a1665d0ed8317 Mon Sep 17 00:00:00 2001 From: Richie Date: Wed, 25 Mar 2026 17:04:37 +1100 Subject: [PATCH] Add Switch and Radio atom components Switch: - Wraps MUI Switch with FA brand tokens - Bordered pill track (Figma Style One), brand.500 fill when active - 4 component tokens: track width/height/borderRadius, thumb size - Stories include interactive service add-ons demo Radio: - Wraps MUI Radio with FA brand tokens - Brand.500 fill when selected, neutral.400 unchecked - 2 component tokens: outer size, dot size - Stories include card selection and payment method patterns Also: - Added ColourToggle and Slider to component registry (deferred) - Updated token registry and session log Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/memory/component-registry.md | 4 + docs/memory/session-log.md | 44 +++++- docs/memory/token-registry.md | 20 +++ src/components/atoms/Radio/Radio.stories.tsx | 146 ++++++++++++++++++ src/components/atoms/Radio/Radio.tsx | 33 ++++ src/components/atoms/Radio/index.ts | 2 + .../atoms/Switch/Switch.stories.tsx | 134 ++++++++++++++++ src/components/atoms/Switch/Switch.tsx | 33 ++++ src/components/atoms/Switch/index.ts | 2 + src/theme/generated/tokens.css | 6 + src/theme/generated/tokens.js | 6 + src/theme/index.ts | 69 +++++++++ tokens/component/radio.json | 15 ++ tokens/component/switch.json | 17 ++ 14 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 src/components/atoms/Radio/Radio.stories.tsx create mode 100644 src/components/atoms/Radio/Radio.tsx create mode 100644 src/components/atoms/Radio/index.ts create mode 100644 src/components/atoms/Switch/Switch.stories.tsx create mode 100644 src/components/atoms/Switch/Switch.tsx create mode 100644 src/components/atoms/Switch/index.ts create mode 100644 tokens/component/radio.json create mode 100644 tokens/component/switch.json diff --git a/docs/memory/component-registry.md b/docs/memory/component-registry.md index e60749c..b31453d 100644 --- a/docs/memory/component-registry.md +++ b/docs/memory/component-registry.md @@ -26,6 +26,10 @@ duplicates) and MUST update it after completing one. | Divider | planned | horizontal, vertical | | Visual separator | | Chip | review | filled, outlined × small, medium × clickable, deletable, selected × default, primary | chip.height/paddingX/fontSize/iconSize/deleteIconSize/iconGap/borderRadius, color.neutral.200-700, color.brand.200-700 | Interactive tag. Wraps MUI Chip with FA tokens. Selected state promotes to brand colour. Filled uses soft tonal bg (like Badge). | | Card | done | elevated, outlined × default, compact, none padding × interactive × selected | card.borderRadius/padding/shadow/border/background, color.surface.raised/subtle/warm, color.border.default/brand, shadow.md/lg | Content container. Elevated (shadow) or outlined (border). Interactive adds hover bg fill + shadow lift. Selected adds brand border + warm bg. Three padding presets. | +| Switch | review | bordered style × checked, unchecked, disabled | switch.track.width/height/borderRadius, switch.thumb.size, color.interactive.*, color.neutral.400 | Toggle for add-ons/options. Wraps MUI Switch. Bordered pill, brand.500 fill when active. From Parsons 1.0 Figma Style One. | +| Radio | review | checked, unchecked, disabled | radio.size/dotSize, color.interactive.*, color.neutral.400 | Single-select option. Wraps MUI Radio. Brand.500 fill when selected. From Parsons 1.0 Figma. | +| ColourToggle | planned | inactive, hover, active, locked × single, two-colour × desktop, mobile | | Circular colour swatch picker for products. Custom component. Deferred until product detail organisms. | +| Slider | planned | single, range × desktop, mobile | | Price range filter. Wraps MUI Slider. Deferred until search/filtering molecules. | | Link | planned | default, subtle | | Navigation text | ## Molecules diff --git a/docs/memory/session-log.md b/docs/memory/session-log.md index 85c0a14..c06371f 100644 --- a/docs/memory/session-log.md +++ b/docs/memory/session-log.md @@ -456,6 +456,46 @@ Each entry follows this structure: - **Planned (5 organisms):** ServiceSelector, PricingTable, ArrangementForm, Navigation, Footer **Next steps:** -- User to review Chip in Storybook +- ~~User to review Chip in Storybook~~ ✓ Approved +- ~~Consider running /audit or /critique on completed atoms to establish baseline scores~~ ✓ Done - Begin PriceCard molecule (depends on Card + Badge + Typography + Button + Chip) -- Consider running /audit or /critique on completed atoms to establish baseline scores + +### Session 2026-03-25k — Audit, P1 fixes, Switch + Radio atoms + +**Agent(s):** Claude Opus (via conversation) + +**Work completed:** +- Ran /audit on all 5 completed atoms (Button, Typography, Input, Card, Badge) + - Average score: 18/20 (Excellent) + - 2 P1 issues identified and fixed, 7 P2s documented, 5 P3s noted +- Fixed P1: Button loading state now announces to screen readers (aria-busy + visually-hidden text) +- Fixed P1: Card interactive now has tabIndex={0} and role="button" for keyboard operability +- Fixed P2: Card Record → Theme type for type safety +- Reviewed Parsons 1.0 Figma "Toggles" board (node 2322:42538) — identified 4 new atoms: Switch, Radio, ColourToggle, Slider +- Added all 4 to component registry (ColourToggle and Slider deferred) +- Created switch component tokens (`tokens/component/switch.json`): track width/height/borderRadius, thumb size — 4 tokens +- Created radio component tokens (`tokens/component/radio.json`): size, dotSize — 2 tokens +- Updated MUI theme with MuiSwitch overrides: bordered pill track, brand.500 active fill, focus-visible ring +- Updated MUI theme with MuiRadio overrides: neutral.400 unchecked, brand.500 checked, hover states +- Created Switch component — thin MUI wrapper with forwardRef +- Created Radio component — thin MUI wrapper with forwardRef +- Created Switch stories (4): Default, States, ServiceAddOns (interactive add-on toggle demo), WithLabels +- Created Radio stories (5): Default, States, RadioGroup, CardSelection (interactive card + radio demo), PaymentMethod +- Preflight passed all 5 checks + +**Decisions made:** +- Switch implements Figma "Style One" (bordered pill) only — other styles deferred as variants if needed +- Switch/Radio are ultra-thin wrappers — all styling via MUI theme overrides, no component-level sx +- ColourToggle and Slider deferred until their consuming organisms are built + +**Component status at end of session:** +- **Done (5):** Button, Typography, Input, Card, Badge +- **Review (3):** Chip, Switch, Radio +- **Planned (6 atoms):** IconButton, Icon, Avatar, Divider, ColourToggle, Slider, Link +- **Planned (5 molecules):** FormField, PriceCard, ServiceOption, SearchBar, StepIndicator +- **Planned (5 organisms):** ServiceSelector, PricingTable, ArrangementForm, Navigation, Footer + +**Next steps:** +- User to review Switch and Radio in Storybook +- Begin PriceCard molecule +- Address P2 audit issues in a future cleanup pass diff --git a/docs/memory/token-registry.md b/docs/memory/token-registry.md index f1facb1..b92f121 100644 --- a/docs/memory/token-registry.md +++ b/docs/memory/token-registry.md @@ -244,3 +244,23 @@ the correct token for any design property. | chip.deleteIconSize.md | 16px | Chip | Medium chip delete icon | | chip.iconGap.default | → spacing.1 (4px) | Chip | Icon-to-text gap | | chip.borderRadius.default | → borderRadius.full (9999px) | Chip | Pill shape | + +### Switch + +`tokens/component/switch.json` + +| Token path | Value / Reference | Used by | Description | +|-----------|-----------|---------|-------------| +| switch.track.width | 44px | Switch | Track width | +| switch.track.height | 24px | Switch | Track height | +| switch.track.borderRadius | → borderRadius.full (9999px) | Switch | Pill shape | +| switch.thumb.size | 18px | Switch | Thumb diameter | + +### Radio + +`tokens/component/radio.json` + +| Token path | Value / Reference | Used by | Description | +|-----------|-----------|---------|-------------| +| radio.size.default | 20px | Radio | Outer circle size | +| radio.dotSize.default | 10px | Radio | Inner selected dot size | diff --git a/src/components/atoms/Radio/Radio.stories.tsx b/src/components/atoms/Radio/Radio.stories.tsx new file mode 100644 index 0000000..e2fa186 --- /dev/null +++ b/src/components/atoms/Radio/Radio.stories.tsx @@ -0,0 +1,146 @@ +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Radio } from './Radio'; +import { Typography } from '../Typography'; +import { Card } from '../Card'; +import Box from '@mui/material/Box'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControl from '@mui/material/FormControl'; + +const meta: Meta = { + title: 'Atoms/Radio', + component: Radio, + tags: ['autodocs'], + parameters: { + layout: 'centered', + design: { + type: 'figma', + url: 'https://www.figma.com/design/XUDUrw4yMkEexBCCYHXUvT/Parsons?node-id=2322-42538', + }, + }, + argTypes: { + checked: { + control: 'boolean', + description: 'Whether the radio is selected', + }, + disabled: { + control: 'boolean', + description: 'Disable the radio', + table: { defaultValue: { summary: 'false' } }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ─── Default ──────────────────────────────────────────────────────────────── + +/** Default radio — unchecked */ +export const Default: Story = { + args: {}, +}; + +// ─── States ───────────────────────────────────────────────────────────────── + +/** All visual states */ +export const States: Story = { + render: () => ( + + } label="Unchecked" /> + } label="Checked" /> + } label="Disabled unchecked" /> + } label="Disabled checked" /> + + ), +}; + +// ─── Radio Group ──────────────────────────────────────────────────────────── + +/** Standard radio group with keyboard navigation */ +export const Group: Story = { + name: 'Radio Group', + render: () => ( + + Service type + + } label="Chapel ceremony" /> + } label="Graveside service" /> + } label="Memorial service" /> + } label="Direct cremation" /> + + + ), +}; + +// ─── Interactive: Card Selection ──────────────────────────────────────────── + +/** + * Radio buttons inside interactive cards — the recommended pattern + * for option selection in FA. Combines Card's selected state with + * Radio for accessible single-select. + */ +export const CardSelection: Story = { + name: 'Interactive — Card Selection', + render: () => { + const CardSelectDemo = () => { + const [selected, setSelected] = useState('standard'); + + const options = [ + { value: 'direct', label: 'Direct cremation', desc: 'Simple, dignified cremation with no service.', price: '$1,800' }, + { value: 'standard', label: 'Standard service', desc: 'Traditional chapel ceremony with viewing.', price: '$4,200' }, + { value: 'premium', label: 'Premium service', desc: 'Full service with personalised memorial.', price: '$7,500' }, + ]; + + return ( + + Choose a package + setSelected(e.target.value)}> + {options.map((opt) => ( + setSelected(opt.value)} + > + + + + + {opt.label} + {opt.price} + + {opt.desc} + + + + ))} + + + ); + }; + + return ; + }, +}; + +// ─── Interactive: Payment Method ──────────────────────────────────────────── + +/** Horizontal radio group for payment method selection */ +export const PaymentMethod: Story = { + name: 'Interactive — Payment Method', + render: () => ( + + Payment method + + } label="Credit card" /> + } label="Bank transfer" /> + } label="Payment plan" /> + + + ), +}; diff --git a/src/components/atoms/Radio/Radio.tsx b/src/components/atoms/Radio/Radio.tsx new file mode 100644 index 0000000..63f313f --- /dev/null +++ b/src/components/atoms/Radio/Radio.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import MuiRadio from '@mui/material/Radio'; +import type { RadioProps as MuiRadioProps } from '@mui/material/Radio'; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +/** Props for the FA Radio component */ +export interface RadioProps extends MuiRadioProps {} + +// ─── Component ─────────────────────────────────────────────────────────────── + +/** + * Radio button for the FA design system. + * + * Single-select control for mutually exclusive options. Wraps MUI Radio + * with FA brand tokens — warm gold fill when selected. + * + * From Parsons 1.0 Figma radio component — 16px circle with brand dot. + * + * Usage: + * - Always use inside a RadioGroup for proper keyboard navigation + * - Pair with FormControlLabel for accessible labels + * - For binary on/off, use Switch instead + * - For multi-select, use Checkbox (planned) or Chip + */ +export const Radio = React.forwardRef( + (props, ref) => { + return ; + }, +); + +Radio.displayName = 'Radio'; +export default Radio; diff --git a/src/components/atoms/Radio/index.ts b/src/components/atoms/Radio/index.ts new file mode 100644 index 0000000..97a6e56 --- /dev/null +++ b/src/components/atoms/Radio/index.ts @@ -0,0 +1,2 @@ +export { Radio, default } from './Radio'; +export type { RadioProps } from './Radio'; diff --git a/src/components/atoms/Switch/Switch.stories.tsx b/src/components/atoms/Switch/Switch.stories.tsx new file mode 100644 index 0000000..6f16592 --- /dev/null +++ b/src/components/atoms/Switch/Switch.stories.tsx @@ -0,0 +1,134 @@ +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Switch } from './Switch'; +import { Typography } from '../Typography'; +import { Card } from '../Card'; +import Box from '@mui/material/Box'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormGroup from '@mui/material/FormGroup'; + +const meta: Meta = { + title: 'Atoms/Switch', + component: Switch, + tags: ['autodocs'], + parameters: { + layout: 'centered', + design: { + type: 'figma', + url: 'https://www.figma.com/design/XUDUrw4yMkEexBCCYHXUvT/Parsons?node-id=2322-42538', + }, + }, + argTypes: { + checked: { + control: 'boolean', + description: 'Whether the switch is on', + }, + disabled: { + control: 'boolean', + description: 'Disable the switch', + table: { defaultValue: { summary: 'false' } }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ─── Default ──────────────────────────────────────────────────────────────── + +/** Default switch — unchecked */ +export const Default: Story = { + args: {}, +}; + +// ─── States ───────────────────────────────────────────────────────────────── + +/** All visual states */ +export const States: Story = { + render: () => ( + + } label="Unchecked" /> + } label="Checked" /> + } label="Disabled unchecked" /> + } label="Disabled checked" /> + + ), +}; + +// ─── Interactive: Service Add-ons ─────────────────────────────────────────── + +/** + * Realistic arrangement form pattern — toggle add-on services. + */ +export const ServiceAddOns: Story = { + name: 'Interactive — Service Add-ons', + render: () => { + const AddOnDemo = () => { + const [addOns, setAddOns] = useState({ + catering: true, + flowers: true, + music: false, + memorial: false, + guestBook: false, + }); + + const toggle = (key: keyof typeof addOns) => { + setAddOns((prev) => ({ ...prev, [key]: !prev[key] })); + }; + + const items = [ + { key: 'catering' as const, label: 'Catering', desc: 'Light refreshments after the service', price: '$450' }, + { key: 'flowers' as const, label: 'Floral arrangements', desc: 'Seasonal flowers for the chapel', price: '$280' }, + { key: 'music' as const, label: 'Live music', desc: 'Organist or solo musician', price: '$350' }, + { key: 'memorial' as const, label: 'Memorial video', desc: 'Photo slideshow with music', price: '$200' }, + { key: 'guestBook' as const, label: 'Guest book', desc: 'Leather-bound memorial guest book', price: '$85' }, + ]; + + const total = items.reduce((sum, item) => + addOns[item.key] ? sum + parseInt(item.price.replace('$', ''), 10) : sum, 0, + ); + + return ( + + Service add-ons + + {items.map((item) => ( + + + + {item.label} + {item.desc} + + + {item.price} + toggle(item.key)} /> + + + + ))} + + + Total add-ons + ${total} + + + ); + }; + + return ; + }, +}; + +// ─── With Labels ──────────────────────────────────────────────────────────── + +/** FormControlLabel pairing — the recommended usage pattern */ +export const WithLabels: Story = { + name: 'With Labels', + render: () => ( + + } label="Email notifications" /> + } label="SMS notifications" /> + } label="Save arrangement progress" /> + + ), +}; diff --git a/src/components/atoms/Switch/Switch.tsx b/src/components/atoms/Switch/Switch.tsx new file mode 100644 index 0000000..5d673d9 --- /dev/null +++ b/src/components/atoms/Switch/Switch.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import MuiSwitch from '@mui/material/Switch'; +import type { SwitchProps as MuiSwitchProps } from '@mui/material/Switch'; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +/** Props for the FA Switch component */ +export interface SwitchProps extends MuiSwitchProps {} + +// ─── Component ─────────────────────────────────────────────────────────────── + +/** + * Toggle switch for the FA design system. + * + * Binary on/off control for enabling add-ons and options in arrangement + * forms. Wraps MUI Switch with FA brand tokens — warm gold track when + * active, bordered pill shape when inactive. + * + * From Parsons 1.0 Figma "Style One" — bordered variant with brand fill. + * + * Usage: + * - Use for boolean settings ("Include catering", "Add memorial video") + * - Pair with a label via FormControlLabel for accessibility + * - For mutually exclusive options, use Radio instead + */ +export const Switch = React.forwardRef( + (props, ref) => { + return ; + }, +); + +Switch.displayName = 'Switch'; +export default Switch; diff --git a/src/components/atoms/Switch/index.ts b/src/components/atoms/Switch/index.ts new file mode 100644 index 0000000..a69f08a --- /dev/null +++ b/src/components/atoms/Switch/index.ts @@ -0,0 +1,2 @@ +export { Switch, default } from './Switch'; +export type { SwitchProps } from './Switch'; diff --git a/src/theme/generated/tokens.css b/src/theme/generated/tokens.css index f727a6e..45fedc4 100644 --- a/src/theme/generated/tokens.css +++ b/src/theme/generated/tokens.css @@ -26,6 +26,11 @@ --fa-input-height-sm: 40px; /** Small — compact forms, admin layouts, matches Button medium height */ --fa-input-height-md: 48px; /** Medium (default) — standard forms, matches Button large for alignment */ --fa-input-icon-size-default: 20px; /** 20px — icon size inside input field, matches Figma trailing icon */ + --fa-radio-size-default: 20px; /** Default radio size — matches Figma 16px + padding for 44px touch target area */ + --fa-radio-dot-size-default: 10px; /** Selected indicator dot — 50% of outer size */ + --fa-switch-track-width: 44px; /** Track width — slightly narrower than Figma 52px for better proportion with 44px touch target */ + --fa-switch-track-height: 24px; /** Track height */ + --fa-switch-thumb-size: 18px; /** Thumb diameter — sits inside the track with 3px inset */ --fa-color-brand-50: #fef9f5; /** Lightest warm tint — warm section backgrounds */ --fa-color-brand-100: #f7ecdf; /** Light warm — hover backgrounds, subtle fills */ --fa-color-brand-200: #ebdac8; /** Warm light — secondary backgrounds, divider tones */ @@ -260,6 +265,7 @@ --fa-input-font-size-default: var(--fa-font-size-base); /** 16px — prevents iOS auto-zoom on focus, matches Figma */ --fa-input-border-radius-default: var(--fa-border-radius-sm); /** 4px — subtle rounding, consistent with Figma design */ --fa-input-gap-default: var(--fa-spacing-2); /** 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability */ + --fa-switch-track-border-radius: var(--fa-border-radius-full); /** Pill shape */ --fa-color-text-primary: var(--fa-color-neutral-800); /** Primary text — body content, headings. Cool charcoal (#2C2E35) for comfortable extended reading */ --fa-color-text-secondary: var(--fa-color-neutral-600); /** Secondary text — helper text, descriptions, metadata, less prominent content */ --fa-color-text-tertiary: var(--fa-color-neutral-500); /** Tertiary text — placeholders, timestamps, attribution, meta information */ diff --git a/src/theme/generated/tokens.js b/src/theme/generated/tokens.js index fa94681..32523cb 100644 --- a/src/theme/generated/tokens.js +++ b/src/theme/generated/tokens.js @@ -72,6 +72,12 @@ export const InputFontSizeDefault = "1rem"; // 16px — prevents iOS auto-zoom o export const InputBorderRadiusDefault = "4px"; // 4px — subtle rounding, consistent with Figma design export const InputGapDefault = "8px"; // 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability export const InputIconSizeDefault = "20px"; // 20px — icon size inside input field, matches Figma trailing icon +export const RadioSizeDefault = "20px"; // Default radio size — matches Figma 16px + padding for 44px touch target area +export const RadioDotSizeDefault = "10px"; // Selected indicator dot — 50% of outer size +export const SwitchTrackWidth = "44px"; // Track width — slightly narrower than Figma 52px for better proportion with 44px touch target +export const SwitchTrackHeight = "24px"; // Track height +export const SwitchTrackBorderRadius = "9999px"; // Pill shape +export const SwitchThumbSize = "18px"; // Thumb diameter — sits inside the track with 3px inset export const ColorBrand50 = "#fef9f5"; // Lightest warm tint — warm section backgrounds export const ColorBrand100 = "#f7ecdf"; // Light warm — hover backgrounds, subtle fills export const ColorBrand200 = "#ebdac8"; // Warm light — secondary backgrounds, divider tones diff --git a/src/theme/index.ts b/src/theme/index.ts index 13ca1b0..131b5fa 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -636,6 +636,75 @@ export const theme = createTheme({ }, }, }, + MuiSwitch: { + styleOverrides: { + root: { + width: parseInt(t.SwitchTrackWidth, 10) + 12, + height: parseInt(t.SwitchTrackHeight, 10) + 12, + padding: 6, + }, + switchBase: { + padding: 9, + '&.Mui-checked': { + transform: `translateX(${parseInt(t.SwitchTrackWidth, 10) - parseInt(t.SwitchTrackHeight, 10)}px)`, + color: t.ColorWhite, + '& + .MuiSwitch-track': { + backgroundColor: t.ColorInteractiveDefault, + borderColor: t.ColorInteractiveDefault, + opacity: 1, + }, + '&:hover + .MuiSwitch-track': { + backgroundColor: t.ColorInteractiveHover, + }, + }, + '&.Mui-disabled + .MuiSwitch-track': { + opacity: 0.4, + }, + '&:focus-visible + .MuiSwitch-track': { + outline: `2px solid ${t.ColorInteractiveFocus}`, + outlineOffset: '2px', + }, + }, + thumb: { + width: parseInt(t.SwitchThumbSize, 10), + height: parseInt(t.SwitchThumbSize, 10), + boxShadow: '0 1px 3px rgba(0,0,0,0.2)', + }, + track: { + borderRadius: parseInt(t.SwitchTrackBorderRadius, 10), + backgroundColor: t.ColorWhite, + border: `1.5px solid ${t.ColorNeutral400}`, + opacity: 1, + transition: 'background-color 150ms ease-in-out, border-color 150ms ease-in-out', + }, + }, + }, + MuiRadio: { + styleOverrides: { + root: { + color: t.ColorNeutral400, + transition: 'color 150ms ease-in-out', + '&:hover': { + color: t.ColorNeutral600, + backgroundColor: 'transparent', + }, + '&.Mui-checked': { + color: t.ColorInteractiveDefault, + '&:hover': { + color: t.ColorInteractiveHover, + }, + }, + '&:focus-visible': { + outline: `2px solid ${t.ColorInteractiveFocus}`, + outlineOffset: '2px', + borderRadius: '50%', + }, + '&.Mui-disabled': { + color: t.ColorNeutral300, + }, + }, + }, + }, }, }); diff --git a/tokens/component/radio.json b/tokens/component/radio.json new file mode 100644 index 0000000..054c9db --- /dev/null +++ b/tokens/component/radio.json @@ -0,0 +1,15 @@ +{ + "radio": { + "$description": "Radio component tokens — single-select control for mutually exclusive options. Used in service selection, payment method, and arrangement forms.", + "size": { + "$type": "dimension", + "$description": "Radio button outer circle size.", + "default": { "$value": "20px", "$description": "Default radio size — matches Figma 16px + padding for 44px touch target area" } + }, + "dotSize": { + "$type": "dimension", + "$description": "Inner dot size when selected.", + "default": { "$value": "10px", "$description": "Selected indicator dot — 50% of outer size" } + } + } +} diff --git a/tokens/component/switch.json b/tokens/component/switch.json new file mode 100644 index 0000000..6f423b6 --- /dev/null +++ b/tokens/component/switch.json @@ -0,0 +1,17 @@ +{ + "switch": { + "$description": "Switch component tokens — toggle control for enabling/disabling options. Used in arrangement forms for add-on services.", + "track": { + "$type": "dimension", + "$description": "Switch track dimensions.", + "width": { "$value": "44px", "$description": "Track width — slightly narrower than Figma 52px for better proportion with 44px touch target" }, + "height": { "$value": "24px", "$description": "Track height" }, + "borderRadius": { "$value": "{borderRadius.full}", "$description": "Pill shape" } + }, + "thumb": { + "$type": "dimension", + "$description": "Switch thumb (knob) dimensions.", + "size": { "$value": "18px", "$description": "Thumb diameter — sits inside the track with 3px inset" } + } + } +}