mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Forms: introduce checkbox (#20701)
* introduce checkbox theme variables * Add checkbox component * Style tweaks * Namespace form styles returned from getFormStyles * Name fix
This commit is contained in:
@@ -237,6 +237,10 @@ export interface GrafanaTheme extends GrafanaThemeCommons {
|
|||||||
formSwitchBgHover: string;
|
formSwitchBgHover: string;
|
||||||
formSwitchBgDisabled: string;
|
formSwitchBgDisabled: string;
|
||||||
formSwitchDot: string;
|
formSwitchDot: string;
|
||||||
|
formCheckboxBg: string;
|
||||||
|
formCheckboxBgChecked: string;
|
||||||
|
formCheckboxBgCheckedHover: string;
|
||||||
|
formCheckboxCheckmark: string;
|
||||||
};
|
};
|
||||||
shadow: {
|
shadow: {
|
||||||
pageHeader: string;
|
pageHeader: string;
|
||||||
|
18
packages/grafana-ui/src/components/Forms/Checkbox.mdx
Normal file
18
packages/grafana-ui/src/components/Forms/Checkbox.mdx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
|
||||||
|
import { Checkbox } from './Checkbox';
|
||||||
|
|
||||||
|
<Meta title="MDX|Checkbox" component={Checkbox} />
|
||||||
|
|
||||||
|
# Checkbox
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Forms } from '@grafana/ui';
|
||||||
|
|
||||||
|
<Forms.Checkbox value={true|false} label={...} description={...} onChange={...} />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Props
|
||||||
|
<Props of={Checkbox} />
|
||||||
|
|
25
packages/grafana-ui/src/components/Forms/Checkbox.story.tsx
Normal file
25
packages/grafana-ui/src/components/Forms/Checkbox.story.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import mdx from './Checkbox.mdx';
|
||||||
|
import { Checkbox } from './Checkbox';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'UI/Forms/Checkbox',
|
||||||
|
component: Checkbox,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
page: mdx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const simple = () => {
|
||||||
|
const [checked, setChecked] = useState(false);
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
value={checked}
|
||||||
|
onChange={setChecked}
|
||||||
|
label="Skip SLL cert validation"
|
||||||
|
description="Set to true if you want to skip sll cert validation"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
129
packages/grafana-ui/src/components/Forms/Checkbox.tsx
Normal file
129
packages/grafana-ui/src/components/Forms/Checkbox.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import React, { HTMLProps } from 'react';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import { getLabelStyles } from './Label';
|
||||||
|
import { useTheme, stylesFactory } from '../../themes';
|
||||||
|
import { css, cx } from 'emotion';
|
||||||
|
import { getFocusCss } from './commonStyles';
|
||||||
|
|
||||||
|
export interface CheckboxProps extends Omit<HTMLProps<HTMLInputElement>, 'onChange' | 'value'> {
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
value: boolean;
|
||||||
|
onChange?: (checked: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
const labelStyles = getLabelStyles(theme);
|
||||||
|
const checkboxSize = '16px';
|
||||||
|
return {
|
||||||
|
label: cx(
|
||||||
|
labelStyles.label,
|
||||||
|
css`
|
||||||
|
padding-left: ${theme.spacing.formSpacingBase}px;
|
||||||
|
`
|
||||||
|
),
|
||||||
|
description: cx(
|
||||||
|
labelStyles.description,
|
||||||
|
css`
|
||||||
|
padding-left: ${theme.spacing.formSpacingBase}px;
|
||||||
|
`
|
||||||
|
),
|
||||||
|
wrapper: css`
|
||||||
|
position: relative;
|
||||||
|
padding-left: ${checkboxSize};
|
||||||
|
`,
|
||||||
|
input: css`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
&:focus + span {
|
||||||
|
${getFocusCss(theme)}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using adjacent sibling selector to style checked state.
|
||||||
|
* Primarily to limit the classes necessary to use when these classes will be used
|
||||||
|
* for angular components styling
|
||||||
|
* */
|
||||||
|
&:checked + span {
|
||||||
|
background: blue;
|
||||||
|
background: ${theme.colors.formCheckboxBgChecked};
|
||||||
|
border: none;
|
||||||
|
&:hover {
|
||||||
|
background: ${theme.colors.formCheckboxBgCheckedHover};
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 5px;
|
||||||
|
top: 1px;
|
||||||
|
width: 6px;
|
||||||
|
height: 12px;
|
||||||
|
border: solid ${theme.colors.formCheckboxCheckmark};
|
||||||
|
border-width: 0 3px 3px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
checkmark: css`
|
||||||
|
display: inline-block;
|
||||||
|
width: ${checkboxSize};
|
||||||
|
height: ${checkboxSize};
|
||||||
|
border-radius: ${theme.border.radius.sm};
|
||||||
|
margin-right: ${theme.spacing.formSpacingBase}px;
|
||||||
|
background: ${theme.colors.formCheckboxBg};
|
||||||
|
border: 1px solid ${theme.colors.formInputBorder};
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: 0;
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
border-color: ${theme.colors.formInputBorderHover};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Checkbox: React.FC<CheckboxProps> = ({
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
id,
|
||||||
|
disabled,
|
||||||
|
...inputProps
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getCheckboxStyles(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className={styles.wrapper}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className={styles.input}
|
||||||
|
id={id}
|
||||||
|
checked={value}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={event => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange(event.target.checked);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
<span className={styles.checkmark} />
|
||||||
|
{label && <span className={styles.label}>{label}</span>}
|
||||||
|
{description && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<span className={styles.description}>{description}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkbox.displayName = 'Checkbox';
|
@@ -9,6 +9,7 @@ import { Button } from './Button';
|
|||||||
import { Form } from './Form';
|
import { Form } from './Form';
|
||||||
import { Switch } from './Switch';
|
import { Switch } from './Switch';
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { Checkbox } from './Checkbox';
|
||||||
import { TextArea } from './TextArea/TextArea';
|
import { TextArea } from './TextArea/TextArea';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -22,6 +23,7 @@ export const users = () => {
|
|||||||
const [username, setUsername] = useState();
|
const [username, setUsername] = useState();
|
||||||
const [password, setPassword] = useState();
|
const [password, setPassword] = useState();
|
||||||
const [disabledUser, setDisabledUser] = useState(false);
|
const [disabledUser, setDisabledUser] = useState(false);
|
||||||
|
const [checked, setChecked] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -58,6 +60,14 @@ export const users = () => {
|
|||||||
<Field label="Disable" description="Added for testing purposes">
|
<Field label="Disable" description="Added for testing purposes">
|
||||||
<Switch checked={disabledUser} onChange={(_e, checked) => setDisabledUser(checked)} />
|
<Switch checked={disabledUser} onChange={(_e, checked) => setDisabledUser(checked)} />
|
||||||
</Field>
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<Checkbox
|
||||||
|
label="Skip SLL cert validation"
|
||||||
|
description="Set to true if you want to skip sll cert validation"
|
||||||
|
value={checked}
|
||||||
|
onChange={setChecked}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
<Button>Update</Button>
|
<Button>Update</Button>
|
||||||
</Form>
|
</Form>
|
||||||
<Form>
|
<Form>
|
||||||
|
@@ -20,7 +20,10 @@ export const getLabelStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
`,
|
`,
|
||||||
description: css`
|
description: css`
|
||||||
|
color: ${theme.colors.formLabel};
|
||||||
|
font-size: ${theme.typography.size.sm};
|
||||||
font-weight: ${theme.typography.weight.regular};
|
font-weight: ${theme.typography.weight.regular};
|
||||||
|
display: block;
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -31,8 +34,10 @@ export const Label: React.FC<LabelProps> = ({ children, description, className,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.label, className)}>
|
<div className={cx(styles.label, className)}>
|
||||||
<label {...labelProps}>{children}</label>
|
<label {...labelProps}>
|
||||||
{description && <div className={styles.description}>{description}</div>}
|
{children}
|
||||||
|
{description && <span className={styles.description}>{description}</span>}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,12 +1,16 @@
|
|||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
|
||||||
export const getFocusStyle = (theme: GrafanaTheme) => css`
|
export const getFocusCss = (theme: GrafanaTheme) => `
|
||||||
&:focus {
|
|
||||||
outline: 2px dotted transparent;
|
outline: 2px dotted transparent;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
box-shadow: 0 0 0 2px ${theme.colors.pageBg}, 0 0 0px 4px ${theme.colors.formFocusOutline};
|
box-shadow: 0 0 0 2px ${theme.colors.pageBg}, 0 0 0px 4px ${theme.colors.formFocusOutline};
|
||||||
transition: all 0.2s cubic-bezier(0.19, 1, 0.22, 1);
|
transition: all 0.2s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const getFocusStyle = (theme: GrafanaTheme) => css`
|
||||||
|
&:focus {
|
||||||
|
${getFocusCss(theme)}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@@ -7,20 +7,22 @@ import { getButtonStyles, ButtonVariant } from './Button';
|
|||||||
import { ButtonSize } from '../Button/types';
|
import { ButtonSize } from '../Button/types';
|
||||||
import { getInputStyles } from './Input/Input';
|
import { getInputStyles } from './Input/Input';
|
||||||
import { getSwitchStyles } from './Switch';
|
import { getSwitchStyles } from './Switch';
|
||||||
|
import { getCheckboxStyles } from './Checkbox';
|
||||||
|
|
||||||
export const getFormStyles = stylesFactory(
|
export const getFormStyles = stylesFactory(
|
||||||
(theme: GrafanaTheme, options: { variant: ButtonVariant; size: ButtonSize; invalid: boolean }) => {
|
(theme: GrafanaTheme, options: { variant: ButtonVariant; size: ButtonSize; invalid: boolean }) => {
|
||||||
return {
|
return {
|
||||||
...getLabelStyles(theme),
|
label: getLabelStyles(theme),
|
||||||
...getLegendStyles(theme),
|
legend: getLegendStyles(theme),
|
||||||
...getFieldValidationMessageStyles(theme),
|
fieldValidationMessage: getFieldValidationMessageStyles(theme),
|
||||||
...getButtonStyles({
|
button: getButtonStyles({
|
||||||
theme,
|
theme,
|
||||||
variant: options.variant,
|
variant: options.variant,
|
||||||
size: options.size,
|
size: options.size,
|
||||||
}),
|
}),
|
||||||
...getInputStyles({ theme, invalid: options.invalid }),
|
input: getInputStyles({ theme, invalid: options.invalid }),
|
||||||
...getSwitchStyles(theme),
|
switch: getSwitchStyles(theme),
|
||||||
|
checkbox: getCheckboxStyles(theme),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -99,6 +99,10 @@ const darkTheme: GrafanaTheme = {
|
|||||||
formSwitchBgActiveHover: basicColors.blueBase,
|
formSwitchBgActiveHover: basicColors.blueBase,
|
||||||
formSwitchBgDisabled: basicColors.gray25,
|
formSwitchBgDisabled: basicColors.gray25,
|
||||||
formSwitchDot: basicColors.gray15,
|
formSwitchDot: basicColors.gray15,
|
||||||
|
formCheckboxBg: basicColors.dark5,
|
||||||
|
formCheckboxBgChecked: basicColors.blueLight,
|
||||||
|
formCheckboxBgCheckedHover: basicColors.blueBase,
|
||||||
|
formCheckboxCheckmark: basicColors.gray25,
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
dropdown: basicColors.dark3,
|
dropdown: basicColors.dark3,
|
||||||
|
@@ -100,6 +100,10 @@ const lightTheme: GrafanaTheme = {
|
|||||||
formSwitchBgActiveHover: basicColors.blueBase,
|
formSwitchBgActiveHover: basicColors.blueBase,
|
||||||
formSwitchBgDisabled: basicColors.gray4,
|
formSwitchBgDisabled: basicColors.gray4,
|
||||||
formSwitchDot: basicColors.white,
|
formSwitchDot: basicColors.white,
|
||||||
|
formCheckboxBg: basicColors.white,
|
||||||
|
formCheckboxBgChecked: basicColors.blueShade,
|
||||||
|
formCheckboxBgCheckedHover: basicColors.blueBase,
|
||||||
|
formCheckboxCheckmark: basicColors.white,
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
dropdown: basicColors.white,
|
dropdown: basicColors.white,
|
||||||
|
Reference in New Issue
Block a user