Skip to content

Commit a744480

Browse files
committed
IBX-7999: Checkbox and ThreeStateCheckbox
1 parent 270cc10 commit a744480

File tree

14 files changed

+502
-1
lines changed

14 files changed

+502
-1
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
@use '../functions' as *;
2+
@use '../variables' as *;
3+
4+
.ids-checkbox {
5+
.ids-input--checkbox {
6+
-webkit-appearance: none;
7+
-moz-appearance: none;
8+
appearance: none;
9+
width: calculateRem(16px);
10+
height: calculateRem(16px);
11+
border-radius: calculateRem(2px);
12+
padding: 0;
13+
margin: 0;
14+
cursor: pointer;
15+
position: relative;
16+
17+
&::after {
18+
content: ' ';
19+
position: absolute;
20+
top: calculateRem(2px);
21+
left: calculateRem(2px);
22+
display: block;
23+
width: calculateRem(8px);
24+
height: calculateRem(5px);
25+
border-left: calculateRem(2px) solid transparent;
26+
border-bottom: calculateRem(2px) solid transparent;
27+
transform: rotate(-45deg);
28+
}
29+
30+
&:checked {
31+
border-color: var(--ids-input-checkbox-checked-border-color, #{$color-primary-80});
32+
background-color: var(--ids-input-checkbox-checked-bg-color, #{$color-primary-80});
33+
34+
&::after {
35+
border-color: var(--ids-input-checkbox-checked-text-color, #{$color-neutral-10});
36+
}
37+
38+
&:disabled {
39+
border-color: var(--ids-input-checkbox-checked-disabled-bg-color, #{$color-primary-60});
40+
background-color: var(--ids-input-checkbox-checked-disabled-bg-color, #{$color-primary-60});
41+
42+
&::after {
43+
border-color: var(--ids-input-checkbox-checked-text-color, #{$color-neutral-10});
44+
}
45+
}
46+
}
47+
48+
&:indeterminate {
49+
border-color: var(--ids-input-checkbox-checked-border-color, #{$color-primary-80});
50+
background-color: var(--ids-input-checkbox-checked-bg-color, #{$color-primary-80});
51+
52+
&:disabled {
53+
border-color: var(--ids-input-checkbox-checked-disabled-bg-color, #{$color-primary-60});
54+
background-color: var(--ids-input-checkbox-checked-disabled-bg-color, #{$color-primary-60});
55+
}
56+
57+
&::after {
58+
border-color: var(--ids-input-checkbox-checked-text-color, #{$color-neutral-10});
59+
background-color: var(--ids-input-checkbox-checked-text-color, #{$color-neutral-10});
60+
height: calculateRem(2px);
61+
top: 50%;
62+
transform: translateY(-50%);
63+
}
64+
}
65+
}
66+
}

packages/assets/src/scss/styles.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
@use 'label';
1212
@use 'helper-text';
1313

14+
@use 'inputs/checkbox';
1415
@use 'inputs/input-text';
1516

1617
@use 'ui/clear-btn';
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { action } from 'storybook/actions';
3+
4+
import Checkbox from './Checkbox';
5+
6+
const meta: Meta<typeof Checkbox> = {
7+
component: Checkbox,
8+
parameters: {
9+
layout: 'centered',
10+
},
11+
tags: ['autodocs', 'foundation', 'inputs'],
12+
argTypes: {
13+
className: {
14+
control: 'text',
15+
},
16+
title: {
17+
control: 'text',
18+
},
19+
value: {
20+
control: 'text',
21+
},
22+
},
23+
args: {
24+
onBlur: action('on-blur'),
25+
onChange: action('on-change'),
26+
onFocus: action('on-focus'),
27+
onInput: action('on-input'),
28+
},
29+
};
30+
31+
export default meta;
32+
33+
type Story = StoryObj<typeof Checkbox>;
34+
35+
export const Empty: Story = {
36+
name: 'Empty',
37+
args: {
38+
name: 'default-input',
39+
},
40+
};
41+
42+
export const EmptyDisabled: Story = {
43+
name: 'Empty (Disabled)',
44+
args: {
45+
name: 'default-input',
46+
disabled: true,
47+
},
48+
};
49+
50+
export const EmptyError: Story = {
51+
name: 'Empty (Error)',
52+
args: {
53+
name: 'default-input',
54+
error: true,
55+
},
56+
};
57+
58+
export const Checked: Story = {
59+
name: 'Checked',
60+
args: {
61+
name: 'default-input',
62+
checked: true,
63+
},
64+
};
65+
66+
export const CheckedDisabled: Story = {
67+
name: 'Checked (Disabled)',
68+
args: {
69+
name: 'default-input',
70+
checked: true,
71+
disabled: true,
72+
},
73+
};
74+
75+
export const CheckedError: Story = {
76+
name: 'Checked (Error)',
77+
args: {
78+
name: 'default-input',
79+
checked: true,
80+
error: true,
81+
},
82+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { expect, fn, userEvent, within } from 'storybook/test';
3+
4+
import Checkbox from './Checkbox';
5+
6+
const meta: Meta<typeof Checkbox> = {
7+
component: Checkbox,
8+
parameters: {
9+
layout: 'centered',
10+
},
11+
tags: ['!dev'],
12+
args: {
13+
name: 'default-input',
14+
onBlur: fn(),
15+
onChange: fn(),
16+
onFocus: fn(),
17+
onInput: fn(),
18+
},
19+
};
20+
21+
export default meta;
22+
23+
type Story = StoryObj<typeof Checkbox>;
24+
25+
const NUMBER_OF_CLICKS_FOCUS = 2;
26+
27+
export const Default: Story = {
28+
name: 'Default',
29+
play: async ({ canvasElement, step, args }) => {
30+
const canvas = within(canvasElement);
31+
const input = canvas.getByRole('checkbox');
32+
33+
await step('Checkbox handles focus event', async () => {
34+
await expect(args.onFocus).not.toHaveBeenCalled();
35+
36+
await userEvent.click(input);
37+
38+
await expect(args.onFocus).toHaveBeenCalledOnce();
39+
40+
await userEvent.click(input);
41+
42+
await expect(args.onFocus).toHaveBeenCalledOnce();
43+
await expect(args.onChange).toHaveBeenCalledTimes(NUMBER_OF_CLICKS_FOCUS);
44+
await expect(args.onInput).toHaveBeenCalledTimes(NUMBER_OF_CLICKS_FOCUS);
45+
});
46+
47+
await step('Checkbox handles blur event', async () => {
48+
await expect(args.onBlur).not.toHaveBeenCalled();
49+
50+
await userEvent.click(canvasElement);
51+
52+
await expect(args.onBlur).toHaveBeenCalledOnce();
53+
});
54+
},
55+
};
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
3+
import BaseInput from '@ids-internal/partials/BaseInput';
4+
import { createCssClassNames } from '@ids-internal/shared/css.class.names';
5+
6+
import { CheckboxProps } from './Checkbox.types';
7+
8+
const Checkbox = ({
9+
name,
10+
onBlur = () => undefined,
11+
onChange = () => undefined,
12+
onFocus = () => undefined,
13+
onInput = () => undefined,
14+
checked = false,
15+
className = '',
16+
disabled = false,
17+
error = false,
18+
extraAria = {},
19+
id = undefined,
20+
ref,
21+
required = false,
22+
title = '',
23+
value = '',
24+
}: CheckboxProps) => {
25+
const checkboxClassName = createCssClassNames({
26+
'ids-checkbox': true,
27+
[className]: true,
28+
});
29+
const componentOnBlur = (event: React.FocusEvent<HTMLInputElement>) => {
30+
onBlur(event);
31+
};
32+
const componentOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
33+
onChange(event.target.checked, event);
34+
};
35+
const componentOnFocus = (event: React.FocusEvent<HTMLInputElement>) => {
36+
onFocus(event);
37+
};
38+
const componentOnInput = (event: React.ChangeEvent<HTMLInputElement>) => {
39+
onInput(event.target.checked, event);
40+
};
41+
42+
return (
43+
<div className={checkboxClassName}>
44+
<BaseInput
45+
disabled={disabled}
46+
error={error}
47+
extraInputAttrs={{
48+
checked,
49+
onBlur: componentOnBlur,
50+
onChange: componentOnChange,
51+
onFocus: componentOnFocus,
52+
onInput: componentOnInput,
53+
...extraAria,
54+
}}
55+
id={id}
56+
name={name}
57+
ref={ref}
58+
required={required}
59+
title={title}
60+
type="checkbox"
61+
value={value}
62+
/>
63+
</div>
64+
);
65+
};
66+
67+
export default Checkbox;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Ref } from 'react';
2+
3+
import { BaseComponentAriaAttributes } from '@ids-types/general';
4+
5+
export interface CheckboxProps extends BaseComponentAriaAttributes {
6+
name: string;
7+
onBlur?: React.FocusEventHandler<HTMLInputElement>;
8+
onChange?: (value: boolean, event?: React.ChangeEvent<HTMLInputElement>) => void;
9+
onFocus?: React.FocusEventHandler<HTMLInputElement>;
10+
onInput?: (value: boolean, event?: React.ChangeEvent<HTMLInputElement>) => void;
11+
checked?: boolean;
12+
disabled?: boolean;
13+
error?: boolean;
14+
id?: string;
15+
ref?: Ref<HTMLInputElement>;
16+
required?: boolean;
17+
value?: string | number;
18+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Checkbox from './Checkbox';
2+
import { CheckboxProps } from './Checkbox.types';
3+
4+
export default Checkbox;
5+
export type { CheckboxProps };

0 commit comments

Comments
 (0)