Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 94 additions & 44 deletions src/components/ui/__stories__/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,109 @@
import type { Meta, StoryObj } from "@storybook/nextjs"

import CheckboxComponent, { type CheckboxProps } from "../checkbox"
import Checkbox, { type CheckboxProps } from "../checkbox"
import { HStack, VStack } from "../flex"

const meta = {
title: "Atoms / Form / Checkbox",
component: CheckboxComponent,
} satisfies Meta<typeof CheckboxComponent>
title: "UI / Primitives / Checkbox",
component: Checkbox,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Single binary toggle built on Radix Checkbox. Supports `checked`, `disabled`, `aria-invalid` for error styling, and tri-state via `checked='indeterminate'`. Pair with a `<label>` so clicks on the label toggle the control.",
},
},
},
} satisfies Meta<typeof Checkbox>

export default meta

const checkboxDataSet: (CheckboxProps & { label: string })[] = [
{
id: "default",
value: "default",
label: "default",
},
{
id: "checked",
value: "checked",
label: "checked",
checked: true,
},
{
id: "disabled",
value: "disabled",
label: "disabled",
disabled: true,
},
{
id: "disabled-checked",
value: "disabled-checked",
label: "disabled-checked",
disabled: true,
checked: true,
type Story = StoryObj<typeof meta>

const LabeledCheckbox = ({
label,
...props
}: CheckboxProps & { label: string }) => (
<HStack asChild>
<label>
<Checkbox {...props} />
{label}
</label>
</HStack>
)

export const Default: Story = {
render: () => <LabeledCheckbox id="default" label="Subscribe to updates" />,
}

export const Checked: Story = {
render: () => (
<LabeledCheckbox id="checked" label="Already subscribed" defaultChecked />
),
}

export const Indeterminate: Story = {
parameters: {
docs: {
description: {
story:
"`checked='indeterminate'` represents a mixed state, typically used as a parent controlling a group where some (but not all) children are checked.",
},
},
},
{
id: "invalid",
value: "invalid",
label: "invalid",
"aria-invalid": true,
render: () => (
<LabeledCheckbox
id="indeterminate"
label="Some items selected"
checked="indeterminate"
/>
),
}

export const Disabled: Story = {
render: () => (
<VStack className="items-start gap-3">
<LabeledCheckbox id="disabled" label="Disabled" disabled />
<LabeledCheckbox
id="disabled-checked"
label="Disabled and checked"
disabled
defaultChecked
/>
</VStack>
),
}

export const Invalid: Story = {
parameters: {
docs: {
description: {
story:
"Apply `aria-invalid` to surface error styling defined by `commonControlClasses`.",
},
},
},
]
render: () => (
<LabeledCheckbox id="invalid" label="Required field" aria-invalid="true" />
),
}

export const Checkbox: StoryObj<typeof meta> = {
export const AsGroup: Story = {
parameters: {
docs: {
description: {
story:
"Multiple checkboxes form an unordered group. For mutually exclusive options, use `RadioGroup` instead.",
},
},
},
render: () => (
<VStack className="items-start gap-4">
{checkboxDataSet.map(({ label, ...props }) => (
<HStack key={props.id} asChild>
<label>
<CheckboxComponent {...props} />
{label}
</label>
</HStack>
))}
<VStack className="items-start gap-3">
<LabeledCheckbox id="group-eth" label="Ethereum" defaultChecked />
<LabeledCheckbox id="group-arb" label="Arbitrum" />
<LabeledCheckbox id="group-base" label="Base" defaultChecked />
<LabeledCheckbox id="group-op" label="OP Mainnet" />
</VStack>
),
}
82 changes: 71 additions & 11 deletions src/components/ui/__stories__/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,94 @@ import { VStack } from "../flex"
import Input from "../input"

const meta = {
title: "Atoms / Form / Input",
title: "UI / Primitives / Input",
component: Input,
parameters: {
docs: {
description: {
component:
"Single-line text input. Variants: `size: md|sm` and `hasError: true|false`. Pair with a sibling helper text element when validation guidance is needed.",
},
},
},
} satisfies Meta<typeof Input>

export default meta

type Story = StoryObj<typeof meta>

export const Sizes: Story = {
args: {
placeholder: "Search",
},
export const Default: Story = {
parameters: { chromatic: { disableSnapshot: true } },
args: { placeholder: "Search" },
render: (args) => (
<VStack className="w-[154px]">
<div className="w-[258px]">
<Input {...args} />
</div>
),
}

export const Sizes: Story = {
args: { placeholder: "Search" },
render: (args) => (
<VStack className="w-[258px] gap-4">
<Input {...args} size="md" />
<Input {...args} size="sm" />
</VStack>
),
}

export const ElementVariations: Story = {
args: {
placeholder: "input text",
},
export const HasError: Story = {
parameters: { chromatic: { disableSnapshot: true } },
args: { placeholder: "Required" },
render: (args) => (
<VStack className="w-[258px] gap-4">
<Input {...args} hasError={false} />
<Input {...args} hasError />
</VStack>
),
}

export const Disabled: Story = {
parameters: { chromatic: { disableSnapshot: true } },
args: { placeholder: "Cannot edit" },
render: (args) => (
<VStack className="w-[258px] gap-4">
<Input {...args} autoFocus />
<Input {...args} disabled />
<Input {...args} disabled defaultValue="Pre-filled" />
</VStack>
),
}

export const AutoFocus: Story = {
parameters: { chromatic: { disableSnapshot: true } },
args: { placeholder: "Focused on mount", autoFocus: true },
render: (args) => (
<div className="w-[258px]">
<Input {...args} />
</div>
),
}

export const WithHelperText: Story = {
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
story:
"Helper text and error text are rendered as sibling elements. The error tone pairs with `hasError`.",
},
},
},
render: () => (
<VStack className="w-[258px] gap-6">
<div className="space-y-1">
<Input placeholder="username" />
<p className="text-xs text-body-medium">3 to 16 characters.</p>
</div>
<div className="space-y-1">
<Input hasError placeholder="username" />
<p className="text-xs text-error">Username is already taken.</p>
</div>
</VStack>
),
}
68 changes: 0 additions & 68 deletions src/components/ui/__stories__/RadioGroup.stories.tsx

This file was deleted.

Loading
Loading