Skip to content

Commit 217b733

Browse files
Merge pull request #698 from glints-dev/feature/add-next-checkbox
Add next checkbox
2 parents c539920 + 6f01575 commit 217b733

25 files changed

Lines changed: 374 additions & 0 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
"classnames": "^2.2.6",
130130
"downshift": "^6.1.3",
131131
"moment": "^2.24.0",
132+
"nanoid": "^4.0.0",
132133
"react-id-generator": "^3.0.1",
133134
"styled-system": "^5.1.5"
134135
},
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
import { Story, Meta } from '@storybook/react';
3+
4+
import { Checkbox, CheckboxProps } from './Checkbox';
5+
import { BaseContainer } from '../../Layout/GlintsContainer/GlintsContainer';
6+
7+
(Checkbox as React.FunctionComponent<CheckboxProps>).displayName = 'Checkbox';
8+
9+
export default {
10+
title: '@next/Checkbox',
11+
component: Checkbox,
12+
decorators: [Story => <BaseContainer>{Story()}</BaseContainer>],
13+
} as Meta;
14+
15+
const Template: Story<CheckboxProps> = args => <Checkbox {...args} />;
16+
17+
export const Interactive = Template.bind({});
18+
Interactive.args = { label: 'Label' };

src/@next/Checkbox/Checkbox.tsx

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import { nanoid } from 'nanoid';
3+
import {
4+
StyledCheckbox,
5+
StyledCheckboxContainer,
6+
StyledColumn,
7+
StyledContainer,
8+
StyledLeftColumn,
9+
StyledRow,
10+
} from './CheckboxStyle';
11+
import { noop } from 'lodash-es';
12+
import { Icon } from '../Icon';
13+
import { Typography } from '../Typography';
14+
import { Neutral, Red, Blue } from '../utilities/colors';
15+
16+
export interface CheckboxProps
17+
extends Omit<
18+
React.HtmlHTMLAttributes<HTMLInputElement>,
19+
'type' | 'onChange'
20+
> {
21+
id?: string;
22+
label?: string;
23+
hasError?: boolean;
24+
indeterminate?: boolean;
25+
checked?: boolean;
26+
disabled?: boolean;
27+
helpText?: string;
28+
onChange?(newChecked: boolean, id: string): void;
29+
}
30+
31+
const randomId = nanoid();
32+
33+
export const Checkbox = ({
34+
label,
35+
id,
36+
checked,
37+
onChange,
38+
disabled,
39+
indeterminate,
40+
hasError,
41+
helpText,
42+
...otherProps
43+
}: CheckboxProps) => {
44+
const checkBoxId = id ? id : randomId;
45+
const labelId = `label-${checkBoxId}`;
46+
47+
const inputNode = useRef<HTMLInputElement>(null);
48+
const [checkedState, setCheckedState] = useState<'mixed' | boolean>(false);
49+
const [isFocused, setIsFocused] = useState(false);
50+
const checkedIcon = indeterminate
51+
? 'ri-checkbox-indeterminate-fill'
52+
: 'ri-checkbox-fill';
53+
const iconFill = disabled ? Neutral.B95 : hasError ? Red.B93 : Blue.S99;
54+
55+
useEffect(() => {
56+
const value = indeterminate ? 'mixed' : checked;
57+
setCheckedState(value);
58+
}, [checked, indeterminate]);
59+
60+
const handleClick = () => {
61+
if (!onChange || !inputNode.current || disabled) {
62+
return;
63+
}
64+
65+
const checkedValue = !checkedState;
66+
setCheckedState(checkedValue);
67+
onChange(checkedValue, checkBoxId);
68+
};
69+
70+
return (
71+
<StyledContainer aria-disabled={disabled}>
72+
<StyledRow>
73+
<StyledLeftColumn>
74+
<StyledCheckboxContainer>
75+
<StyledCheckbox
76+
role="checkbox"
77+
aria-labelledby={labelId}
78+
aria-checked={checkedState}
79+
data-focus={isFocused}
80+
onClick={() => handleClick()}
81+
onMouseDown={() => setIsFocused(false)}
82+
onMouseUp={() => setIsFocused(false)}
83+
hasError={hasError}
84+
>
85+
<input
86+
ref={inputNode}
87+
disabled={disabled}
88+
id={checkBoxId}
89+
type="checkbox"
90+
onChange={noop}
91+
checked={checked}
92+
onFocus={() => setIsFocused(true)}
93+
onBlur={() => setIsFocused(false)}
94+
{...otherProps}
95+
/>
96+
</StyledCheckbox>
97+
<Icon
98+
name={checkedIcon}
99+
height="20px"
100+
width="20px"
101+
fill={iconFill}
102+
/>
103+
</StyledCheckboxContainer>
104+
</StyledLeftColumn>
105+
<StyledColumn>
106+
<label
107+
id={labelId}
108+
htmlFor={checkBoxId}
109+
onClick={e => {
110+
e.preventDefault();
111+
handleClick();
112+
}}
113+
onMouseDown={e => e.preventDefault()}
114+
onMouseUp={e => e.preventDefault()}
115+
>
116+
{
117+
<Typography as="div" variant="body1">
118+
{label}
119+
</Typography>
120+
}
121+
</label>
122+
</StyledColumn>
123+
</StyledRow>
124+
<StyledRow>
125+
<StyledLeftColumn></StyledLeftColumn>
126+
<StyledColumn className="help-text">
127+
<Typography as="span" variant="subtitle2">
128+
{helpText}
129+
</Typography>
130+
</StyledColumn>
131+
</StyledRow>
132+
</StyledContainer>
133+
);
134+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import styled from 'styled-components';
2+
import { Breakpoints } from '../..';
3+
import { borderRadius4 } from '../utilities/borderRadius';
4+
import { Neutral, Red } from '../utilities/colors';
5+
import { space4 } from '../utilities/spacing';
6+
7+
import { CheckboxProps } from './Checkbox';
8+
9+
export const StyledContainer = styled.div`
10+
flex-direction: row;
11+
align-items: flex-start;
12+
padding: ${space4} 0px;
13+
gap: 8px;
14+
color: ${Neutral.B18};
15+
16+
label {
17+
cursor: pointer;
18+
margin-top: -1px;
19+
}
20+
21+
svg {
22+
border-radius: 4px;
23+
position: absolute;
24+
top: 0;
25+
left: 0;
26+
opacity: 0;
27+
pointer-events: none;
28+
}
29+
30+
&[aria-disabled='true'],
31+
&[aria-disabled='true'] .help-text {
32+
color: ${Neutral.B68};
33+
}
34+
&[aria-disabled='true'] div[role='checkbox'] {
35+
border-color: ${Neutral.B85};
36+
background: ${Neutral.B95};
37+
}
38+
&[aria-disabled='true'] svg {
39+
background: ${Neutral.B68};
40+
}
41+
`;
42+
43+
export const StyledCheckboxContainer = styled.div`
44+
position: relative;
45+
cursor: pointer;
46+
`;
47+
48+
export const StyledCheckbox = styled.div<CheckboxProps>`
49+
width: 20px;
50+
height: 20px;
51+
background: ${Neutral.B100};
52+
border: 2px solid;
53+
border-color: ${({ hasError }: CheckboxProps) => {
54+
return hasError ? Red.B93 : Neutral.B68;
55+
}};
56+
cursor: pointer;
57+
border-radius: ${borderRadius4};
58+
> input {
59+
opacity: 0;
60+
cursor: pointer;
61+
}
62+
&[data-focus='true'] {
63+
box-shadow: 0px 0px 0px 1px ${Neutral.B100}, 0px 0px 0px 3px #6ac9ec;
64+
}
65+
66+
&[aria-checked='true'] + svg,
67+
&[aria-checked='mixed'] + svg {
68+
opacity: 1;
69+
}
70+
71+
@media (max-width: ${Breakpoints.large}) {
72+
width: 18px;
73+
height: 18px;
74+
}
75+
`;
76+
77+
export const StyledRow = styled.div`
78+
display: flex;
79+
flex-direction: row;
80+
flex-wrap: wrap;
81+
gap: 8px;
82+
`;
83+
export const StyledColumn = styled.div`
84+
display: flex;
85+
flex-direction: column;
86+
&.help-text {
87+
color: ${Neutral.B40};
88+
}
89+
`;
90+
91+
export const StyledLeftColumn = styled(StyledColumn)`
92+
flex-basis: 20px;
93+
`;

src/@next/Checkbox/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Checkbox';

src/@next/Icon/icons/icons.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ export const iconNames = [
5959
'ri-send-plane-line',
6060
'ri-settings-fill',
6161
'ri-settings-line',
62+
'ri-checkbox-fill',
63+
'ri-checkbox-line',
64+
'ri-checkbox-indeterminate-fill',
65+
'ri-checkbox-indeterminate-line',
6266
] as const;
6367

6468
export type IconNames = typeof iconNames[number];
@@ -122,4 +126,8 @@ export const iconsMappingComponent: { [name in IconNames]: SVGComponent } = {
122126
['ri-send-plane-line']: Icons.RiSendPlaneLine,
123127
['ri-settings-fill']: Icons.RiSettingsFill,
124128
['ri-settings-line']: Icons.RiSettingsLine,
129+
['ri-checkbox-fill']: Icons.RiCheckboxFill,
130+
['ri-checkbox-line']: Icons.RiCheckboxLine,
131+
['ri-checkbox-indeterminate-fill']: Icons.RiCheckboxIndeterminateFill,
132+
['ri-checkbox-indeterminate-line']: Icons.RiCheckboxIndeterminateLine,
125133
};
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)