Skip to content

Commit ddccb7b

Browse files
authored
Checkbox: support indeterminate state (#640)
* feat(Checkbox): support indeterminate state * docs: include radix-ui props into storybook autogeneration * docs(Checkbox): improve storybook * refactor(Checkbox): move default values to declaration
1 parent 48f0835 commit ddccb7b

File tree

3 files changed

+56
-42
lines changed

3 files changed

+56
-42
lines changed

.storybook/main.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import type { StorybookConfig } from "@storybook/react-vite";
22
const config: StorybookConfig = {
33
core: {
4-
disableTelemetry: true
4+
disableTelemetry: true,
55
},
66
stories: [
7-
"../src/Introduction.mdx",
7+
"../src/Introduction.mdx",
88
"../src/components/icons/Icons.mdx",
99
"../src/**/*.stories.@(js|jsx|ts|tsx)",
1010
],
1111
addons: [
1212
"@storybook/addon-links",
1313
"@storybook/addon-essentials",
1414
{
15-
name: '@storybook/addon-storysource',
15+
name: "@storybook/addon-storysource",
1616
options: {
1717
loaderOptions: {
1818
prettierConfig: { printWidth: 80, singleQuote: false },
@@ -31,5 +31,18 @@ const config: StorybookConfig = {
3131
autodocs: "tag",
3232
},
3333
staticDirs: ["../public"],
34+
typescript: {
35+
reactDocgen: "react-docgen-typescript",
36+
reactDocgenTypescriptOptions: {
37+
compilerOptions: {
38+
allowSyntheticDefaultImports: false,
39+
esModuleInterop: false,
40+
},
41+
shouldRemoveUndefinedFromOptional: true,
42+
shouldExtractLiteralValuesFromEnum: true,
43+
propFilter: prop =>
44+
prop.parent ? !/node_modules\/(?!@radix-ui)/.test(prop.parent.fileName) : true,
45+
},
46+
},
3447
};
3548
export default config;
Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,38 @@
1+
import { useEffect, useState } from "react";
2+
3+
import { Meta, StoryObj } from "@storybook/react";
4+
15
import { Checkbox } from "./Checkbox";
26

3-
const CheckboxComponent = ({
4-
checked,
5-
...props
6-
}: {
7-
checked: "default" | "checked" | "unchecked";
8-
disabled: boolean;
9-
label?: string;
10-
}) => {
11-
return (
12-
<Checkbox
13-
checked={checked === "default" ? undefined : checked === "checked"}
14-
{...props}
15-
/>
16-
);
17-
};
18-
export default {
19-
component: CheckboxComponent,
7+
const meta: Meta<typeof Checkbox> = {
8+
component: Checkbox,
209
title: "Forms/Checkbox",
2110
tags: ["checkbox", "autodocs"],
2211
argTypes: {
23-
checked: { control: "radio", options: ["default", "checked", "unchecked"] },
24-
disabled: { control: "boolean" },
25-
label: { control: "text" },
26-
orientation: { control: "inline-radio", options: ["horizontal", "vertical"] },
27-
dir: { control: "inline-radio", options: ["start", "end"] },
28-
variant: {
29-
control: "radio",
30-
options: ["default", "var1", "var2", "var3", "var4", "var5", "var6"],
31-
},
12+
checked: { control: "radio", options: [true, false, "indeterminate"] },
13+
defaultChecked: { control: "radio", options: [true, false, "indeterminate"] },
14+
},
15+
render: ({ checked, ...props }) => {
16+
const [checkedState, setCheckedState] = useState(checked);
17+
18+
useEffect(() => {
19+
setCheckedState(checked);
20+
}, [checked]);
21+
22+
return (
23+
<Checkbox
24+
{...props}
25+
checked={checkedState}
26+
onCheckedChange={setCheckedState}
27+
/>
28+
);
3229
},
3330
};
3431

35-
export const Playground = {
32+
export default meta;
33+
34+
export const Playground: StoryObj<typeof Checkbox> = {
3635
args: {
3736
label: "Accept terms and conditions",
38-
disabled: false,
39-
checked: "default",
4037
},
4138
};

src/components/Checkbox/Checkbox.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,31 @@ const Wrapper = styled(FormRoot)`
2828
export const Checkbox = ({
2929
id,
3030
label,
31-
variant,
31+
variant = "default",
3232
disabled,
33-
orientation,
34-
dir,
33+
orientation = "horizontal",
34+
dir = "end",
35+
checked,
3536
...delegated
3637
}: CheckboxProps) => {
3738
const defaultId = useId();
3839
return (
3940
<Wrapper
40-
$orientation={orientation ?? "horizontal"}
41-
$dir={dir ?? "end"}
41+
$orientation={orientation}
42+
$dir={dir}
4243
>
4344
<CheckInput
4445
id={id ?? defaultId}
4546
data-testid="checkbox"
46-
variant={variant ?? "default"}
47+
variant={variant}
4748
disabled={disabled}
4849
aria-label={`${label}`}
50+
checked={checked}
4951
{...delegated}
5052
>
5153
<CheckIconWrapper>
5254
<Icon
53-
name="check"
55+
name={checked === "indeterminate" ? "minus" : "check"}
5456
size="sm"
5557
/>
5658
</CheckIconWrapper>
@@ -75,7 +77,7 @@ const CheckInput = styled(RadixCheckbox.Root)<{
7577
justify-content: center;
7678
flex-shrink: 0;
7779
78-
${({ theme, variant = "default" }) => `
80+
${({ theme, variant }) => `
7981
border-radius: ${theme.click.checkbox.radii.all};
8082
width: ${theme.click.checkbox.size.all};
8183
height: ${theme.click.checkbox.size.all};
@@ -86,15 +88,17 @@ const CheckInput = styled(RadixCheckbox.Root)<{
8688
&:hover {
8789
background: ${theme.click.checkbox.color.variations[variant].background.hover};
8890
}
89-
&[data-state="checked"] {
91+
&[data-state="checked"],
92+
&[data-state="indeterminate"] {
9093
border-color: ${theme.click.checkbox.color.variations[variant].stroke.active};
9194
background: ${theme.click.checkbox.color.variations[variant].background.active};
9295
}
9396
&[data-disabled] {
9497
background: ${theme.click.checkbox.color.background.disabled};
9598
border-color: ${theme.click.checkbox.color.stroke.disabled};
9699
cursor: not-allowed;
97-
&[data-state="checked"] {
100+
&[data-state="checked"],
101+
&[data-state="indeterminate"] {
98102
background: ${theme.click.checkbox.color.background.disabled};
99103
border-color: ${theme.click.checkbox.color.stroke.disabled};
100104
}

0 commit comments

Comments
 (0)