import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'; import { css, cx } from 'emotion'; import tinycolor from 'tinycolor2'; import { useTheme } from '../../themes'; import { IconName } from '../../types/icon'; import { getPropertiesForButtonSize } from '../Forms/commonStyles'; import { GrafanaTheme } from '@grafana/data'; import { ComponentSize } from '../../types/size'; import { focusCss } from '../../themes/mixins'; import { Icon } from '../Icon/Icon'; export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link'; type CommonProps = { size?: ComponentSize; variant?: ButtonVariant; icon?: IconName; className?: string; children?: React.ReactNode; fullWidth?: boolean; }; export type ButtonProps = CommonProps & ButtonHTMLAttributes; export const Button = React.forwardRef( ({ variant = 'primary', size = 'md', icon, fullWidth, children, className, ...otherProps }, ref) => { const theme = useTheme(); const styles = getButtonStyles({ theme, size, variant, icon, fullWidth, children, }); return ( ); } ); Button.displayName = 'Button'; type ButtonLinkProps = CommonProps & ButtonHTMLAttributes & AnchorHTMLAttributes; export const LinkButton = React.forwardRef( ({ variant = 'primary', size = 'md', icon, fullWidth, children, className, disabled, ...otherProps }, ref) => { const theme = useTheme(); const styles = getButtonStyles({ theme, fullWidth, size, variant, icon, children, }); const linkButtonStyles = disabled && cx( disabledStyles, css` pointer-events: none; ` ); return ( {icon && } {children && {children}} ); } ); 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), }; } }