import * as React from 'react';
import { Checkbox } from '@/components/checkbox';
import type { CheckboxProps } from '@/components/checkbox';
import { Switch } from '@/components/switch';
import type { SwitchProps } from '@/components/switch';
import { RadioGroup, RadioGroupItem } from '@/components/radio-group';
import { Label } from '@/components/label';
import { PALETTES } from './axis-values';
import type { Entry, Palette } from './types';
const SIZES = ['xs', 'sm', 'md', 'lg'] as const;
/**
* The parent/child group — the reason the third state exists.
*
* Named, with an explicit `displayName`, for `chrome/lib/codegen.ts` — see the note
* on `ControlledSortDemo` in `registry/data.tsx`.
*/
function ParentChildDemo() {
const [items, setItems] = React.useState([true, false, false]);
const all = items.every(Boolean);
const some = items.some(Boolean);
return (
setItems(items.map(() => next === true))}
/>
{items.map((checked, index) => (
setItems(items.map((value, i) => (i === index ? next === true : value)))
}
/>
))}
);
}
ParentChildDemo.displayName = 'ParentChildDemo';
export const checkbox: Entry = {
slug: 'checkbox',
name: 'Checkbox',
group: 'Forms',
description:
'A tri-state box (on / off / indeterminate). The palette colours the checked fill; the unchecked box stays neutral so a form does not read as decorated.',
imports: { subpath: 'checkbox', names: ['Checkbox'] },
/* CBAR folds checked, hover, invalid and disabled into one `state` axis and
spells its resting value with a capital. The kit keeps them as separate
props and draws the interaction states in CSS, so the axis is pinned. */
figma: { set: 'Checkmark', pin: { state: 'Default' } },
axes: {
colorPalette: PALETTES,
size: SIZES,
checked: [true, false],
disabled: [false, true],
},
defaults: { colorPalette: 'primary', size: 'md', checked: true, disabled: false },
render: (props) => ,
matrices: [
{
title: 'Size × colorPalette',
description: 'Checked, which is the only state the palette is visible in.',
rows: 'size',
cols: 'colorPalette',
pin: { checked: true },
},
{
title: 'Size × state',
rows: 'size',
cols: 'checked',
pin: { colorPalette: 'primary' },
},
],
compositions: [
{
name: 'With a label',
description: 'Label htmlFor + Checkbox id makes the whole label a hit target.',
render: () => (
),
},
{
name: 'Indeterminate',
description: 'The parent of a partially selected group.',
render: () => ,
},
{
name: 'Parent and children',
description:
'What that third state is for: the parent goes indeterminate when only some children are checked, rather than lying about the group in either direction.',
render: () => ,
},
],
};
export const switchEntry: Entry = {
slug: 'switch',
name: 'Switch',
group: 'Forms',
description:
'An immediate on/off toggle. Reach for it when the change applies at once; use a Checkbox when it applies on submit.',
imports: { subpath: 'switch', names: ['Switch'] },
figma: { set: 'Switch', axisMap: { Size: 'size' }, pin: { State: 'default' } },
axes: {
colorPalette: PALETTES,
size: SIZES,
checked: [true, false],
disabled: [false, true],
},
defaults: { colorPalette: 'primary', size: 'md', checked: true, disabled: false },
render: (props) => ,
matrices: [
{
title: 'Size × colorPalette',
rows: 'size',
cols: 'colorPalette',
pin: { checked: true },
},
{ title: 'Size × state', rows: 'size', cols: 'checked', pin: { colorPalette: 'primary' } },
{
title: 'Disabled',
rows: 'size',
cols: 'checked',
pin: { colorPalette: 'primary', disabled: true },
},
],
compositions: [
{
name: 'With a label',
render: () => (
),
},
],
};
export const radioGroup: Entry = {
slug: 'radio-group',
name: 'RadioGroup',
group: 'Forms',
description:
'One choice out of several. `colorPalette` and `size` live on RadioGroupItem, not on the group — the group itself is only a roving-focus container.',
imports: { subpath: 'radio-group', names: ['RadioGroup', 'RadioGroupItem'] },
/* CBAR draws two RadioMark sets on one page; the capture keeps both and this
resolves to the first, whose `state` axis is the long one. */
figma: { set: 'RadioMark', pin: { state: 'default' } },
axes: {
colorPalette: PALETTES,
size: SIZES,
disabled: [false, true],
},
defaults: { colorPalette: 'primary', size: 'sm', disabled: false },
/* `value` is required on the item and must not be overwritten by the bag, so
the axes are passed by name. */
render: (props) => (
),
matrices: [
{ title: 'Size × colorPalette', rows: 'size', cols: 'colorPalette' },
{
title: 'Disabled',
rows: 'size',
cols: 'colorPalette',
pin: { disabled: true },
},
],
compositions: [
{
name: 'A real group',
render: () => (
{[
['monthly', 'Monthly'],
['yearly', 'Yearly — two months free'],
['lifetime', 'Lifetime'],
].map(([value, label]) => (
))}
),
},
{
name: 'With a disabled option',
description:
'A disabled item is skipped by the arrow keys but stays in the list, so the choice that is unavailable is still visible as a choice.',
render: () => (
{[
['standard', 'Standard delivery', false],
['express', 'Express delivery', false],
['same-day', 'Same day (unavailable in your area)', true],
].map(([value, label, disabled]) => (
))}
),
},
{
name: 'Horizontal',
description:
'Layout is entirely the container’s — the group is a roving-focus wrapper and draws no direction of its own.',
render: () => (
{[
['yes', 'Yes'],
['no', 'No'],
].map(([value, label]) => (