Skip to content

Commit

Permalink
fix: improve usability of checkbox, checkbox group, radio and radio g…
Browse files Browse the repository at this point in the history
…roup component (EightfoldAI#156)
  • Loading branch information
ychhabra-eightfold authored May 31, 2022
1 parent b2a3812 commit 69650ac
Show file tree
Hide file tree
Showing 19 changed files with 753 additions and 586 deletions.
4 changes: 2 additions & 2 deletions src/__snapshots__/storybook.test.js.snap
Git LFS file not shown
39 changes: 39 additions & 0 deletions src/components/ConfigProvider/ConfigProvider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ConfigProvider, OcThemeNames, useConfig } from './';
import { MatchScore } from '../MatchScore';
import { Spinner } from '../Spinner';
import { Stack } from '../Stack';
import { CheckBoxGroup, CheckboxValueType, RadioGroup } from '../Selectors';

const theme: OcThemeNames[] = [
'red',
Expand Down Expand Up @@ -206,6 +207,44 @@ const ThemedComponents: FC = () => {
</Tabs>
<MatchScore score={3} />
<Spinner />
<CheckBoxGroup
{...{
value: ['First'],
defaultChecked: ['First'],
items: [
{
name: 'group',
value: 'First',
label: 'First',
id: 'test-1',
},
{
name: 'group',
value: 'Second',
label: 'Second',
id: 'test-2',
},
{
name: 'group',
value: 'Third',
label: 'Third',
id: 'test-3',
},
],
}}
/>
<RadioGroup
{...{
ariaLabel: 'Radio Group',
value: 'Radio1',
items: [1, 2, 3].map((i) => ({
value: `Radio${i}`,
label: `Radio${i}`,
name: 'group',
id: `oea2exk-${i}`,
})),
}}
/>
</Stack>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/Inputs/input.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@

.text-area {
background-color: var(--background-color);
border: 1px solid --border-color;
border: 1px solid var(--border-color);
border-radius: $corner-radius-s;
box-sizing: border-box;
color: var(--grey-color-60);
Expand Down
35 changes: 21 additions & 14 deletions src/components/Selectors/CheckBox/CheckBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import React, { useState } from 'react';
import { Stories } from '@storybook/addon-docs';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { CheckBox, CheckBoxGroup } from '../index';
import { CheckBox, CheckBoxGroup } from '../';
import { CheckboxValueType } from './Checkbox.types';

export default {
title: 'Check Box',
Expand Down Expand Up @@ -74,10 +75,6 @@ export default {
defaultValue: true,
control: { type: 'boolean' },
},
defaultChecked: {
defaultValue: false,
control: { type: 'boolean' },
},
},
} as ComponentMeta<typeof CheckBox>;

Expand All @@ -87,9 +84,16 @@ const CheckBox_Story: ComponentStory<typeof CheckBox> = (args) => (

export const Check_Box = CheckBox_Story.bind({});

const CheckBoxGroup_Story: ComponentStory<typeof CheckBox> = (args) => (
<CheckBoxGroup {...args} />
);
const CheckBoxGroup_Story: ComponentStory<typeof CheckBoxGroup> = (args) => {
const [selected, setSelected] = useState<CheckboxValueType[]>([]);
return (
<CheckBoxGroup
{...args}
value={selected}
onChange={(newSelected) => setSelected([...newSelected])}
/>
);
};

export const Check_Box_Group = CheckBoxGroup_Story.bind({});

Expand All @@ -99,33 +103,36 @@ const checkBoxArgs: Object = {
classNames: 'my-checkbox-class',
disabled: false,
name: 'myCheckBoxName',
value: 'Label',
value: 'label',
label: 'Label',
id: 'myCheckBoxId',
defaultChecked: false,
};

Check_Box.args = {
...checkBoxArgs,
};

Check_Box_Group.args = {
...checkBoxArgs,
value: ['First'],
defaultChecked: ['First'],
items: [
{
checked: true,
name: 'group',
value: 'First',
label: 'First',
id: 'test-1',
},
{
checked: true,
name: 'group',
value: 'Second',
label: 'Second',
id: 'test-2',
},
{
checked: true,
name: 'group',
value: 'Third',
label: 'Third',
id: 'test-3',
},
],
Expand Down
129 changes: 75 additions & 54 deletions src/components/Selectors/CheckBox/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,79 @@
import React, { FC, useState } from 'react';
import { CheckBoxProps } from '../';
import { mergeClasses, generateId } from '../../../shared/utilities';
import React, { FC, Ref, useEffect, useRef, useState } from 'react';
import { generateId, mergeClasses } from '../../../shared/utilities';
import { CheckboxProps } from './Checkbox.types';

import styles from './checkbox.module.scss';

export const CheckBox: FC<CheckBoxProps> = ({
ariaLabel,
checked = false,
defaultChecked,
disabled = false,
name,
value = '',
id,
onChange,
}) => {
const [checkBoxId] = useState<string>(id || generateId());
const [isChecked, setIsChecked] = useState<boolean>(checked);

const checkBoxCheckClassNames: string = mergeClasses([
styles.checkmark,
{ [styles.disabled]: disabled },
]);

const toggleChecked = (): void => {
if (!disabled) setIsChecked(!isChecked);
};

const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key !== 'Tab') event.preventDefault();
if (event.key === 'Enter' || event.key === ' ') toggleChecked();
};

return (
<div className={styles.selector} onKeyDown={handleKeyDown}>
<input
aria-label={ariaLabel}
checked={isChecked}
defaultChecked={defaultChecked}
disabled={disabled}
id={checkBoxId}
onChange={onChange ? onChange : toggleChecked}
name={name}
tabIndex={-1}
type={'checkbox'}
value={value}
readOnly
/>
<label
htmlFor={checkBoxId}
className={value === '' ? styles.labelNoValue : ''}
export const CheckBox: FC<CheckboxProps> = React.forwardRef(
(
{
ariaLabel,
checked = false,
defaultChecked,
disabled = false,
name,
value,
id,
onChange,
label,
classNames,
style,
'data-test-id': dataTestId,
},
ref: Ref<HTMLInputElement>
) => {
const checkBoxId = useRef<string>(id || generateId());
const [isChecked, setIsChecked] = useState<boolean>(
defaultChecked || checked
);

useEffect(() => {
setIsChecked(checked);
}, [checked]);

const checkboxWrapperClassNames: string = mergeClasses([
styles.selector,
classNames,
]);

const checkBoxCheckClassNames: string = mergeClasses([
styles.checkmark,
]);

const labelClassNames: string = mergeClasses([
{ [styles.labelNoValue]: value === '' },
]);

const toggleChecked = (
e: React.ChangeEvent<HTMLInputElement>
): void => {
setIsChecked(e.target.checked);
onChange?.(e);
};

return (
<div
className={checkboxWrapperClassNames}
style={style}
data-test-id={dataTestId}
>
<span className={checkBoxCheckClassNames} tabIndex={0}></span>
<span className={styles.selectorLabel}>{value}</span>
</label>
</div>
);
};
<input
ref={ref}
aria-label={ariaLabel}
checked={isChecked}
disabled={disabled}
id={checkBoxId.current}
onChange={toggleChecked}
name={name}
type={'checkbox'}
value={value}
readOnly
/>
<label htmlFor={checkBoxId.current} className={labelClassNames}>
<span className={checkBoxCheckClassNames} />
<span className={styles.selectorLabel}>{label}</span>
</label>
</div>
);
}
);
69 changes: 45 additions & 24 deletions src/components/Selectors/CheckBox/CheckBoxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
import React, { FC } from 'react';
import { CheckBoxProps } from '../';
import React, { FC, Ref } from 'react';
import { CheckBox } from './CheckBox';
import { generateId } from '../../../shared/utilities';
import { mergeClasses } from '../../../shared/utilities';
import { CheckboxGroupProps } from './Checkbox.types';

export const CheckBoxGroup: FC<CheckBoxProps> = ({
defaultChecked = true,
items,
onChange,
}) => {
return (
<>
{items.map((item, index) => (
<CheckBox
ariaLabel={item.ariaLabel}
checked={item.checked ? item.checked : defaultChecked}
id={item.id || generateId()}
key={index}
name={item.name}
value={item.value}
onChange={onChange}
/>
))}
</>
);
};
import styles from './checkbox.module.scss';

export const CheckBoxGroup: FC<CheckboxGroupProps> = React.forwardRef(
(
{ items = [], onChange, value, classNames, style, ariaLabel, ...rest },
ref: Ref<HTMLDivElement>
) => {
const checkboxGroupClassNames = mergeClasses([
styles.checkboxGroup,
classNames,
]);

return (
<div
className={checkboxGroupClassNames}
style={style}
role="group"
aria-label={ariaLabel}
ref={ref}
{...rest}
>
{items.map((item) => (
<CheckBox
{...item}
checked={value?.includes(item.value)}
key={item.value}
onChange={() => {
const optionIndex = value?.indexOf(item.value);
const newValue = [...value];
if (optionIndex === -1) {
newValue.push(item.value);
} else {
newValue.splice(optionIndex, 1);
}
onChange?.(newValue);
}}
/>
))}
</div>
);
}
);
Loading

0 comments on commit 69650ac

Please sign in to comment.