Skip to content

Commit 569133b

Browse files
committed
chore!: refactor Checkbox to use radix primitive
1 parent 0d901f8 commit 569133b

File tree

19 files changed

+250
-585
lines changed

19 files changed

+250
-585
lines changed

.changeset/long-cats-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@strapi/design-system': major
3+
---
4+
5+
chore!: refactor Checkbox to use radix primitive

docs/stories/00-getting started/migration guides/migration-v1-v2.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ for any of these components to understand better how to migrate your code.
204204

205205
- `Accordion`
206206
- `Avatar`
207+
- `Checkbox`
207208
- `Popover`
208209
- `Radio`
209210
- `Tabs`

docs/stories/03-inputs/Checkbox.mdx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import * as CheckboxStories from './Checkbox.stories';
1515

1616
## Overview
1717

18-
Checkboxes allow users to choose one or multiple values from a range of options.
18+
Checkboxes allow users to choose one or multiple values from a range of options. Use the
19+
[Radio](../?path=/docs/inputs-radio--docs) button component if the user should select only one value.
1920

2021
<ViewSource path="components/Checkbox" />
2122

@@ -27,10 +28,6 @@ Checkboxes allow users to choose one or multiple values from a range of options.
2728
import { Checkbox } from '@strapi/design-system';
2829
```
2930

30-
- Use the [Radio](../?path=/docs/inputs-radio--docs) button component if the user should select only one value.
31-
- Checkbox labels are aligned differently based on language direction: to the right in LTR languages and to the left in
32-
RTL languages.
33-
3431
## Props
3532

3633
<TypeTable of={Checkbox} />
@@ -41,11 +38,12 @@ import { Checkbox } from '@strapi/design-system';
4138

4239
<Canvas of={CheckboxStories.Base} />
4340

44-
### Checkbox Group
41+
### Indeterminate
4542

46-
Partially selected options display as an indeterminate state at parent level
43+
The `checked` prop can be set to either a `boolean` value denoting checked or unchecked, or to `'indeterminate'`. This
44+
is useful when the checkbox is parent of a checkbox group and some, but not all, of the children checkboxes are checked.
4745

48-
<Canvas of={CheckboxStories.CheckboxGroup} />
46+
<Canvas of={CheckboxStories.Indeterminate} />
4947

5048
### Disabled
5149

@@ -54,14 +52,21 @@ another action first.
5452

5553
<Canvas of={CheckboxStories.Disabled} />
5654

57-
### Checkbox field
55+
### Without Label
56+
57+
Some instances, such as being used in a table, you may want to have a checkbox without the visual label. If this is the
58+
case, you should instead supply the `aria-label` prop so it is still accessible.
59+
60+
<Canvas of={CheckboxStories.WithoutLabel} />
61+
62+
### With Name & Label
5863

59-
Wrap the input in the necessary [`Field`](../?path=/docs/components-field--docs) component to create a powerful complete
60-
form input
64+
If using in a typical `form` element, you can provide a `name` and `value` prop that will be added to the `FormData` on
65+
submission. The story below fires a unique action `onSubmit` demonstrating this. However, if you're not using it within
66+
a `form`, you can omit this.
6167

62-
<Canvas of={CheckboxStories.WithField} />
68+
<Canvas of={CheckboxStories.WithNameAndValue} />
6369

6470
## Accessibility
6571

66-
- Checkbox has role of checkbox.
67-
- When Checkbox has focus, `Space` or `Enter` activates/deactivates it.
72+
Adheres to the [tri-state Checkbox WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/).
Lines changed: 53 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as React from 'react';
22

3-
import { useArgs } from '@storybook/preview-api';
43
import { Meta, StoryObj } from '@storybook/react';
5-
import { Checkbox, Field, Flex } from '@strapi/design-system';
4+
import { fn } from '@storybook/test';
5+
import { Button, Checkbox, Flex } from '@strapi/design-system';
66
import { default as outdent } from 'outdent';
77

88
const meta: Meta<typeof Checkbox> = {
@@ -15,37 +15,22 @@ const meta: Meta<typeof Checkbox> = {
1515
</Flex>
1616
),
1717
],
18+
args: {
19+
children: 'Remember me',
20+
onCheckedChange: fn(),
21+
},
1822
parameters: {
1923
chromatic: { disableSnapshot: false },
2024
},
25+
render: (args) => {
26+
return <Checkbox {...args} />;
27+
},
2128
};
2229
export default meta;
2330

2431
type Story = StoryObj<typeof Checkbox>;
2532

26-
const Template: Story = {
27-
render: ({ children, checked, ...props }) => {
28-
const [, updateArgs] = useArgs();
29-
30-
const handleChange = () => {
31-
updateArgs({ checked: !checked });
32-
};
33-
34-
return (
35-
<Checkbox {...props} value={checked} onChange={handleChange}>
36-
{children}
37-
</Checkbox>
38-
);
39-
},
40-
};
41-
4233
export const Base = {
43-
...Template,
44-
args: {
45-
children: 'Label',
46-
checked: false,
47-
},
48-
4934
parameters: {
5035
docs: {
5136
source: {
@@ -59,186 +44,67 @@ export const Base = {
5944
name: 'base',
6045
};
6146

62-
export const CheckboxGroup = {
63-
render: () => {
64-
const [fruitsChecked, setFruitsChecked] = React.useState([true, false]);
65-
const [veggiesChecked, setVeggiesChecked] = React.useState([true, true]);
66-
const [sweetsChecked, setSweetsChecked] = React.useState([false, false]);
67-
const allFruitsChecked = fruitsChecked.every(Boolean);
68-
const allVeggiesChecked = veggiesChecked.every(Boolean);
69-
const allSweetsChecked = sweetsChecked.every(Boolean);
70-
const isFruitsIndeterminate = fruitsChecked.some(Boolean) && !allFruitsChecked;
71-
const isVeggiesIndeterminate = veggiesChecked.some(Boolean) && !allVeggiesChecked;
72-
const isSweetsIndeterminate = sweetsChecked.some(Boolean) && !allSweetsChecked;
73-
74-
return (
75-
<ul>
76-
<li>
77-
<Checkbox
78-
id="fruits"
79-
name="fruits"
80-
indeterminate={isFruitsIndeterminate}
81-
onValueChange={(value) => setFruitsChecked([value, value])}
82-
value={allFruitsChecked}
83-
>
84-
Fruits
85-
</Checkbox>
86-
</li>
87-
<li>
88-
<ul style={{ paddingLeft: '24px' }}>
89-
<li>
90-
<Checkbox
91-
id="apple"
92-
name="apple"
93-
onValueChange={(value) => setFruitsChecked([value, fruitsChecked[1]])}
94-
value={fruitsChecked[0]}
95-
>
96-
Apple
97-
</Checkbox>
98-
</li>
99-
<li>
100-
<Checkbox
101-
id="banana"
102-
name="banana"
103-
onValueChange={(value) => setFruitsChecked([fruitsChecked[0], value])}
104-
value={fruitsChecked[1]}
105-
>
106-
Banana
107-
</Checkbox>
108-
</li>
109-
</ul>
110-
</li>
111-
<li>
112-
<Checkbox
113-
id="vegetables"
114-
name="vegetables"
115-
indeterminate={isVeggiesIndeterminate}
116-
onValueChange={(value) => setVeggiesChecked([value, value])}
117-
value={allVeggiesChecked}
118-
>
119-
Vegetables
120-
</Checkbox>
121-
</li>
122-
<li>
123-
<ul style={{ paddingLeft: '24px' }}>
124-
<li>
125-
<Checkbox
126-
id="beans"
127-
name="beans"
128-
onValueChange={(value) => setVeggiesChecked([value, veggiesChecked[1]])}
129-
value={veggiesChecked[0]}
130-
>
131-
Beans
132-
</Checkbox>
133-
</li>
134-
<li>
135-
<Checkbox
136-
id="pumpkin"
137-
name="pumpkin"
138-
onValueChange={(value) => setVeggiesChecked([veggiesChecked[0], value])}
139-
value={veggiesChecked[1]}
140-
>
141-
Pumpkin
142-
</Checkbox>
143-
</li>
144-
</ul>
145-
</li>
146-
<li>
147-
<Checkbox
148-
id="sweets"
149-
name="sweets"
150-
indeterminate={isSweetsIndeterminate}
151-
onValueChange={(value) => setSweetsChecked([value, value])}
152-
value={allSweetsChecked}
153-
>
154-
Sweets
155-
</Checkbox>
156-
</li>
157-
<li>
158-
<ul style={{ paddingLeft: '24px' }}>
159-
<li>
160-
<Checkbox
161-
id="chocolate"
162-
name="chocolate"
163-
onValueChange={(value) => setSweetsChecked([value, sweetsChecked[1]])}
164-
value={sweetsChecked[0]}
165-
>
166-
Chocolate
167-
</Checkbox>
168-
</li>
169-
<li>
170-
<Checkbox
171-
id="candy"
172-
name="candy"
173-
onValueChange={(value) => setSweetsChecked([sweetsChecked[0], value])}
174-
value={sweetsChecked[1]}
175-
>
176-
Candy
177-
</Checkbox>
178-
</li>
179-
</ul>
180-
</li>
181-
</ul>
182-
);
47+
export const Indeterminate = {
48+
argTypes: {
49+
checked: {
50+
control: 'select',
51+
options: [true, false, 'indeterminate'],
52+
},
18353
},
184-
185-
name: 'checkbox group',
54+
args: {
55+
checked: 'indeterminate',
56+
},
57+
name: 'indeterminate',
18658
} satisfies Story;
18759

18860
export const Disabled = {
189-
...Template,
19061
args: {
191-
...Base.args,
19262
disabled: true,
19363
},
19464
name: 'disabled',
195-
19665
parameters: {
19766
docs: {
19867
source: {
199-
code: '<Checkbox disabled>Label</Checkbox>',
68+
code: '<Checkbox disabled>Remember me</Checkbox>',
20069
},
20170
},
20271
},
20372
} satisfies Story;
20473

205-
export const WithField = {
206-
render: ({ checked, error, hint, children }) => {
207-
const [, updateArgs] = useArgs();
208-
209-
const handleChange = () => {
210-
updateArgs({ checked: !checked });
211-
};
212-
213-
return (
214-
<Field.Root id="with_field" error={error} hint={hint}>
215-
<Checkbox value={checked} onChange={handleChange}>
216-
{children}
217-
</Checkbox>
218-
<Field.Error />
219-
<Field.Hint />
220-
</Field.Root>
221-
);
74+
export const WithoutLabel = {
75+
args: {
76+
children: null,
77+
['aria-label']: 'Select row 1',
22278
},
79+
name: 'without label',
80+
} satisfies Story;
81+
82+
export const WithNameAndValue = {
22383
args: {
224-
...Base.args,
225-
error: 'Error',
226-
hint: 'Description line lorem ipsum',
84+
name: 'rememberMe',
85+
value: 'yes',
86+
onSubmit: fn(),
22787
},
228-
parameters: {
229-
docs: {
230-
source: {
231-
code: outdent`
232-
<Field.Root id="with_field" error={error} hint={hint}>
233-
<Checkbox value={checked} onChange={handleChange}>
234-
{children}
235-
</Checkbox>
236-
<Field.Error />
237-
<Field.Hint />
238-
</Field.Root>
239-
`,
240-
},
241-
},
88+
render: ({ onSubmit, ...args }) => {
89+
return (
90+
<Flex
91+
tag="form"
92+
direction="column"
93+
gap={4}
94+
alignItems="flex-start"
95+
onSubmit={(e) => {
96+
e.preventDefault();
97+
if (onSubmit) {
98+
const data = new FormData(e.target as HTMLFormElement);
99+
// @ts-expect-error – We're demonstrating `value` and `name` here.
100+
onSubmit(data.get('rememberMe'));
101+
}
102+
}}
103+
>
104+
<Checkbox {...args} />
105+
<Button type="submit">Submit</Button>
106+
</Flex>
107+
);
242108
},
243-
name: 'with field',
244-
};
109+
name: 'with name and value',
110+
} satisfies Story;

0 commit comments

Comments
 (0)