GrafanaUI: Add indeterminate state to Checkbox (#67312)

This commit is contained in:
Joao Silva 2023-04-27 15:18:23 +02:00 committed by GitHub
parent 6d8f9c5bf4
commit 48933e121f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 89 additions and 16 deletions

View File

@ -9,22 +9,27 @@ import { Checkbox } from './Checkbox';
Checked represents true, un-checked represent false. So you can use them to select a binary option or multiple options in a set. `Checkbox` can be used in groups, where the single checkboxes have no dependencies. That means that selecting one doesnt affect any other `Checkbox`. When adding a description to your `Checkbox`, write positive statements so that "checked" means "yes" and not "no". That way, you can avoid confusion.
**DO:** [ ] Hide options
**DON'T:** [ ] Do not show options
- **DO:** [ ] Hide options
- **DON'T:** [ ] Do not show options
Checkboxes typically only trigger changes after sending a form. If your component should trigger a change immediately, it's better to use a toggle switch instead. Furthermore, checkboxes are not mutually exclusive. That means that selecting one will not disable the others or impact them in any other way. If you want to offer a mutually exclusive choice, use `RadioButtonGroup` or a `Select` dropdown.
**DO:**
Show series
[ ] A-series
[ ] B-series
[ ] C-series
**DO:** Show series
**DON'T:**
Show only
[ ] A-series
[ ] B-series
[ ] C-series
- [ ] A-series
- [ ] B-series
- [ ] C-series
**DON'T:** Show only
- [ ] A-series
- [ ] B-series
- [ ] C-series
The indeterminate state of the checkbox should be used when there is a group of child checkboxes that are in a mix of checked and unchecked states. For instance when you have a list of checkboxes representing the columns of a table, and you want to allow the user to select which columns to display. If some of the columns are checked, and some are unchecked, then the parent checkbox should be in an indeterminate state. If all the columns are checked, then the parent should be checked. If none of the columns are checked, then the parent should be unchecked.
It is discouraged to set both `indeterminate` and `checked` state as a checkboxs emit boolean values which can only ever be TRUE or FALSE, and if something is partially true, then it is false.
- **DON'T:** `<Checkbox checked indeterminate />`
### Usage

View File

@ -37,6 +37,7 @@ Basic.args = {
label: 'Skip TLS cert validation',
description: 'Set to true if you want to skip TLS cert validation',
disabled: false,
indeterminate: false,
};
export const StackedList = () => {
@ -78,6 +79,33 @@ InAField.args = {
description:
'Annotation queries can be toggled on or of at the top of the dashboard. With this option checked this toggle will be hidden.',
disabled: false,
indeterminate: false,
};
export const AllStates: ComponentStory<typeof Checkbox> = (args) => {
const [checked, setChecked] = useState(false);
const onChange = useCallback(
(e: React.FormEvent<HTMLInputElement>) => setChecked(e.currentTarget.checked),
[setChecked]
);
return (
<div>
<VerticalGroup>
<Checkbox value={checked} onChange={onChange} {...args} />
<Checkbox value={true} label="Checked" />
<Checkbox value={false} label="Unchecked" />
<Checkbox value={false} indeterminate={true} label="Interdeterminate" />
</VerticalGroup>
</div>
);
};
AllStates.args = {
label: 'Props set from controls',
description: 'Set to true if you want to skip TLS cert validation',
disabled: false,
indeterminate: false,
};
export default meta;

View File

@ -9,15 +9,20 @@ import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
import { getLabelStyles } from './Label';
export interface CheckboxProps extends Omit<HTMLProps<HTMLInputElement>, 'value'> {
/** Label to display next to checkbox */
label?: string;
/** Description to display under the label */
description?: string;
/** Current value of the checkbox */
value?: boolean;
// htmlValue allows to specify the input "value" attribute
/** htmlValue allows to specify the input "value" attribute */
htmlValue?: string | number;
/** Sets the checkbox into a "mixed" state. This is only a visual change and does not affect the value. */
indeterminate?: boolean;
}
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
({ label, description, value, htmlValue, onChange, disabled, className, ...inputProps }, ref) => {
({ label, description, value, htmlValue, onChange, disabled, className, indeterminate, ...inputProps }, ref) => {
const handleOnChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (onChange) {
@ -28,16 +33,19 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
);
const styles = useStyles2(getCheckboxStyles);
const ariaChecked = indeterminate ? 'mixed' : undefined;
return (
<label className={cx(styles.wrapper, className)}>
<div className={styles.checkboxWrapper}>
<input
type="checkbox"
className={styles.input}
className={cx(styles.input, indeterminate && styles.inputIndeterminate)}
checked={value}
disabled={disabled}
onChange={handleOnChange}
value={htmlValue}
aria-checked={ariaChecked}
{...inputProps}
ref={ref}
/>
@ -89,7 +97,6 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme2) => {
* for angular components styling
* */
&:checked + span {
background: blue;
background: ${theme.colors.primary.main};
border: none;
@ -124,6 +131,39 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme2) => {
}
}
`,
inputIndeterminate: css`
&[aria-checked='mixed'] + span {
border: none;
background: ${theme.colors.primary.main};
&:hover {
background: ${theme.colors.primary.shade};
}
&:after {
content: '';
position: absolute;
z-index: 2;
left: 3px;
right: 3px;
top: calc(50% - 1.5px);
height: 3px;
border: 1.5px solid ${theme.colors.primary.contrastText};
background-color: ${theme.colors.primary.contrastText};
width: auto;
transform: none;
}
}
&:disabled[aria-checked='mixed'] + span {
background-color: ${theme.colors.action.disabledBackground};
&:after {
border-color: ${theme.colors.action.disabledText};
}
}
`,
checkboxWrapper: css`
display: flex;
align-items: center;