Forms: Introduce new Primary, Secondary and Destructive buttons (#19973)

* Implement Label component

* Expose next-gen form components from grafana-ui under Forms namespace

* adding knobs to story, setting new variants and sizes

* handle next gen button in their own component

* removing duplication

* fix Thresholds test

* removing blank lines

* moving noUnusedLocals

* new button should not need theme

* remove not used export

* pseudo classes for focus state

* remove not used things

* use correct border radius

* extracting focus styles to commonStyles for reuse

* tidying up getButtonStyles

* Adding a few examples to the doc

* Adding props table
This commit is contained in:
Peter Holmberg 2019-10-25 14:35:29 +02:00 committed by GitHub
parent fc1ded5026
commit 24183ba390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 380 additions and 54 deletions

View File

@ -161,7 +161,7 @@
"jest": "jest --notify --watch", "jest": "jest --notify --watch",
"e2e-tests": "jest --runInBand --config=jest.config.e2e.js", "e2e-tests": "jest --runInBand --config=jest.config.e2e.js",
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js", "api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
"storybook": "cd packages/grafana-ui && yarn storybook", "storybook": "cd packages/grafana-ui && yarn storybook --ci",
"storybook:build": "cd packages/grafana-ui && yarn storybook:build", "storybook:build": "cd packages/grafana-ui && yarn storybook:build",
"prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"", "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"",
"prettier:write": "prettier --list-different \"**/*.{ts,tsx,scss}\" --write", "prettier:write": "prettier --list-different \"**/*.{ts,tsx,scss}\" --write",

View File

@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"include": ["../src/**/*.ts", "../src/**/*.tsx"],
"exclude": ["../dist", "../node_modules"],
"compilerOptions": {
"noUnusedLocals": false,
"declarationDir": "dist",
"outDir": "compiled"
}
}

View File

@ -9,13 +9,13 @@ module.exports = ({ config, mode }) => {
loader: require.resolve('ts-loader'), loader: require.resolve('ts-loader'),
options: { options: {
// transpileOnly: true, // transpileOnly: true,
configFile: path.resolve(__dirname, '../tsconfig.json'), configFile: path.resolve(__dirname, 'tsconfig.json'),
}, },
}, },
{ {
loader: require.resolve('react-docgen-typescript-loader'), loader: require.resolve('react-docgen-typescript-loader'),
options: { options: {
tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), tsconfigPath: path.resolve(__dirname, 'tsconfig.json'),
// https://github.com/styleguidist/react-docgen-typescript#parseroptions // https://github.com/styleguidist/react-docgen-typescript#parseroptions
// @ts-ignore // @ts-ignore
propFilter: prop => { propFilter: prop => {

View File

@ -1,33 +1,9 @@
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'; import React, { ComponentType, ReactNode } from 'react';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import { Themeable, GrafanaTheme } from '../../types'; import { selectThemeVariant, stylesFactory } from '../../themes';
import { selectThemeVariant } from '../../themes/selectThemeVariant'; import { AbstractButtonProps, ButtonSize, ButtonStyles, ButtonVariant, CommonButtonProps, StyleDeps } from './types';
import { stylesFactory } from '../../themes/stylesFactory'; import { GrafanaTheme } from '../../types';
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'inverse' | 'transparent';
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export interface CommonButtonProps {
size?: ButtonSize;
variant?: ButtonVariant;
/**
* icon prop is a temporary solution. It accepts lefacy icon class names for the icon to be rendered.
* TODO: migrate to a component when we are going to migrate icons to @grafana/ui
*/
icon?: string;
className?: string;
}
export interface LinkButtonProps extends CommonButtonProps, AnchorHTMLAttributes<HTMLAnchorElement> {
disabled?: boolean;
}
export interface ButtonProps extends CommonButtonProps, ButtonHTMLAttributes<HTMLButtonElement> {}
interface AbstractButtonProps extends CommonButtonProps, Themeable {
renderAs: React.ComponentType<CommonButtonProps> | string;
}
const buttonVariantStyles = ( const buttonVariantStyles = (
from: string, from: string,
@ -50,12 +26,6 @@ const buttonVariantStyles = (
} }
`; `;
interface StyleDeps {
theme: GrafanaTheme;
size: ButtonSize;
variant: ButtonVariant;
withIcon: boolean;
}
const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: StyleDeps) => { const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: StyleDeps) => {
const borderRadius = theme.border.radius.sm; const borderRadius = theme.border.radius.sm;
let padding, let padding,
@ -72,12 +42,14 @@ const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: Style
iconDistance = theme.spacing.xs; iconDistance = theme.spacing.xs;
height = theme.height.sm; height = theme.height.sm;
break; break;
case 'md': case 'md':
padding = `${theme.spacing.sm} ${theme.spacing.md}`; padding = `${theme.spacing.sm} ${theme.spacing.md}`;
fontSize = theme.typography.size.md; fontSize = theme.typography.size.md;
iconDistance = theme.spacing.sm; iconDistance = theme.spacing.sm;
height = theme.height.md; height = theme.height.md;
break; break;
case 'lg': case 'lg':
padding = `${theme.spacing.md} ${theme.spacing.lg}`; padding = `${theme.spacing.md} ${theme.spacing.lg}`;
fontSize = theme.typography.size.lg; fontSize = theme.typography.size.lg;
@ -85,6 +57,7 @@ const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: Style
iconDistance = theme.spacing.sm; iconDistance = theme.spacing.sm;
height = theme.height.lg; height = theme.height.lg;
break; break;
default: default:
padding = `${theme.spacing.sm} ${theme.spacing.md}`; padding = `${theme.spacing.sm} ${theme.spacing.md}`;
iconDistance = theme.spacing.sm; iconDistance = theme.spacing.sm;
@ -96,12 +69,15 @@ const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: Style
case 'primary': case 'primary':
background = buttonVariantStyles(theme.colors.greenBase, theme.colors.greenShade, theme.colors.white); background = buttonVariantStyles(theme.colors.greenBase, theme.colors.greenShade, theme.colors.white);
break; break;
case 'secondary': case 'secondary':
background = buttonVariantStyles(theme.colors.blueBase, theme.colors.blueShade, theme.colors.white); background = buttonVariantStyles(theme.colors.blueBase, theme.colors.blueShade, theme.colors.white);
break; break;
case 'danger': case 'danger':
background = buttonVariantStyles(theme.colors.redBase, theme.colors.redShade, theme.colors.white); background = buttonVariantStyles(theme.colors.redBase, theme.colors.redShade, theme.colors.white);
break; break;
case 'inverse': case 'inverse':
const from = selectThemeVariant({ light: theme.colors.gray5, dark: theme.colors.dark6 }, theme.type) as string; const from = selectThemeVariant({ light: theme.colors.gray5, dark: theme.colors.dark6 }, theme.type) as string;
const to = selectThemeVariant( const to = selectThemeVariant(
@ -118,6 +94,7 @@ const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: Style
background = buttonVariantStyles(from, to, theme.colors.link, 'rgba(0, 0, 0, 0.1)', true); background = buttonVariantStyles(from, to, theme.colors.link, 'rgba(0, 0, 0, 0.1)', true);
break; break;
case 'transparent': case 'transparent':
background = css` background = css`
${buttonVariantStyles('', '', theme.colors.link, 'rgba(0, 0, 0, 0.1)', true)}; ${buttonVariantStyles('', '', theme.colors.link, 'rgba(0, 0, 0, 0.1)', true)};
@ -164,23 +141,22 @@ const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: Style
}; };
}); });
export const AbstractButton: React.FunctionComponent<AbstractButtonProps> = ({ export const renderButton = (
renderAs, theme: GrafanaTheme,
theme, buttonStyles: ButtonStyles,
size = 'md', renderAs: ComponentType<CommonButtonProps> | string,
variant = 'primary', children: ReactNode,
className, size: ButtonSize,
icon, variant: ButtonVariant,
children, icon?: string,
...otherProps className?: string,
}) => { otherProps?: Partial<AbstractButtonProps>
const buttonStyles = getButtonStyles({ theme, size, variant, withIcon: !!icon }); ) => {
const nonHtmlProps = { const nonHtmlProps = {
theme, theme,
size, size,
variant, variant,
}; };
const finalClassName = cx(buttonStyles.button, className); const finalClassName = cx(buttonStyles.button, className);
const finalChildren = icon ? ( const finalChildren = icon ? (
<span className={buttonStyles.iconWrap}> <span className={buttonStyles.iconWrap}>
@ -208,4 +184,19 @@ export const AbstractButton: React.FunctionComponent<AbstractButtonProps> = ({
return React.createElement(renderAs, finalProps); return React.createElement(renderAs, finalProps);
}; };
export const AbstractButton: React.FunctionComponent<AbstractButtonProps> = ({
renderAs,
theme,
size = 'md',
variant = 'primary',
className,
icon,
children,
...otherProps
}) => {
const buttonStyles = getButtonStyles({ theme, size, variant, withIcon: !!icon });
return renderButton(theme, buttonStyles, renderAs, children, size, variant, icon, className, otherProps);
};
AbstractButton.displayName = 'AbstractButton'; AbstractButton.displayName = 'AbstractButton';

View File

@ -1,11 +1,11 @@
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { Button, LinkButton } from './Button'; import { Button, LinkButton } from './Button';
import { CommonButtonProps } from './AbstractButton';
// @ts-ignore // @ts-ignore
import withPropsCombinations from 'react-storybook-addon-props-combinations'; import withPropsCombinations from 'react-storybook-addon-props-combinations';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { ThemeableCombinationsRowRenderer } from '../../utils/storybook/CombinationsRowRenderer'; import { ThemeableCombinationsRowRenderer } from '../../utils/storybook/CombinationsRowRenderer';
import { select, boolean } from '@storybook/addon-knobs'; import { select, boolean } from '@storybook/addon-knobs';
import { CommonButtonProps } from './types';
const ButtonStories = storiesOf('UI/Button', module); const ButtonStories = storiesOf('UI/Button', module);
@ -15,7 +15,7 @@ const defaultProps = {
}; };
const variants = { const variants = {
size: ['xs', 'sm', 'md', 'lg', 'xl'], size: ['xs', 'sm', 'md', 'lg'],
variant: ['primary', 'secondary', 'danger', 'inverse', 'transparent'], variant: ['primary', 'secondary', 'danger', 'inverse', 'transparent'],
}; };
const combinationOptions = { const combinationOptions = {

View File

@ -1,6 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { AbstractButton, ButtonProps, LinkButtonProps } from './AbstractButton'; import { AbstractButton } from './AbstractButton';
import { ThemeContext } from '../../themes'; import { ThemeContext } from '../../themes';
import { ButtonProps, LinkButtonProps } from './types';
export const Button: React.FunctionComponent<ButtonProps> = props => { export const Button: React.FunctionComponent<ButtonProps> = props => {
const theme = useContext(ThemeContext); const theme = useContext(ThemeContext);

View File

@ -0,0 +1,39 @@
import { AnchorHTMLAttributes, ButtonHTMLAttributes, ComponentType } from 'react';
import { GrafanaTheme, Themeable } from '../../types';
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'inverse' | 'transparent' | 'destructive';
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
export interface StyleDeps {
theme: GrafanaTheme;
size: ButtonSize;
variant: ButtonVariant;
withIcon: boolean;
}
export interface ButtonStyles {
button: string;
iconWrap: string;
icon: string;
}
export interface CommonButtonProps {
size?: ButtonSize;
variant?: ButtonVariant;
/**
* icon prop is a temporary solution. It accepts legacy icon class names for the icon to be rendered.
* TODO: migrate to a component when we are going to migrate icons to @grafana/ui
*/
icon?: string;
className?: string;
}
export interface LinkButtonProps extends CommonButtonProps, AnchorHTMLAttributes<HTMLAnchorElement> {
disabled?: boolean;
}
export interface ButtonProps extends CommonButtonProps, ButtonHTMLAttributes<HTMLButtonElement> {}
export interface AbstractButtonProps extends CommonButtonProps, Themeable {
renderAs: ComponentType<CommonButtonProps> | string;
}

View File

@ -0,0 +1,63 @@
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import { Button } from './Button';
<Meta title="MDX|Button" component={Button} />
# Button
## Primary
Used for "call to action".
<Preview>
<div>
<Button variant="primary" size="sm" renderAs="button" style={{ margin: '5px' }}>
Small
</Button>
<Button variant="primary" size="md" renderAs="button" style={{ margin: '5px' }}>
Medium
</Button>
<Button variant="primary" size="lg" renderAs="button" style={{ margin: '5px' }}>
Large
</Button>
</div>
</Preview>
## Secondary
The secondary button, used for "cancel" or aborting.
<Preview>
<div>
<Button variant="secondary" size="sm" renderAs="button" style={{ margin: '5px' }}>
Small
</Button>
<Button variant="secondary" size="md" renderAs="button" style={{ margin: '5px' }}>
Medium
</Button>
<Button variant="secondary" size="lg" renderAs="button" style={{ margin: '5px' }}>
Large
</Button>
</div>
</Preview>
## Destructive
Used for removing or deleting entities.
<Preview>
<div>
<Button variant="destructive" size="sm" renderAs="button" style={{ margin: '5px' }}>
Small
</Button>
<Button variant="destructive" size="md" renderAs="button" style={{ margin: '5px' }}>
Medium
</Button>
<Button variant="destructive" size="lg" renderAs="button" style={{ margin: '5px' }}>
Large
</Button>
</div>
</Preview>
<Props of={Button} />

View File

@ -0,0 +1,33 @@
import React from 'react';
import { Button } from './Button';
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
import { select, text } from '@storybook/addon-knobs';
import { ButtonSize, ButtonVariant } from '../Button/types';
import mdx from './Button.mdx';
export default {
title: 'UI/Forms/Button',
component: Button,
decorators: [withCenteredStory, withHorizontallyCenteredStory],
parameters: {
docs: {
page: mdx,
},
},
};
const variants = ['primary', 'secondary', 'destructive'];
const sizes = ['sm', 'md', 'lg'];
export const simple = () => {
const variant = select('Variant', variants, 'primary');
const size = select('Size', sizes, 'md');
const buttonText = text('text', 'Button');
return (
<Button variant={variant as ButtonVariant} size={size as ButtonSize} renderAs="button">
{buttonText}
</Button>
);
};

View File

@ -0,0 +1,158 @@
import { FC } from 'react';
import { css, cx } from 'emotion';
import tinycolor from 'tinycolor2';
import { selectThemeVariant, stylesFactory, useTheme } from '../../themes';
import { renderButton } from '../Button/AbstractButton';
import { getFocusStyle } from './commonStyles';
import { AbstractButtonProps, ButtonSize, ButtonVariant, StyleDeps } from '../Button/types';
import { GrafanaTheme } from '../../types';
const buttonVariantStyles = (from: string, to: string, textColor: string) => css`
background: linear-gradient(180deg, ${from} 0%, ${to} 100%);
color: ${textColor};
&:hover {
background: ${from};
color: ${textColor};
}
&:focus {
background: ${from};
outline: none;
}
`;
const getPropertiesForSize = (theme: GrafanaTheme, size: ButtonSize) => {
switch (size) {
case 'sm':
return {
padding: `0 ${theme.spacing.sm}`,
fontSize: theme.typography.size.sm,
iconDistance: theme.spacing.xs,
height: theme.height.sm,
};
case 'md':
return {
padding: `0 ${theme.spacing.md}`,
fontSize: theme.typography.size.md,
iconDistance: theme.spacing.sm,
height: `${theme.spacing.formButtonHeight}px`,
};
case 'lg':
return {
padding: `0 ${theme.spacing.lg}`,
fontSize: theme.typography.size.lg,
iconDistance: theme.spacing.sm,
height: theme.height.lg,
};
default:
return {
padding: `0 ${theme.spacing.md}`,
iconDistance: theme.spacing.sm,
fontSize: theme.typography.size.base,
height: theme.height.md,
};
}
};
const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => {
switch (variant) {
case 'secondary':
const from = selectThemeVariant({ light: theme.colors.gray7, dark: theme.colors.gray15 }, theme.type) as string;
const to = selectThemeVariant(
{
light: tinycolor(from)
.darken(5)
.toString(),
dark: tinycolor(from)
.lighten(4)
.toString(),
},
theme.type
) as string;
return {
borderColor: selectThemeVariant({ light: theme.colors.gray70, dark: theme.colors.gray33 }, theme.type),
background: buttonVariantStyles(from, to, selectThemeVariant(
{ light: theme.colors.gray25, dark: theme.colors.gray4 },
theme.type
) as string),
};
case 'destructive':
return {
borderColor: theme.colors.redShade,
background: buttonVariantStyles(theme.colors.redBase, theme.colors.redShade, theme.colors.white),
};
case 'primary':
default:
return {
borderColor: theme.colors.blueShade,
background: buttonVariantStyles(theme.colors.blueBase, theme.colors.blueShade, theme.colors.white),
};
}
};
export const getButtonStyles = stylesFactory(({ theme, size, variant, withIcon }: StyleDeps) => {
const { padding, fontSize, iconDistance, height } = getPropertiesForSize(theme, size);
const { background, borderColor } = getPropertiesForVariant(theme, variant);
return {
button: cx(
css`
position: relative;
label: button;
display: inline-flex;
align-items: center;
font-weight: ${theme.typography.weight.semibold};
font-size: ${fontSize};
font-family: ${theme.typography.fontFamily.sansSerif};
line-height: ${theme.typography.lineHeight.sm};
padding: ${padding};
text-align: ${withIcon ? 'left' : 'center'};
vertical-align: middle;
cursor: pointer;
border: 1px solid ${borderColor};
height: ${height};
border-radius: ${theme.border.radius.sm};
${background};
&[disabled],
&:disabled {
cursor: not-allowed;
opacity: 0.65;
box-shadow: none;
}
`,
getFocusStyle(theme)
),
iconWrap: css`
label: button-icon-wrap;
display: flex;
align-items: center;
`,
icon: css`
label: button-icon;
margin-right: ${iconDistance};
filter: brightness(100);
`,
};
});
export const Button: FC<Omit<AbstractButtonProps, 'theme'>> = ({
renderAs,
size = 'md',
variant = 'primary',
className,
icon,
children,
...otherProps
}) => {
const theme = useTheme();
const buttonStyles = getButtonStyles({ theme, size, variant, withIcon: !!icon });
return renderButton(theme, buttonStyles, renderAs, children, size, variant, icon, className, otherProps);
};

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { text } from '@storybook/addon-knobs'; import { text } from '@storybook/addon-knobs';
import { Label } from './Label'; import { Label } from './Label';

View File

@ -0,0 +1,20 @@
import { css } from 'emotion';
import { GrafanaTheme } from '../../types';
export const getFocusStyle = (theme: GrafanaTheme) => css`
&[focus],
&:focus {
&:before {
content: '';
position: absolute;
border: 2px solid ${theme.colors.blueLight};
border-radius: ${theme.border.radius.lg};
background-color: ${theme.colors.bodyBg};
height: calc(100% + 8px);
width: calc(100% + 8px);
top: -4px;
left: -4px;
z-index: -1;
}
}
`;

View File

@ -3,11 +3,13 @@ import { GrafanaTheme } from '../../types';
import { getLabelStyles } from './Label'; import { getLabelStyles } from './Label';
import { getLegendStyles } from './Legend'; import { getLegendStyles } from './Legend';
import { getFieldValidationMessageStyles } from './FieldValidationMessage'; import { getFieldValidationMessageStyles } from './FieldValidationMessage';
import { getButtonStyles } from './Button';
export const getFormStyles = stylesFactory((theme: GrafanaTheme) => { export const getFormStyles = stylesFactory((theme: GrafanaTheme, options?: any) => {
return { return {
...getLabelStyles(theme), ...getLabelStyles(theme),
...getLegendStyles(theme), ...getLegendStyles(theme),
...getFieldValidationMessageStyles(theme), ...getFieldValidationMessageStyles(theme),
...getButtonStyles({ theme, variant: options.variant, size: options.size, withIcon: options.withIcon }),
}; };
}); });

View File

@ -202,6 +202,7 @@ exports[`Render should render with base threshold 1`] = `
}, },
"spacing": Object { "spacing": Object {
"d": "14px", "d": "14px",
"formButtonHeight": 32,
"formFieldsetMargin": "16px", "formFieldsetMargin": "16px",
"formInputAffixPaddingHorizontal": "4px", "formInputAffixPaddingHorizontal": "4px",
"formInputHeight": "32px", "formInputHeight": "32px",
@ -255,6 +256,7 @@ exports[`Render should render with base threshold 1`] = `
"xs": "10px", "xs": "10px",
}, },
"weight": Object { "weight": Object {
"bold": 600,
"light": 300, "light": 300,
"regular": 400, "regular": 400,
"semibold": 500, "semibold": 500,
@ -410,6 +412,7 @@ exports[`Render should render with base threshold 1`] = `
}, },
"spacing": Object { "spacing": Object {
"d": "14px", "d": "14px",
"formButtonHeight": 32,
"formFieldsetMargin": "16px", "formFieldsetMargin": "16px",
"formInputAffixPaddingHorizontal": "4px", "formInputAffixPaddingHorizontal": "4px",
"formInputHeight": "32px", "formInputHeight": "32px",
@ -463,6 +466,7 @@ exports[`Render should render with base threshold 1`] = `
"xs": "10px", "xs": "10px",
}, },
"weight": Object { "weight": Object {
"bold": 600,
"light": 300, "light": 300,
"regular": 400, "regular": 400,
"semibold": 500, "semibold": 500,

View File

@ -6,7 +6,6 @@ export { Portal } from './Portal/Portal';
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar'; export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
export * from './Button/Button'; export * from './Button/Button';
export { ButtonVariant } from './Button/AbstractButton';
// Select // Select
export { Select, AsyncSelect } from './Select/Select'; export { Select, AsyncSelect } from './Select/Select';

View File

@ -50,6 +50,7 @@ const theme: GrafanaThemeCommons = {
light: 300, light: 300,
regular: 400, regular: 400,
semibold: 500, semibold: 500,
bold: 600,
}, },
lineHeight: { lineHeight: {
xs: 1, xs: 1,
@ -87,6 +88,7 @@ const theme: GrafanaThemeCommons = {
formFieldsetMargin: `${SPACING_BASE * 2}px`, formFieldsetMargin: `${SPACING_BASE * 2}px`,
formLegendMargin: `0 0 ${SPACING_BASE * 2}px 0`, formLegendMargin: `0 0 ${SPACING_BASE * 2}px 0`,
formInputHeight: `${SPACING_BASE * 4}px`, formInputHeight: `${SPACING_BASE * 4}px`,
formButtonHeight: SPACING_BASE * 4,
formInputPaddingHorizontal: `${SPACING_BASE}px`, formInputPaddingHorizontal: `${SPACING_BASE}px`,
// Used for icons do define spacing between icon and input field // Used for icons do define spacing between icon and input field

View File

@ -30,6 +30,7 @@ export interface GrafanaThemeCommons {
light: number; light: number;
regular: number; regular: number;
semibold: number; semibold: number;
bold: number;
}; };
lineHeight: { lineHeight: {
xs: number; //1 xs: number; //1
@ -69,6 +70,7 @@ export interface GrafanaThemeCommons {
formFieldsetMargin: string; formFieldsetMargin: string;
formLegendMargin: string; formLegendMargin: string;
formInputHeight: string; formInputHeight: string;
formButtonHeight: number;
formInputPaddingHorizontal: string; formInputPaddingHorizontal: string;
// Used for icons do define spacing between icon and input field // Used for icons do define spacing between icon and input field
// Applied on the right(prefix) or left(suffix) // Applied on the right(prefix) or left(suffix)