mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ToolbarButton: New emotion based component to replace all navbar, DashNavButton and scss styles (#30333)
* ToolbarButton: New emotion based component to replace all navbar, DashNavButton and scss styles * Component ready for use * Dam dam dam * Starting big button design update * Tried to use main button component but failed * Minor fix * Updates * Updated * Update packages/grafana-ui/src/components/Button/Button.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update packages/grafana-ui/src/components/Button/ButtonGroup.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Updated to use spacing base * Button updates * Removd unused import * Remove unused import * Use correct theme variable for border-radius Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
parent
c0cddc303a
commit
abe808bcfd
@ -53,6 +53,7 @@ export interface GrafanaThemeCommons {
|
||||
};
|
||||
};
|
||||
spacing: {
|
||||
base: number;
|
||||
insetSquishMd: string;
|
||||
d: string;
|
||||
xxs: string;
|
||||
|
@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Story } from '@storybook/react';
|
||||
import { Button, ButtonProps } from './Button';
|
||||
import { Button, ButtonProps, ButtonVariant } from './Button';
|
||||
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { iconOptions } from '../../utils/storybook/knobs';
|
||||
import mdx from './Button.mdx';
|
||||
import { HorizontalGroup, VerticalGroup } from '../Layout/Layout';
|
||||
import { ButtonGroup } from './ButtonGroup';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
|
||||
export default {
|
||||
title: 'Buttons/Button',
|
||||
@ -26,11 +29,53 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
export const Simple: Story<ButtonProps> = ({ children, ...args }) => <Button {...args}>{children}</Button>;
|
||||
Simple.args = {
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
disabled: false,
|
||||
children: 'Button',
|
||||
icon: undefined,
|
||||
export const Variants: Story<ButtonProps> = ({ children, ...args }) => {
|
||||
const sizes: ComponentSize[] = ['lg', 'md', 'sm'];
|
||||
const variants: ButtonVariant[] = ['primary', 'secondary', 'destructive', 'link'];
|
||||
|
||||
return (
|
||||
<VerticalGroup>
|
||||
<HorizontalGroup spacing="lg">
|
||||
{variants.map(variant => (
|
||||
<VerticalGroup spacing="lg" key={variant}>
|
||||
{sizes.map(size => (
|
||||
<Button variant={variant} size={size} key={size}>
|
||||
{variant} {size}
|
||||
</Button>
|
||||
))}
|
||||
</VerticalGroup>
|
||||
))}
|
||||
</HorizontalGroup>
|
||||
<div />
|
||||
<HorizontalGroup spacing="lg">
|
||||
<div>With icon and text</div>
|
||||
<Button icon="cloud" size="sm">
|
||||
Configure
|
||||
</Button>
|
||||
<Button icon="cloud">Configure</Button>
|
||||
<Button icon="cloud" size="lg">
|
||||
Configure
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
<div />
|
||||
<HorizontalGroup spacing="lg">
|
||||
<div>With icon only</div>
|
||||
<Button icon="cloud" size="sm" />
|
||||
<Button icon="cloud" size="md" />
|
||||
<Button icon="cloud" size="lg" />
|
||||
</HorizontalGroup>
|
||||
<div />
|
||||
<Button icon="plus" fullWidth>
|
||||
Button with fullWidth
|
||||
</Button>
|
||||
<div />
|
||||
<HorizontalGroup spacing="lg">
|
||||
<div>Inside ButtonGroup</div>
|
||||
<ButtonGroup noSpacing>
|
||||
<Button icon="sync">Run query</Button>
|
||||
<Button icon="angle-down" />
|
||||
</ButtonGroup>
|
||||
</HorizontalGroup>
|
||||
</VerticalGroup>
|
||||
);
|
||||
};
|
||||
|
@ -1,128 +1,13 @@
|
||||
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, useContext } from 'react';
|
||||
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { stylesFactory, ThemeContext } from '../../themes';
|
||||
import { useTheme } from '../../themes';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { getFocusStyle, getPropertiesForButtonSize } from '../Forms/commonStyles';
|
||||
import { getPropertiesForButtonSize } from '../Forms/commonStyles';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { ButtonContent } from './ButtonContent';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
|
||||
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 getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => {
|
||||
switch (variant) {
|
||||
case 'secondary':
|
||||
const from = theme.isLight ? theme.palette.gray7 : theme.palette.gray15;
|
||||
const to = theme.isLight
|
||||
? tinycolor(from)
|
||||
.darken(5)
|
||||
.toString()
|
||||
: tinycolor(from)
|
||||
.lighten(4)
|
||||
.toString();
|
||||
return {
|
||||
borderColor: theme.isLight ? theme.palette.gray85 : theme.palette.gray25,
|
||||
background: buttonVariantStyles(from, to, theme.isLight ? theme.palette.gray25 : theme.palette.gray4),
|
||||
};
|
||||
|
||||
case 'destructive':
|
||||
return {
|
||||
borderColor: theme.palette.redShade,
|
||||
background: buttonVariantStyles(theme.palette.redBase, theme.palette.redShade, theme.palette.white),
|
||||
};
|
||||
|
||||
case 'link':
|
||||
return {
|
||||
borderColor: 'transparent',
|
||||
background: buttonVariantStyles('transparent', 'transparent', theme.colors.linkExternal),
|
||||
variantStyles: css`
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`,
|
||||
};
|
||||
case 'primary':
|
||||
default:
|
||||
return {
|
||||
borderColor: theme.colors.bgBlue1,
|
||||
background: buttonVariantStyles(theme.colors.bgBlue1, theme.colors.bgBlue2, theme.palette.white),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export interface StyleProps {
|
||||
theme: GrafanaTheme;
|
||||
size: ComponentSize;
|
||||
variant: ButtonVariant;
|
||||
hasIcon: boolean;
|
||||
hasText: boolean;
|
||||
}
|
||||
|
||||
const disabledStyles = css`
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
box-shadow: none;
|
||||
`;
|
||||
|
||||
export const getButtonStyles = stylesFactory((props: StyleProps) => {
|
||||
const { theme, variant } = props;
|
||||
const { padding, fontSize, height } = getPropertiesForButtonSize(props);
|
||||
const { background, borderColor, variantStyles } = getPropertiesForVariant(theme, variant);
|
||||
|
||||
return {
|
||||
button: cx(
|
||||
css`
|
||||
label: button;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: ${theme.typography.weight.semibold};
|
||||
font-family: ${theme.typography.fontFamily.sansSerif};
|
||||
font-size: ${fontSize};
|
||||
padding: ${padding};
|
||||
height: ${height}px;
|
||||
// Deduct border from line-height for perfect vertical centering on windows and linux
|
||||
line-height: ${height - 2}px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
border: 1px solid ${borderColor};
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
${background};
|
||||
|
||||
&[disabled],
|
||||
&:disabled {
|
||||
${disabledStyles};
|
||||
}
|
||||
`,
|
||||
getFocusStyle(theme),
|
||||
css`
|
||||
${variantStyles}
|
||||
`
|
||||
),
|
||||
// used for buttons with icon only
|
||||
iconButton: css`
|
||||
padding-right: 0;
|
||||
`,
|
||||
iconWrap: css`
|
||||
label: button-icon-wrap;
|
||||
& + * {
|
||||
margin-left: ${theme.spacing.sm};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
import { focusCss } from '../../themes/mixins';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link';
|
||||
|
||||
@ -131,26 +16,28 @@ type CommonProps = {
|
||||
variant?: ButtonVariant;
|
||||
icon?: IconName;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
export type ButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ variant, icon, children, className, ...otherProps }, ref) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
({ variant = 'primary', size = 'md', icon, fullWidth, children, className, ...otherProps }, ref) => {
|
||||
const theme = useTheme();
|
||||
const styles = getButtonStyles({
|
||||
theme,
|
||||
size: otherProps.size || 'md',
|
||||
variant: variant || 'primary',
|
||||
hasText: children !== undefined,
|
||||
hasIcon: icon !== undefined,
|
||||
size,
|
||||
variant,
|
||||
icon,
|
||||
fullWidth,
|
||||
children,
|
||||
});
|
||||
|
||||
return (
|
||||
<button className={cx(styles.button, className)} {...otherProps} ref={ref}>
|
||||
<ButtonContent icon={icon} size={otherProps.size}>
|
||||
{children}
|
||||
</ButtonContent>
|
||||
{icon && <Icon name={icon} size={size} className={styles.icon} />}
|
||||
{children && <span className={styles.content}>{children}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@ -159,15 +46,17 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
Button.displayName = 'Button';
|
||||
|
||||
type ButtonLinkProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement> & AnchorHTMLAttributes<HTMLAnchorElement>;
|
||||
|
||||
export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
|
||||
({ variant, icon, children, className, disabled, ...otherProps }, ref) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
({ variant = 'primary', size = 'md', icon, fullWidth, children, className, disabled, ...otherProps }, ref) => {
|
||||
const theme = useTheme();
|
||||
const styles = getButtonStyles({
|
||||
theme,
|
||||
size: otherProps.size || 'md',
|
||||
variant: variant || 'primary',
|
||||
hasText: children !== undefined,
|
||||
hasIcon: icon !== undefined,
|
||||
fullWidth,
|
||||
size,
|
||||
variant,
|
||||
icon,
|
||||
children,
|
||||
});
|
||||
|
||||
const linkButtonStyles =
|
||||
@ -186,11 +75,153 @@ export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
|
||||
ref={ref}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
>
|
||||
<ButtonContent icon={icon} size={otherProps.size}>
|
||||
{children}
|
||||
</ButtonContent>
|
||||
{icon && <Icon name={icon} size={size} className={styles.icon} />}
|
||||
{children && <span className={styles.content}>{children}</span>}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
LinkButton.displayName = 'LinkButton';
|
||||
|
||||
export interface StyleProps {
|
||||
size: ComponentSize;
|
||||
variant: ButtonVariant;
|
||||
children?: React.ReactNode;
|
||||
icon?: IconName;
|
||||
theme: GrafanaTheme;
|
||||
fullWidth?: boolean;
|
||||
narrow?: boolean;
|
||||
}
|
||||
|
||||
const disabledStyles = css`
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
box-shadow: none;
|
||||
`;
|
||||
|
||||
export const getButtonStyles = (props: StyleProps) => {
|
||||
const { theme, variant, size, children, fullWidth } = props;
|
||||
const { padding, fontSize, height } = getPropertiesForButtonSize(size, theme);
|
||||
const { borderColor, variantStyles } = getPropertiesForVariant(theme, variant);
|
||||
const iconOnly = !children;
|
||||
|
||||
return {
|
||||
button: css`
|
||||
label: button;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: ${theme.typography.weight.semibold};
|
||||
font-family: ${theme.typography.fontFamily.sansSerif};
|
||||
font-size: ${fontSize};
|
||||
padding: 0 ${padding}px;
|
||||
height: ${height}px;
|
||||
// Deduct border from line-height for perfect vertical centering on windows and linux
|
||||
line-height: ${height - 2}px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
border: 1px solid ${borderColor};
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
${fullWidth &&
|
||||
`
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
`}
|
||||
${variantStyles}
|
||||
|
||||
&[disabled],
|
||||
&:disabled {
|
||||
${disabledStyles};
|
||||
}
|
||||
`,
|
||||
img: css`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: ${theme.spacing.sm};
|
||||
margin-left: -${theme.spacing.xs};
|
||||
`,
|
||||
icon: css`
|
||||
margin-left: -${padding / 2}px;
|
||||
margin-right: ${(iconOnly ? -padding : padding) / 2}px;
|
||||
`,
|
||||
content: css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
height: 100%;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
function getButtonVariantStyles(from: string, to: string, textColor: string, theme: GrafanaTheme) {
|
||||
return css`
|
||||
background: linear-gradient(180deg, ${from} 0%, ${to} 100%);
|
||||
color: ${textColor};
|
||||
&:hover {
|
||||
background: ${from};
|
||||
color: ${textColor};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: ${from};
|
||||
outline: none;
|
||||
${focusCss(theme)};
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function getPropertiesForVariant(theme: GrafanaTheme, variant: ButtonVariant) {
|
||||
switch (variant) {
|
||||
case 'secondary':
|
||||
const from = theme.isLight ? theme.palette.gray7 : theme.palette.gray15;
|
||||
const to = theme.isLight
|
||||
? tinycolor(from)
|
||||
.darken(5)
|
||||
.toString()
|
||||
: tinycolor(from)
|
||||
.lighten(4)
|
||||
.toString();
|
||||
return {
|
||||
borderColor: theme.isLight ? theme.palette.gray85 : theme.palette.gray25,
|
||||
variantStyles: getButtonVariantStyles(
|
||||
from,
|
||||
to,
|
||||
theme.isLight ? theme.palette.gray25 : theme.palette.gray4,
|
||||
theme
|
||||
),
|
||||
};
|
||||
|
||||
case 'destructive':
|
||||
return {
|
||||
borderColor: theme.palette.redShade,
|
||||
variantStyles: getButtonVariantStyles(
|
||||
theme.palette.redBase,
|
||||
theme.palette.redShade,
|
||||
theme.palette.white,
|
||||
theme
|
||||
),
|
||||
};
|
||||
|
||||
case 'link':
|
||||
return {
|
||||
borderColor: 'transparent',
|
||||
variantStyles: css`
|
||||
background: transparent;
|
||||
color: ${theme.colors.linkExternal};
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
case 'primary':
|
||||
default:
|
||||
return {
|
||||
borderColor: theme.colors.bgBlue1,
|
||||
variantStyles: getButtonVariantStyles(theme.colors.bgBlue1, theme.colors.bgBlue2, theme.palette.white, theme),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
content: css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
height: 100%;
|
||||
`,
|
||||
|
||||
icon: css`
|
||||
& + * {
|
||||
margin-left: ${theme.spacing.sm};
|
||||
}
|
||||
`,
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
icon?: IconName;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
size?: ComponentSize;
|
||||
};
|
||||
|
||||
export function ButtonContent(props: Props) {
|
||||
const { icon, children, size } = props;
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme);
|
||||
|
||||
if (!children) {
|
||||
return <span className={styles.content}>{icon && <Icon name={icon} size={size} />}</span>;
|
||||
}
|
||||
|
||||
const iconElement = icon && (
|
||||
<span className={styles.icon}>
|
||||
<Icon name={icon} size={size} />
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<span className={styles.content}>
|
||||
{iconElement}
|
||||
<span>{children}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
54
packages/grafana-ui/src/components/Button/ButtonGroup.tsx
Normal file
54
packages/grafana-ui/src/components/Button/ButtonGroup.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React, { forwardRef, HTMLAttributes } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { useStyles } from '../../themes';
|
||||
|
||||
export interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
noSpacing?: boolean;
|
||||
}
|
||||
|
||||
export const ButtonGroup = forwardRef<HTMLDivElement, Props>(({ noSpacing, children, ...rest }, ref) => {
|
||||
const styles = useStyles(getStyles);
|
||||
const className = noSpacing ? styles.wrapperNoSpacing : styles.wrapper;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={className} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ButtonGroup.displayName = 'ButtonGroup';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
|
||||
> a,
|
||||
> button {
|
||||
margin-left: ${theme.spacing.sm};
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
wrapperNoSpacing: css`
|
||||
display: flex;
|
||||
|
||||
> a,
|
||||
> button {
|
||||
border-radius: 0;
|
||||
border-right: 0;
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 ${theme.border.radius.sm} ${theme.border.radius.sm} 0;
|
||||
border-right: 1px solid ${theme.colors.border2};
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: ${theme.border.radius.sm} 0 0 ${theme.border.radius.sm};
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { ToolbarButton, ButtonGroup, useTheme, VerticalGroup } from '@grafana/ui';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
|
||||
export default {
|
||||
title: 'Buttons/ToolbarButton',
|
||||
component: ToolbarButton,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
export const List = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div style={{ background: theme.colors.dashboardBg, padding: '32px' }}>
|
||||
<VerticalGroup>
|
||||
Wrapped in normal ButtonGroup (md spacing)
|
||||
<ButtonGroup>
|
||||
<ToolbarButton>Just text</ToolbarButton>
|
||||
<ToolbarButton icon="sync" tooltip="Sync" />
|
||||
<ToolbarButton imgSrc="./grafana_icon.svg">With imgSrc</ToolbarButton>
|
||||
<ToolbarButton icon="cloud" isOpen={true}>
|
||||
isOpen
|
||||
</ToolbarButton>
|
||||
<ToolbarButton icon="cloud" isOpen={false}>
|
||||
isOpen = false
|
||||
</ToolbarButton>
|
||||
</ButtonGroup>
|
||||
<br />
|
||||
Wrapped in noSpacing ButtonGroup
|
||||
<ButtonGroup noSpacing>
|
||||
<ToolbarButton icon="clock-nine" tooltip="Time picker">
|
||||
2020-10-02
|
||||
</ToolbarButton>
|
||||
<ToolbarButton icon="search-minus" />
|
||||
</ButtonGroup>
|
||||
<br />
|
||||
Wrapped in noSpacing ButtonGroup
|
||||
<ButtonGroup noSpacing>
|
||||
<ToolbarButton icon="sync" />
|
||||
<ToolbarButton isOpen={false} narrow />
|
||||
</ButtonGroup>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
103
packages/grafana-ui/src/components/Button/ToolbarButton.tsx
Normal file
103
packages/grafana-ui/src/components/Button/ToolbarButton.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import React, { forwardRef, HTMLAttributes } from 'react';
|
||||
import { cx, css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { styleMixins, useStyles } from '../../themes';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
export interface Props extends HTMLAttributes<HTMLButtonElement> {
|
||||
/** Icon name */
|
||||
icon?: IconName;
|
||||
/** Tooltip */
|
||||
tooltip?: string;
|
||||
/** For image icons */
|
||||
imgSrc?: string;
|
||||
/** if true or false will show angle-down/up */
|
||||
isOpen?: boolean;
|
||||
/** Controls flex-grow: 1 */
|
||||
fullWidth?: boolean;
|
||||
/** reduces padding to xs */
|
||||
narrow?: boolean;
|
||||
}
|
||||
|
||||
export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
|
||||
({ tooltip, icon, className, children, imgSrc, fullWidth, isOpen, narrow, ...rest }, ref) => {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
const contentStyles = cx({
|
||||
[styles.content]: true,
|
||||
[styles.contentWithIcon]: !!icon,
|
||||
[styles.contentWithRightIcon]: isOpen !== undefined,
|
||||
});
|
||||
|
||||
const buttonStyles = cx(
|
||||
{
|
||||
[styles.button]: true,
|
||||
[styles.buttonFullWidth]: fullWidth,
|
||||
[styles.narrow]: narrow,
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
const body = (
|
||||
<button ref={ref} className={buttonStyles} {...rest}>
|
||||
{icon && <Icon name={icon} size={'lg'} />}
|
||||
{imgSrc && <img className={styles.img} src={imgSrc} />}
|
||||
{children && <span className={contentStyles}>{children}</span>}
|
||||
{isOpen === false && <Icon name="angle-down" />}
|
||||
{isOpen === true && <Icon name="angle-up" />}
|
||||
</button>
|
||||
);
|
||||
|
||||
return tooltip ? (
|
||||
<Tooltip content={tooltip} placement="bottom">
|
||||
{body}
|
||||
</Tooltip>
|
||||
) : (
|
||||
body
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
button: css`
|
||||
background: ${theme.colors.bg1};
|
||||
border: 1px solid ${theme.colors.border2};
|
||||
height: ${theme.height.md}px;
|
||||
padding: 0 ${theme.spacing.sm};
|
||||
color: ${theme.colors.textWeak};
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: ${theme.colors.text};
|
||||
background: ${styleMixins.hoverColor(theme.colors.bg1, theme)};
|
||||
}
|
||||
`,
|
||||
narrow: css`
|
||||
padding: 0 ${theme.spacing.xs};
|
||||
`,
|
||||
img: css`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: ${theme.spacing.sm};
|
||||
`,
|
||||
buttonFullWidth: css`
|
||||
flex-grow: 1;
|
||||
`,
|
||||
content: css`
|
||||
flex-grow: 1;
|
||||
`,
|
||||
contentWithIcon: css`
|
||||
padding-left: ${theme.spacing.sm};
|
||||
`,
|
||||
contentWithRightIcon: css`
|
||||
padding-right: ${theme.spacing.sm};
|
||||
`,
|
||||
});
|
@ -1 +1,3 @@
|
||||
export * from './Button';
|
||||
export { ButtonGroup } from './ButtonGroup';
|
||||
export { ToolbarButton } from './ToolbarButton';
|
||||
|
@ -72,6 +72,7 @@ export class ButtonSelect<T> extends PureComponent<Props<T>> {
|
||||
tabSelectsValue,
|
||||
autoFocus = true,
|
||||
} = this.props;
|
||||
|
||||
const combinedComponents = {
|
||||
...components,
|
||||
Control: ButtonComponent({ label, className, iconClass }),
|
||||
|
@ -19,13 +19,7 @@ export interface RadioButtonProps {
|
||||
}
|
||||
|
||||
const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize, fullWidth?: boolean) => {
|
||||
const { fontSize, height, padding } = getPropertiesForButtonSize({
|
||||
theme,
|
||||
size,
|
||||
hasIcon: false,
|
||||
hasText: true,
|
||||
variant: 'secondary',
|
||||
});
|
||||
const { fontSize, height, padding } = getPropertiesForButtonSize(size, theme);
|
||||
|
||||
const c = theme.palette;
|
||||
const textColor = theme.colors.textSemiWeak;
|
||||
@ -74,7 +68,7 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButt
|
||||
// Deduct border from line-height for perfect vertical centering on windows and linux
|
||||
line-height: ${height - 2}px;
|
||||
color: ${textColor};
|
||||
padding: ${padding};
|
||||
padding: 0 ${padding}px;
|
||||
margin-left: -1px;
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
border: ${border};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { StyleProps } from '../Button';
|
||||
import { focusCss } from '../../themes/mixins';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
|
||||
export const getFocusStyle = (theme: GrafanaTheme) => css`
|
||||
&:focus {
|
||||
@ -86,30 +86,29 @@ export const inputSizesPixels = (size: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getPropertiesForButtonSize = (props: StyleProps) => {
|
||||
const { hasText, hasIcon, size } = props;
|
||||
const { spacing, typography, height } = props.theme;
|
||||
export function getPropertiesForButtonSize(size: ComponentSize, theme: GrafanaTheme) {
|
||||
const { typography, height, spacing } = theme;
|
||||
|
||||
switch (size) {
|
||||
case 'sm':
|
||||
return {
|
||||
padding: `0 ${spacing.sm}`,
|
||||
padding: spacing.base,
|
||||
fontSize: typography.size.sm,
|
||||
height: height.sm,
|
||||
};
|
||||
|
||||
case 'lg':
|
||||
return {
|
||||
padding: `0 ${hasText ? spacing.lg : spacing.md} 0 ${hasIcon ? spacing.md : spacing.lg}`,
|
||||
padding: spacing.base * 3,
|
||||
fontSize: typography.size.lg,
|
||||
height: height.lg,
|
||||
};
|
||||
case 'md':
|
||||
default:
|
||||
return {
|
||||
padding: `0 ${hasText ? spacing.md : spacing.sm} 0 ${hasIcon ? spacing.sm : spacing.md}`,
|
||||
padding: spacing.base * 2,
|
||||
fontSize: typography.size.md,
|
||||
height: height.md,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -18,8 +18,6 @@ export const getFormStyles = stylesFactory(
|
||||
theme,
|
||||
variant: options.variant,
|
||||
size: options.size,
|
||||
hasIcon: false,
|
||||
hasText: true,
|
||||
}),
|
||||
input: getInputStyles({ theme, invalid: options.invalid }),
|
||||
checkbox: getCheckboxStyles(theme),
|
||||
|
@ -209,11 +209,16 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
const styles = getStyles(theme);
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<FullWidthButtonContainer className={styles.addButton}>
|
||||
<Button size="sm" icon="plus" onClick={() => this.onAddThreshold()} variant="secondary">
|
||||
Add threshold
|
||||
</Button>
|
||||
</FullWidthButtonContainer>
|
||||
<Button
|
||||
size="sm"
|
||||
icon="plus"
|
||||
onClick={() => this.onAddThreshold()}
|
||||
variant="secondary"
|
||||
className={styles.addButton}
|
||||
fullWidth
|
||||
>
|
||||
Add threshold
|
||||
</Button>
|
||||
<div className={styles.thresholds}>
|
||||
{steps
|
||||
.slice(0)
|
||||
|
@ -131,7 +131,7 @@ export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeader
|
||||
// Next-gen forms
|
||||
export { Form } from './Forms/Form';
|
||||
export { InputControl } from './InputControl';
|
||||
export * from './Button';
|
||||
export { Button, LinkButton, ButtonVariant, ToolbarButton, ButtonGroup } from './Button';
|
||||
export { ValuePicker } from './ValuePicker/ValuePicker';
|
||||
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
|
||||
export { getFormStyles } from './Forms/getFormStyles';
|
||||
|
@ -75,6 +75,7 @@ const theme: GrafanaThemeCommons = {
|
||||
xxl: '1440px',
|
||||
},
|
||||
spacing: {
|
||||
base: SPACING_BASE,
|
||||
insetSquishMd: '4px 8px',
|
||||
d: '16px',
|
||||
xxs: '2px',
|
||||
|
@ -84,23 +84,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// element is needed here to override font-awesome specificity
|
||||
i.navbar-page-btn__folder-icon {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-color-weak;
|
||||
padding: 0 $space-sm;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
// element is needed here to override font-awesome specificity
|
||||
i.navbar-page-btn__search {
|
||||
font-size: $font-size-xs;
|
||||
padding: 0 $space-xs;
|
||||
}
|
||||
|
||||
.navbar-buttons {
|
||||
// height: $navbarHeight;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
Loading…
Reference in New Issue
Block a user