Skip to content

Commit c52d8e1

Browse files
committed
CheckBox Component
1 parent 52bc31c commit c52d8e1

8 files changed

Lines changed: 205 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.checkbox {
2+
cursor: pointer;
3+
appearance: none;
4+
width: 18px;
5+
height: 18px;
6+
border: 2px solid #ccc;
7+
border-radius: 4px;
8+
background-color: #fff;
9+
transition: border-color 0.2s, box-shadow 0.2s;
10+
11+
&:hover {
12+
border-color: var(--text-secondary);
13+
box-shadow: 0 0 0 3px var(--primary-200);
14+
}
15+
16+
&:focus-visible {
17+
outline: none;
18+
border-color: var(--primary-800);
19+
box-shadow: 0 0 0 3px var(--primary-200);
20+
}
21+
22+
&:checked {
23+
background-color: var(--primary-400);
24+
border-color: var(--primary-400);
25+
26+
&::after {
27+
content: '';
28+
display: block;
29+
width: 4px;
30+
height: 8px;
31+
border: solid var(--text-primary);
32+
border-width: 0 2px 2px 0;
33+
transform: rotate(45deg);
34+
margin: 2px auto;
35+
}
36+
}
37+
38+
&-small {
39+
width: 14px;
40+
height: 14px;
41+
}
42+
&-medium {
43+
width: 18px;
44+
height: 18px;
45+
}
46+
&-large {
47+
width: 24px;
48+
height: 24px;
49+
}
50+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as React from 'react';
2+
import type { Meta, StoryFn } from '@storybook/react';
3+
import CheckBox from './CheckBox';
4+
5+
export default {
6+
title: 'Components/CheckBox',
7+
component: CheckBox,
8+
decorators: [(story) => <div className="container">{story()}</div>],
9+
} as Meta<typeof CheckBox>;
10+
11+
export const Template: StoryFn<typeof CheckBox> = (args) => (
12+
<CheckBox {...args} checked />
13+
);
14+
15+
export const Controlled: StoryFn<typeof CheckBox> = (args) => {
16+
const [isChecked, setIsChecked] = React.useState(false);
17+
return (
18+
<CheckBox
19+
checked={isChecked}
20+
onChange={(e) => setIsChecked(e.target.checked)}
21+
{...args}
22+
/>
23+
);
24+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import CheckBox from './CheckBox';
4+
5+
describe('CheckBox', () => {
6+
it('renders without crashing', () => {
7+
render(<CheckBox />);
8+
});
9+
10+
it('renders a checkbox input element', () => {
11+
render(<CheckBox />);
12+
const checkbox = screen.getByRole('checkbox');
13+
expect(checkbox).toBeInTheDocument();
14+
});
15+
16+
it('toggles when clicked (uncontrolled)', async () => {
17+
render(<CheckBox checked={false} />);
18+
const checkbox = screen.getByRole('checkbox');
19+
20+
expect(checkbox).not.toBeChecked();
21+
22+
await userEvent.click(checkbox);
23+
24+
expect(checkbox).toBeChecked();
25+
});
26+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import * as React from 'react';
2+
import clsx from 'clsx';
3+
import { useToggle } from '../../../Hooks';
4+
import { useFormContext } from '../FormControl';
5+
import { callAll } from '../../Utils/AllFunctionsCall';
6+
import './CheckBox.scss';
7+
8+
/**
9+
* Props for the CheckBox component.
10+
*
11+
* Extends standard HTML `<input type="checkbox">` props and includes
12+
* support for size-based styling and form context integration.
13+
*
14+
* @property boxSize - Defines the visual size of the checkbox. Options are `'small'`, `'medium'`, or `'large'`.
15+
*/
16+
export interface CheckBoxProps extends React.ComponentPropsWithoutRef<'input'> {
17+
/** Defines the visual size of the checkbox. */
18+
boxSize?: 'small' | 'medium' | 'large';
19+
}
20+
21+
/**
22+
* A reusable checkbox component that supports both controlled and uncontrolled behavior.
23+
* It also integrates with the `FormControl` context to support validation, error states,
24+
* and form-level change tracking.
25+
*
26+
* @example
27+
* // Controlled usage
28+
* <CheckBox checked={isChecked} onChange={(e) => setChecked(e.target.checked)} />
29+
*
30+
* @example
31+
* // Uncontrolled usage
32+
* <CheckBox defaultChecked />
33+
*
34+
*/
35+
function CheckBox({
36+
checked,
37+
boxSize = 'medium',
38+
className,
39+
...props
40+
}: CheckBoxProps) {
41+
const { value: checkValue, toggleValue: toggleCheck } = useToggle(checked);
42+
const { isAlert, inputChange } = useFormContext();
43+
44+
const HandleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
45+
if (e.key === 'Enter') {
46+
toggleCheck();
47+
48+
// Manually fire synthetic change for FormControl context
49+
const syntheticEvent = {
50+
target: { value: String(!checkValue) },
51+
} as React.ChangeEvent<HTMLInputElement>;
52+
53+
if (inputChange) inputChange(syntheticEvent);
54+
}
55+
};
56+
57+
const HandleOnClick = (e: React.MouseEvent<HTMLInputElement>) => {
58+
toggleCheck();
59+
};
60+
61+
const { onKeyDown, onClick, onChange, ...resetProps } = props;
62+
63+
return (
64+
<input
65+
className={clsx(`checkbox checkbox-${boxSize}`, className, {
66+
'checkbox-alert': isAlert,
67+
})}
68+
type="checkbox"
69+
tabIndex={0}
70+
onKeyDown={callAll(onKeyDown, HandleKeyDown)}
71+
onClick={callAll(onClick, HandleOnClick)}
72+
onChange={callAll(onChange, inputChange)}
73+
value={String(checkValue)}
74+
checked={checkValue}
75+
{...resetProps}
76+
/>
77+
);
78+
}
79+
80+
export default CheckBox;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as CheckBox } from './CheckBox';
2+
export type { CheckBoxProps } from './CheckBox';

src/lib/Components/FormControl/FormControl.stories.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { TextArea } from '../../Components/TextArea';
66
import { Slider } from '../Slider';
77
import { Select } from '../Select';
88
import { Options } from '../Select';
9+
import { CheckBox } from '../CheckBox';
910
import type { Meta, StoryFn } from '@storybook/react';
1011
import { Template as Switch } from '../Switch/Switch.stories';
1112
import { useToggle } from '../../../Hooks';
@@ -156,3 +157,23 @@ export const SelectControl: StoryFn<typeof FormControl> = (args) => (
156157
// </FormControl>
157158
// );
158159
// };
160+
161+
export const CheckBoxControl: StoryFn<typeof FormControl> = (args) => {
162+
return (
163+
<FormControl
164+
style={{
165+
display: 'flex',
166+
alignItems: 'center',
167+
gap: '1rem',
168+
}}
169+
//
170+
validate={(value) =>
171+
value !== 'false' ? 'Please check Agreement' : ''
172+
}
173+
{...args}
174+
>
175+
<CheckBox boxSize="medium" checked />
176+
<Label htmlFor="Data field">Agreement</Label>
177+
</FormControl>
178+
);
179+
};

src/lib/Components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ export * from './Subheading';
3636
export * from './Paragraph';
3737
export * from './Blockquote';
3838
export * from './PinInput';
39+
export * from './CheckBox';

todo.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- [x] Refactor Accordion
3030
- [x] Calendar
3131
- [x] Pin Input
32+
- [x] CheckBox
3233

3334
### helper Components
3435

0 commit comments

Comments
 (0)