Skip to content

Commit 14fc4d6

Browse files
committed
feat(checkbox): migrate to css modules & better props
1 parent fffe29f commit 14fc4d6

7 files changed

Lines changed: 844 additions & 201 deletions

File tree

apps/docs/stories/checkbox.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Meta, Controls, Primary } from '@storybook/addon-docs/blocks';
2+
import * as CheckboxStories from './checkbox.stories';
3+
4+
<Meta of={CheckboxStories} />
5+
6+
# Checkbox
7+
8+
A customizable checkbox component for user selections.
9+
10+
## How to use
11+
12+
```tsx
13+
import { Checkbox } from '@signozhq/ui';
14+
15+
export default function MyComponent() {
16+
return <Checkbox>Default checkbox</Checkbox>
17+
}
18+
```
19+
20+
This code above will render the following checkbox:
21+
22+
<Primary />
23+
24+
<Controls of={CheckboxStories.Default} />
Lines changed: 127 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,94 @@
11
import { Checkbox } from '@signozhq/ui';
22
import type { Meta, StoryObj } from '@storybook/react-vite';
3-
import { generateDocs } from '../utils/generateDocs.js';
4-
5-
const checkboxExamples = [
6-
`import { Checkbox } from '@signozhq/ui';
7-
8-
export default function MyComponent() {
9-
return (
10-
<div className="space-y-2">
11-
<Checkbox id="terms" labelName="Accept terms and conditions" />
12-
<Checkbox id="newsletter" labelName="Subscribe to newsletter" defaultChecked />
13-
<Checkbox id="disabled" labelName="Disabled option" disabled />
14-
</div>
15-
);
16-
}`,
17-
];
18-
19-
const checkboxDocs = generateDocs({
20-
packageName: '@signozhq/ui',
21-
description:
22-
'A customizable checkbox component for user selections with support for disabled states.',
23-
examples: checkboxExamples,
24-
});
253

264
const meta: Meta<typeof Checkbox> = {
27-
title: 'Old Components/Checkbox',
5+
title: 'Components/Checkbox',
286
component: Checkbox,
297
argTypes: {
30-
labelName: {
8+
children: {
319
control: 'text',
10+
description:
11+
'The content inside the checkbox. Typically used for adding text or other elements alongside the checkbox.',
12+
table: { category: 'Content' },
13+
},
14+
color: {
15+
control: 'select',
16+
options: [
17+
'primary',
18+
'success',
19+
'warning',
20+
'error',
21+
'robin',
22+
'forest',
23+
'amber',
24+
'sienna',
25+
'cherry',
26+
'sakura',
27+
'aqua',
28+
],
29+
description:
30+
'The color theme of the checkbox. Each color has semantic meaning for different use cases.',
31+
table: { category: 'Appearance', defaultValue: { summary: 'primary' } },
3232
},
3333
disabled: {
3434
control: 'boolean',
35+
description: 'Prevents user interaction and displays the checkbox in a disabled state.',
36+
table: { category: 'Behavior', defaultValue: { summary: 'false' } },
37+
},
38+
required: {
39+
control: 'boolean',
40+
description:
41+
'When true, indicates that the user must check the checkbox before the owning form can be submitted.',
42+
table: {
43+
category: 'Behavior',
44+
defaultValue: { summary: 'false' },
45+
type: { summary: 'boolean' },
46+
},
3547
},
3648
id: {
3749
control: 'text',
50+
description:
51+
'A unique identifier for the checkbox. Links the checkbox with its label for accessibility.',
52+
table: { category: 'Accessibility' },
3853
},
39-
color: {
54+
name: {
55+
control: 'text',
56+
description:
57+
'The name of the checkbox. Submitted with its owning form as part of a name/value pair.',
58+
table: { category: 'Form', type: { summary: 'string' } },
59+
},
60+
value: {
4061
control: 'select',
41-
options: ['robin', 'forest', 'amber', 'sienna', 'cherry', 'sakura', 'aqua'],
42-
description: 'The color variant of the checkbox',
62+
options: [true, false, 'indeterminate'],
63+
description:
64+
'The controlled checked state of the checkbox. Use when you need to control its checked state.',
65+
table: { category: 'Form', type: { summary: 'boolean' } },
66+
},
67+
defaultValue: {
68+
control: 'select',
69+
options: [true, false, 'indeterminate'],
70+
description:
71+
'The initial checked state of the checkbox. Use when you do not need to control its checked state.',
72+
table: { category: 'Form', defaultValue: { summary: 'false' } },
73+
},
74+
testId: {
75+
control: 'text',
76+
description: 'The testId associated with the checkbox for testing purposes.',
77+
table: { category: 'Testing', type: { summary: 'string' } },
78+
},
79+
className: {
80+
control: 'text',
81+
description: 'Additional CSS classes to apply to the checkbox.',
82+
table: { category: 'Styling', type: { summary: 'string' } },
83+
},
84+
onChange: {
85+
control: false,
86+
description: 'The callback invoked when the checked state of the checkbox changes.',
87+
table: { category: 'Events', type: { summary: '(checked: CheckedState) => void' } },
4388
},
4489
},
4590
parameters: {
4691
layout: 'fullscreen',
47-
docs: {
48-
description: {
49-
component: checkboxDocs,
50-
},
51-
},
5292
design: [
5393
{
5494
name: 'Figma',
@@ -67,39 +107,65 @@ type Story = StoryObj<typeof Checkbox>;
67107
export const Default: Story = {
68108
args: {
69109
id: 'default',
70-
labelName: 'Default checkbox',
71-
defaultChecked: false,
110+
children: 'Default checkbox',
111+
defaultValue: false,
72112
disabled: false,
73-
color: 'robin',
113+
required: false,
114+
color: 'primary',
74115
},
75116
};
76117

77-
export const Checked: Story = {
78-
args: {
79-
id: 'filled',
80-
labelName: 'Filled checkbox',
81-
defaultChecked: true,
82-
disabled: false,
83-
color: 'robin',
84-
},
85-
};
118+
export const AllVariants: Story = {
119+
render: () => (
120+
<div className="space-y-4">
121+
{[
122+
'primary',
123+
'success',
124+
'warning',
125+
'error',
126+
'robin',
127+
'forest',
128+
'amber',
129+
'sienna',
130+
'cherry',
131+
'sakura',
132+
'aqua',
133+
].map((c) => (
134+
<div key={c} className="flex items-center gap-6">
135+
<div style={{ width: 120 }} className="capitalize">
136+
{c}
137+
</div>
86138

87-
export const Disabled: Story = {
88-
args: {
89-
id: 'disabled',
90-
labelName: 'Disabled checkbox',
91-
defaultChecked: false,
92-
disabled: true,
93-
color: 'robin',
94-
},
95-
};
139+
<Checkbox id={`checkbox-${c}-default`} color={c as any}>
140+
Default
141+
</Checkbox>
96142

97-
export const DisabledChecked: Story = {
98-
args: {
99-
id: 'disabled-filled',
100-
labelName: 'Disabled Filled',
101-
defaultChecked: true,
102-
disabled: true,
103-
color: 'robin',
104-
},
143+
<Checkbox id={`checkbox-${c}-checked`} defaultValue={true} color={c as any}>
144+
Checked
145+
</Checkbox>
146+
147+
<Checkbox
148+
id={`checkbox-${c}-indeterminate`}
149+
defaultValue={'indeterminate' as any}
150+
color={c as any}
151+
>
152+
Indeterminate
153+
</Checkbox>
154+
155+
<Checkbox id={`checkbox-${c}-disabled`} disabled={true} color={c as any}>
156+
Disabled
157+
</Checkbox>
158+
159+
<Checkbox
160+
id={`checkbox-${c}-disabled-checked`}
161+
defaultValue={true}
162+
disabled={true}
163+
color={c as any}
164+
>
165+
Disabled Checked
166+
</Checkbox>
167+
</div>
168+
))}
169+
</div>
170+
),
105171
};

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"jsdom": "^28.1.0",
4444
"postcss": "^8.4.47",
4545
"publint": "^0.3.17",
46+
"sass-embedded": "^1.97.3",
4647
"tailwindcss": "^4.1.4",
4748
"vitest": "^4.0.18"
4849
},
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
.checkbox {
2+
height: var(--checkbox-size, 1rem);
3+
width: var(--checkbox-size, 1rem);
4+
flex-shrink: 0;
5+
border-radius: var(--checkbox-border-radius, calc(var(--radius) - 2px));
6+
border: 2px solid var(--checkbox-border-color, var(--l3-background));
7+
cursor: pointer;
8+
background-color: transparent;
9+
display: flex;
10+
align-items: center;
11+
justify-content: center;
12+
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
13+
14+
&[data-color="robin"] {
15+
--checkbox-checked-background: var(--bg-robin-500);
16+
}
17+
18+
&[data-color="forest"] {
19+
--checkbox-checked-background: var(--bg-forest-500);
20+
}
21+
22+
&[data-color="amber"] {
23+
--checkbox-checked-background: var(--bg-amber-500);
24+
}
25+
26+
&[data-color="sienna"] {
27+
--checkbox-checked-background: var(--bg-sienna-500);
28+
}
29+
30+
&[data-color="cherry"] {
31+
--checkbox-checked-background: var(--bg-cherry-500);
32+
}
33+
34+
&[data-color="sakura"] {
35+
--checkbox-checked-background: var(--bg-sakura-500);
36+
}
37+
38+
&[data-color="aqua"] {
39+
--checkbox-checked-background: var(--bg-aqua-500);
40+
}
41+
42+
&:not([data-state="indeterminate"]) {
43+
&[data-color="forest"],
44+
&[data-color="amber"],
45+
&[data-color="sienna"],
46+
&[data-color="cherry"],
47+
&[data-color="sakura"],
48+
&[data-color="aqua"] {
49+
--checkbox-checked-icon-foreground: var(--bg-neutral-dark-1000);
50+
}
51+
}
52+
53+
&:focus-visible {
54+
outline-offset: -1px;
55+
outline: 2px solid var(--checkbox-checked-background);
56+
}
57+
58+
&:hover {
59+
border-color: var(--checkbox-checked-background);
60+
}
61+
62+
&:disabled {
63+
cursor: not-allowed;
64+
opacity: 0.5;
65+
66+
&:hover {
67+
box-shadow: none;
68+
border-color: var(--border);
69+
}
70+
}
71+
72+
&[data-state="checked"] {
73+
background-color: var(--checkbox-checked-background);
74+
border-color: transparent;
75+
}
76+
77+
&__indicator {
78+
display: flex;
79+
align-items: center;
80+
justify-content: center;
81+
color: currentColor;
82+
}
83+
84+
&__icon {
85+
height: calc(var(--checkbox-size, 1rem) - var(--spacing-1, 0.25rem));
86+
width: calc(var(--checkbox-size, 1rem) - var(--spacing-1, 0.25rem));
87+
color: var(--checkbox-checked-icon-foreground, var(--bg-neutral-dark-50));
88+
padding-left: calc(var(--checkbox-size, 1rem) / 16);
89+
}
90+
91+
&[data-state="indeterminate"] .checkbox__icon {
92+
height: calc(var(--checkbox-size, 1rem) - var(--spacing-2, 0.5rem));
93+
width: calc(var(--checkbox-size, 1rem) - var(--spacing-2, 0.5rem));
94+
}
95+
96+
&__icon--slash {
97+
display: none;
98+
}
99+
100+
&[data-state="indeterminate"] {
101+
.checkbox__icon--slash {
102+
display: flex;
103+
}
104+
.checkbox__icon--check {
105+
display: none;
106+
}
107+
}
108+
}
109+
110+
.checkbox-wrapper {
111+
display: flex;
112+
align-items: center;
113+
gap: 0.5rem;
114+
cursor: pointer;
115+
116+
&__label {
117+
cursor: pointer;
118+
user-select: none;
119+
}
120+
}
121+
122+
.checkbox:disabled ~ .checkbox-wrapper__label {
123+
cursor: not-allowed;
124+
opacity: 0.7;
125+
}

0 commit comments

Comments
 (0)