grafana/packages/grafana-ui/src/components/Button/Button.tsx

209 lines
6.0 KiB
TypeScript
Raw Normal View History

import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react';
import { css, CSSObject, cx } from '@emotion/css';
import { useTheme } from '../../themes';
import { IconName } from '../../types/icon';
import { getPropertiesForButtonSize } from '../Forms/commonStyles';
import { colorManipulator, GrafanaTheme, GrafanaThemeV2, ThemePaletteColor } from '@grafana/data';
import { ComponentSize } from '../../types/size';
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
import { Icon } from '../Icon/Icon';
export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link';
export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive', 'link'];
type CommonProps = {
size?: ComponentSize;
variant?: ButtonVariant;
@grafana/ui: Create Icon component and replace part of the icons (#23402) * Part1: Unicons implementation (#23197) * Create a new Icon component * Update icons in main sidebar * Update icons in Useful links and in react components on main site * Update icons in Useful links and in main top navigation * Adjust sizing * Update panel navigation and timepicker * Update icons in Panel menu * NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179) * Update icons in add panel widget * Resolve merge conflict * Fix part of the test errors and type errors * Fix storybook errors * Update getAvailableIcons import in storybook knobs * Fix import path * Fix SyntaxError: Cannot use import statement outside a module in test environment error * Remove dynamic imports * Remove types as using @ts-ignore * Update snapshot test * Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax * Remove color prop from icon, remove color implemetation in mono icons * Update navbar styling * Move toPascalCase to utils/string Co-authored-by: Torkel Ödegaard <torkel@grafana.com> * Resolve type errors resulted from merge * Part2: Unicons implementation (#23266) * Create a new Icon component * Update icons in main sidebar * Update icons in Useful links and in react components on main site * Update icons in Useful links and in main top navigation * Adjust sizing * Update panel navigation and timepicker * Update icons in Panel menu * Update icons in add panel widget * Resolve merge conflict * Fix part of the test errors and type errors * Fix storybook errors * Update getAvailableIcons import in storybook knobs * Fix import path * Fix SyntaxError: Cannot use import statement outside a module in test environment error * Remove dynamic imports * Remove types as using @ts-ignore * Update snapshot test * Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax * Implment icons in Tabs * Implement icons in search items and empty list * Update buttons * Update button-related snapshot tests * Update icons in modals and page headers * Create anfular wrapper and update all icons on search screen * Update sizing, remove colors, update snapshot tests * Remove color prop from icon, remove color implemetation in mono icons * Remove color props from monochrome icons * Complete update of icons for search screen * Update icons for infor tooltips, playlist, permissions * Support temporarly font awesome icons used in enterprise grafana * Part1: Unicons implementation (#23197) * Create a new Icon component * Update icons in main sidebar * Update icons in Useful links and in react components on main site * Update icons in Useful links and in main top navigation * Adjust sizing * Update panel navigation and timepicker * Update icons in Panel menu * NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179) * Update icons in add panel widget * Resolve merge conflict * Fix part of the test errors and type errors * Fix storybook errors * Update getAvailableIcons import in storybook knobs * Fix import path * Fix SyntaxError: Cannot use import statement outside a module in test environment error * Remove dynamic imports * Remove types as using @ts-ignore * Update snapshot test * Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax * Remove color prop from icon, remove color implemetation in mono icons * Update navbar styling * Move toPascalCase to utils/string Co-authored-by: Torkel Ödegaard <torkel@grafana.com> * Icons update * Add optional chaining to for isFontAwesome variable Co-authored-by: Torkel Ödegaard <torkel@grafana.com> * Part3: Unicons implementation (#23356) * Create a new Icon component * Update icons in main sidebar * Update icons in Useful links and in react components on main site * Update icons in Useful links and in main top navigation * Adjust sizing * Update panel navigation and timepicker * Update icons in Panel menu * Update icons in add panel widget * Resolve merge conflict * Fix part of the test errors and type errors * Fix storybook errors * Update getAvailableIcons import in storybook knobs * Fix import path * Fix SyntaxError: Cannot use import statement outside a module in test environment error * Remove dynamic imports * Remove types as using @ts-ignore * Update snapshot test * Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax * Implment icons in Tabs * Implement icons in search items and empty list * Update buttons * Update button-related snapshot tests * Update icons in modals and page headers * Create anfular wrapper and update all icons on search screen * Update sizing, remove colors, update snapshot tests * Remove color prop from icon, remove color implemetation in mono icons * Remove color props from monochrome icons * Complete update of icons for search screen * Update icons for infor tooltips, playlist, permissions * Support temporarly font awesome icons used in enterprise grafana * Part1: Unicons implementation (#23197) * Create a new Icon component * Update icons in main sidebar * Update icons in Useful links and in react components on main site * Update icons in Useful links and in main top navigation * Adjust sizing * Update panel navigation and timepicker * Update icons in Panel menu * NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179) * Update icons in add panel widget * Resolve merge conflict * Fix part of the test errors and type errors * Fix storybook errors * Update getAvailableIcons import in storybook knobs * Fix import path * Fix SyntaxError: Cannot use import statement outside a module in test environment error * Remove dynamic imports * Remove types as using @ts-ignore * Update snapshot test * Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax * Remove color prop from icon, remove color implemetation in mono icons * Update navbar styling * Move toPascalCase to utils/string Co-authored-by: Torkel Ödegaard <torkel@grafana.com> * Update icons in Explore * Update icons in alerting * Update + and x buttons * Update icons in configurations and settings * Update close icons * Update icons in rich history * Update alert messages * Add optional chaining to for isFontAwesome variable * Remove icon mock, set up jest.config * Fix navbar plus icon * Fir enable-bacground to enableBackgournd Co-authored-by: Torkel Ödegaard <torkel@grafana.com> * Merge remote branch origin master to icons-unicons * Revert "Merge remote branch origin master to icons-unicons" This reverts commit 3f25d50a39a940883fefe73ce51219139c1ed37f. * Size-up dashnav icons * Fix alerting icons, panel headers, update tests * Fix typecheck error * Adjustments - add panel icon, spacing * Set TerserPlugin sourceMap to false to prevent running out of memory when publishing storybook Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2020-04-08 20:33:31 +08:00
icon?: IconName;
className?: string;
children?: React.ReactNode;
fullWidth?: boolean;
};
export type ButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
2020-01-07 16:20:06 +08:00
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', icon, fullWidth, children, className, ...otherProps }, ref) => {
const theme = useTheme();
const styles = getButtonStyles({
theme,
size,
variant,
fullWidth,
iconOnly: !children,
});
return (
<button className={cx(styles.button, className)} {...otherProps} ref={ref}>
{icon && <Icon name={icon} size={size} className={styles.icon} />}
{children && <span className={styles.content}>{children}</span>}
</button>
);
}
);
2020-01-07 16:20:06 +08:00
Button.displayName = 'Button';
type ButtonLinkProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement> & AnchorHTMLAttributes<HTMLAnchorElement>;
export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
(
{
variant = 'primary',
size = 'md',
icon,
fullWidth,
children,
className,
onBlur,
onFocus,
disabled,
...otherProps
},
ref
) => {
const theme = useTheme();
const styles = getButtonStyles({
theme,
fullWidth,
size,
variant,
iconOnly: !children,
});
const linkButtonStyles = cx(styles.button, { [styles.disabled]: disabled }, className);
return (
<a className={linkButtonStyles} {...otherProps} tabIndex={disabled ? -1 : 0} ref={ref}>
{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;
iconOnly?: boolean;
theme: GrafanaTheme;
fullWidth?: boolean;
narrow?: boolean;
}
export const getButtonStyles = (props: StyleProps) => {
const { theme, variant, size, iconOnly, fullWidth } = props;
const { height, padding, fontSize } = getPropertiesForButtonSize(size, theme.v2);
const variantStyles = getPropertiesForVariant(theme.v2, variant);
const disabledStyles: CSSObject = {
cursor: 'not-allowed',
boxShadow: 'none',
background: theme.v2.palette.action.disabledBackground,
border: `1px solid transparent`,
color: theme.v2.palette.text.disabled,
pointerEvents: 'none',
'&:hover': {
background: theme.v2.palette.action.disabledBackground,
color: theme.v2.palette.text.disabled,
boxShadow: 'none',
},
};
const focusStyle = getFocusStyles(theme.v2);
return {
button: css({
label: 'button',
display: 'inline-flex',
alignItems: 'center',
fontSize: fontSize,
fontWeight: theme.v2.typography.fontWeightMedium,
fontFamily: theme.v2.typography.fontFamily,
padding: theme.v2.spacing(0, padding),
height: theme.v2.spacing(height),
// Deduct border from line-height for perfect vertical centering on windows and linux
lineHeight: `${theme.v2.spacing.gridSize * height - 2}px`,
verticalAlign: 'middle',
cursor: 'pointer',
borderRadius: theme.v2.shape.borderRadius(1),
'&:focus': focusStyle,
'&:focus-visible': focusStyle,
'&:focus:not(:focus-visible)': getMouseFocusStyles(theme.v2),
...(fullWidth && {
flexGrow: 1,
justifyContent: 'center',
}),
...variantStyles,
':disabled': disabledStyles,
'&[disabled]': disabledStyles,
}),
disabled: css(disabledStyles),
img: css`
width: 16px;
height: 16px;
margin: ${theme.v2.spacing(0, 1, 0, 0.5)};
`,
icon: css`
margin: ${theme.v2.spacing(0, (iconOnly ? -padding : padding) / 2, 0, -(padding / 2))};
`,
content: css`
display: flex;
flex-direction: row;
align-items: center;
white-space: nowrap;
height: 100%;
`,
};
};
function getButtonVariantStyles(theme: GrafanaThemeV2, color: ThemePaletteColor): CSSObject {
return {
background: color.main,
color: color.contrastText,
border: `1px solid transparent`,
transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
'&:hover': {
background: color.shade,
color: color.contrastText,
boxShadow: theme.shadows.z2,
},
};
}
export function getPropertiesForVariant(theme: GrafanaThemeV2, variant: ButtonVariant) {
switch (variant) {
case 'secondary':
return getButtonVariantStyles(theme, theme.palette.secondary);
case 'destructive':
return getButtonVariantStyles(theme, theme.palette.error);
case 'link':
return {
background: 'transparent',
color: theme.palette.text.link,
border: '1px solid transparent',
'&:focus': {
outline: 'none',
textDecoration: 'underline',
},
'&:hover': {
background: colorManipulator.alpha(theme.palette.text.link, theme.palette.action.hoverOpacity),
textDecoration: 'underline',
},
};
case 'primary':
default:
return getButtonVariantStyles(theme, theme.palette.primary);
}
}