2021-01-19 00:16:35 +08:00
|
|
|
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react';
|
2021-04-08 19:00:58 +08:00
|
|
|
import { css, CSSObject, cx } from '@emotion/css';
|
2021-04-21 20:25:43 +08:00
|
|
|
import { useTheme2 } from '../../themes';
|
2020-04-10 00:42:59 +08:00
|
|
|
import { IconName } from '../../types/icon';
|
2021-01-19 00:16:35 +08:00
|
|
|
import { getPropertiesForButtonSize } from '../Forms/commonStyles';
|
2021-05-03 15:45:54 +08:00
|
|
|
import { colorManipulator, GrafanaTheme2, ThemeRichColor } from '@grafana/data';
|
2020-03-25 20:10:37 +08:00
|
|
|
import { ComponentSize } from '../../types/size';
|
2021-04-16 21:22:31 +08:00
|
|
|
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
|
2021-01-19 00:16:35 +08:00
|
|
|
import { Icon } from '../Icon/Icon';
|
2020-03-26 18:50:27 +08:00
|
|
|
|
|
|
|
export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link';
|
2021-04-27 18:03:06 +08:00
|
|
|
export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive'];
|
|
|
|
export type ButtonFill = 'solid' | 'outline' | 'text';
|
|
|
|
export const allButtonFills: ButtonFill[] = ['solid', 'outline', 'text'];
|
2019-03-27 18:50:36 +08:00
|
|
|
|
2019-10-29 16:39:44 +08:00
|
|
|
type CommonProps = {
|
2020-03-25 20:10:37 +08:00
|
|
|
size?: ComponentSize;
|
2019-10-29 16:39:44 +08:00
|
|
|
variant?: ButtonVariant;
|
2021-04-27 18:03:06 +08:00
|
|
|
fill?: ButtonFill;
|
@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;
|
2019-10-29 16:39:44 +08:00
|
|
|
className?: string;
|
2021-01-19 00:16:35 +08:00
|
|
|
children?: React.ReactNode;
|
|
|
|
fullWidth?: boolean;
|
2019-10-29 16:39:44 +08:00
|
|
|
};
|
|
|
|
|
2020-03-03 20:04:28 +08:00
|
|
|
export type ButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
|
2020-01-07 16:20:06 +08:00
|
|
|
|
2020-03-26 18:50:27 +08:00
|
|
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
2021-04-27 18:03:06 +08:00
|
|
|
({ variant = 'primary', size = 'md', fill = 'solid', icon, fullWidth, children, className, ...otherProps }, ref) => {
|
2021-04-21 20:25:43 +08:00
|
|
|
const theme = useTheme2();
|
2020-03-26 18:50:27 +08:00
|
|
|
const styles = getButtonStyles({
|
2019-11-01 17:25:44 +08:00
|
|
|
theme,
|
2021-01-19 00:16:35 +08:00
|
|
|
size,
|
|
|
|
variant,
|
2021-04-27 18:03:06 +08:00
|
|
|
fill,
|
2021-01-19 00:16:35 +08:00
|
|
|
fullWidth,
|
2021-04-08 19:00:58 +08:00
|
|
|
iconOnly: !children,
|
2019-11-01 17:25:44 +08:00
|
|
|
});
|
2019-10-29 16:39:44 +08:00
|
|
|
|
2021-04-27 18:03:06 +08:00
|
|
|
deprecatedPropWarning(
|
|
|
|
variant === 'link',
|
|
|
|
`${Button.displayName}: Prop variant="link" is deprecated. Please use fill="text".`
|
|
|
|
);
|
|
|
|
|
2020-03-26 18:50:27 +08:00
|
|
|
return (
|
2021-04-17 13:13:25 +08:00
|
|
|
<button className={cx(styles.button, className)} {...otherProps} ref={ref}>
|
2021-01-19 00:16:35 +08:00
|
|
|
{icon && <Icon name={icon} size={size} className={styles.icon} />}
|
|
|
|
{children && <span className={styles.content}>{children}</span>}
|
2020-03-26 18:50:27 +08:00
|
|
|
</button>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
2020-01-07 16:20:06 +08:00
|
|
|
|
2019-03-27 18:50:36 +08:00
|
|
|
Button.displayName = 'Button';
|
|
|
|
|
2020-04-22 21:46:01 +08:00
|
|
|
type ButtonLinkProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement> & AnchorHTMLAttributes<HTMLAnchorElement>;
|
2021-01-19 00:16:35 +08:00
|
|
|
|
2020-03-26 18:50:27 +08:00
|
|
|
export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
|
2021-04-16 21:22:31 +08:00
|
|
|
(
|
|
|
|
{
|
|
|
|
variant = 'primary',
|
|
|
|
size = 'md',
|
2021-04-27 18:03:06 +08:00
|
|
|
fill = 'solid',
|
2021-04-16 21:22:31 +08:00
|
|
|
icon,
|
|
|
|
fullWidth,
|
|
|
|
children,
|
|
|
|
className,
|
|
|
|
onBlur,
|
|
|
|
onFocus,
|
|
|
|
disabled,
|
|
|
|
...otherProps
|
|
|
|
},
|
|
|
|
ref
|
|
|
|
) => {
|
2021-04-21 20:25:43 +08:00
|
|
|
const theme = useTheme2();
|
2020-03-26 18:50:27 +08:00
|
|
|
const styles = getButtonStyles({
|
2019-11-01 17:25:44 +08:00
|
|
|
theme,
|
2021-01-19 00:16:35 +08:00
|
|
|
fullWidth,
|
|
|
|
size,
|
|
|
|
variant,
|
2021-04-27 18:03:06 +08:00
|
|
|
fill,
|
2021-04-08 19:00:58 +08:00
|
|
|
iconOnly: !children,
|
2019-11-01 17:25:44 +08:00
|
|
|
});
|
2019-10-29 16:39:44 +08:00
|
|
|
|
2021-04-08 19:00:58 +08:00
|
|
|
const linkButtonStyles = cx(styles.button, { [styles.disabled]: disabled }, className);
|
2020-06-18 21:22:57 +08:00
|
|
|
|
2021-04-27 18:03:06 +08:00
|
|
|
deprecatedPropWarning(
|
|
|
|
variant === 'link',
|
|
|
|
`${LinkButton.displayName}: Prop variant="link" is deprecated. Please use fill="text".`
|
|
|
|
);
|
|
|
|
|
2020-03-26 18:50:27 +08:00
|
|
|
return (
|
2021-04-17 13:13:25 +08:00
|
|
|
<a className={linkButtonStyles} {...otherProps} tabIndex={disabled ? -1 : 0} ref={ref}>
|
2021-01-19 00:16:35 +08:00
|
|
|
{icon && <Icon name={icon} size={size} className={styles.icon} />}
|
|
|
|
{children && <span className={styles.content}>{children}</span>}
|
2020-03-26 18:50:27 +08:00
|
|
|
</a>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
2021-01-19 00:16:35 +08:00
|
|
|
|
2019-03-27 18:50:36 +08:00
|
|
|
LinkButton.displayName = 'LinkButton';
|
2021-01-19 00:16:35 +08:00
|
|
|
|
|
|
|
export interface StyleProps {
|
|
|
|
size: ComponentSize;
|
|
|
|
variant: ButtonVariant;
|
2021-04-27 18:03:06 +08:00
|
|
|
fill?: ButtonFill;
|
2021-04-08 19:00:58 +08:00
|
|
|
iconOnly?: boolean;
|
2021-05-03 15:45:54 +08:00
|
|
|
theme: GrafanaTheme2;
|
2021-01-19 00:16:35 +08:00
|
|
|
fullWidth?: boolean;
|
|
|
|
narrow?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const getButtonStyles = (props: StyleProps) => {
|
2021-04-27 18:03:06 +08:00
|
|
|
const { theme, variant, fill = 'solid', size, iconOnly, fullWidth } = props;
|
2021-04-21 20:25:43 +08:00
|
|
|
const { height, padding, fontSize } = getPropertiesForButtonSize(size, theme);
|
2021-04-27 18:03:06 +08:00
|
|
|
const variantStyles = getPropertiesForVariant(theme, variant, fill);
|
|
|
|
const disabledStyles = getPropertiesForDisabled(theme, variant, fill);
|
2021-04-21 20:25:43 +08:00
|
|
|
const focusStyle = getFocusStyles(theme);
|
2021-10-28 15:31:45 +08:00
|
|
|
const paddingMinusBorder = theme.spacing.gridSize * padding - 1;
|
2021-04-16 21:22:31 +08:00
|
|
|
|
2021-01-19 00:16:35 +08:00
|
|
|
return {
|
2021-04-08 19:00:58 +08:00
|
|
|
button: css({
|
|
|
|
label: 'button',
|
|
|
|
display: 'inline-flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
fontSize: fontSize,
|
2021-04-21 20:25:43 +08:00
|
|
|
fontWeight: theme.typography.fontWeightMedium,
|
|
|
|
fontFamily: theme.typography.fontFamily,
|
2021-10-28 15:31:45 +08:00
|
|
|
padding: `0 ${paddingMinusBorder}px`,
|
2021-04-21 20:25:43 +08:00
|
|
|
height: theme.spacing(height),
|
2021-01-19 00:16:35 +08:00
|
|
|
// Deduct border from line-height for perfect vertical centering on windows and linux
|
2021-04-21 20:25:43 +08:00
|
|
|
lineHeight: `${theme.spacing.gridSize * height - 2}px`,
|
2021-04-08 19:00:58 +08:00
|
|
|
verticalAlign: 'middle',
|
|
|
|
cursor: 'pointer',
|
2021-04-21 20:25:43 +08:00
|
|
|
borderRadius: theme.shape.borderRadius(1),
|
2021-04-16 21:22:31 +08:00
|
|
|
'&:focus': focusStyle,
|
|
|
|
'&:focus-visible': focusStyle,
|
2021-04-21 20:25:43 +08:00
|
|
|
'&:focus:not(:focus-visible)': getMouseFocusStyles(theme),
|
2021-04-08 19:00:58 +08:00
|
|
|
...(fullWidth && {
|
|
|
|
flexGrow: 1,
|
|
|
|
justifyContent: 'center',
|
|
|
|
}),
|
|
|
|
...variantStyles,
|
|
|
|
':disabled': disabledStyles,
|
|
|
|
'&[disabled]': disabledStyles,
|
|
|
|
}),
|
|
|
|
disabled: css(disabledStyles),
|
2021-01-19 00:16:35 +08:00
|
|
|
img: css`
|
|
|
|
width: 16px;
|
|
|
|
height: 16px;
|
2021-04-21 20:25:43 +08:00
|
|
|
margin: ${theme.spacing(0, 1, 0, 0.5)};
|
2021-01-19 00:16:35 +08:00
|
|
|
`,
|
2021-10-28 15:31:45 +08:00
|
|
|
icon: iconOnly
|
|
|
|
? css({
|
|
|
|
// Important not to set margin bottom here as it would override internal icon bottom margin
|
|
|
|
marginRight: theme.spacing(-padding / 2),
|
|
|
|
marginLeft: theme.spacing(-padding / 2),
|
|
|
|
})
|
|
|
|
: css({
|
|
|
|
marginRight: theme.spacing(padding / 2),
|
|
|
|
}),
|
2021-01-19 00:16:35 +08:00
|
|
|
content: css`
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
align-items: center;
|
|
|
|
white-space: nowrap;
|
|
|
|
height: 100%;
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-05-03 15:45:54 +08:00
|
|
|
function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fill: ButtonFill): CSSObject {
|
2021-04-27 18:03:06 +08:00
|
|
|
if (fill === 'outline') {
|
|
|
|
return {
|
|
|
|
background: 'transparent',
|
|
|
|
color: color.text,
|
|
|
|
border: `1px solid ${color.border}`,
|
|
|
|
transition: theme.transitions.create(['background-color', 'border-color', 'color'], {
|
|
|
|
duration: theme.transitions.duration.short,
|
|
|
|
}),
|
|
|
|
|
|
|
|
'&:hover': {
|
|
|
|
background: colorManipulator.alpha(color.main, theme.colors.action.hoverOpacity),
|
|
|
|
borderColor: theme.colors.emphasize(color.border, 0.25),
|
|
|
|
color: color.text,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fill === 'text') {
|
|
|
|
return {
|
|
|
|
background: 'transparent',
|
|
|
|
color: color.text,
|
|
|
|
border: '1px solid transparent',
|
|
|
|
transition: theme.transitions.create(['background-color', 'color'], {
|
|
|
|
duration: theme.transitions.duration.short,
|
|
|
|
}),
|
|
|
|
|
|
|
|
'&:focus': {
|
|
|
|
outline: 'none',
|
|
|
|
textDecoration: 'none',
|
|
|
|
},
|
|
|
|
|
|
|
|
'&:hover': {
|
|
|
|
background: colorManipulator.alpha(color.shade, theme.colors.action.hoverOpacity),
|
|
|
|
textDecoration: 'none',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-04-08 19:00:58 +08:00
|
|
|
return {
|
|
|
|
background: color.main,
|
|
|
|
color: color.contrastText,
|
|
|
|
border: `1px solid transparent`,
|
2021-04-12 20:23:00 +08:00
|
|
|
transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
|
|
|
|
duration: theme.transitions.duration.short,
|
|
|
|
}),
|
2021-04-08 19:00:58 +08:00
|
|
|
|
|
|
|
'&:hover': {
|
2021-04-12 20:23:00 +08:00
|
|
|
background: color.shade,
|
2021-04-08 19:00:58 +08:00
|
|
|
color: color.contrastText,
|
2021-04-22 15:18:32 +08:00
|
|
|
boxShadow: theme.shadows.z1,
|
2021-04-08 19:00:58 +08:00
|
|
|
},
|
|
|
|
};
|
2021-01-19 00:16:35 +08:00
|
|
|
}
|
|
|
|
|
2021-05-03 15:45:54 +08:00
|
|
|
function getPropertiesForDisabled(theme: GrafanaTheme2, variant: ButtonVariant, fill: ButtonFill) {
|
2021-04-27 18:03:06 +08:00
|
|
|
const disabledStyles: CSSObject = {
|
|
|
|
cursor: 'not-allowed',
|
|
|
|
boxShadow: 'none',
|
|
|
|
pointerEvents: 'none',
|
|
|
|
color: theme.colors.text.disabled,
|
|
|
|
transition: 'none',
|
|
|
|
};
|
|
|
|
|
|
|
|
if (fill === 'text' || variant === 'link') {
|
|
|
|
return {
|
|
|
|
...disabledStyles,
|
|
|
|
background: 'transparent',
|
|
|
|
border: `1px solid transparent`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fill === 'outline') {
|
|
|
|
return {
|
|
|
|
...disabledStyles,
|
|
|
|
background: 'transparent',
|
|
|
|
border: `1px solid ${theme.colors.action.disabledText}`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...disabledStyles,
|
|
|
|
background: theme.colors.action.disabledBackground,
|
|
|
|
border: `1px solid transparent`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-05-03 15:45:54 +08:00
|
|
|
export function getPropertiesForVariant(theme: GrafanaTheme2, variant: ButtonVariant, fill: ButtonFill) {
|
2021-04-27 18:03:06 +08:00
|
|
|
const buttonVariant = variant === 'link' ? 'primary' : variant;
|
|
|
|
const buttonFill = variant === 'link' ? 'text' : fill;
|
|
|
|
|
|
|
|
switch (buttonVariant) {
|
2021-01-19 00:16:35 +08:00
|
|
|
case 'secondary':
|
2021-04-27 18:03:06 +08:00
|
|
|
return getButtonVariantStyles(theme, theme.colors.secondary, buttonFill);
|
2021-01-19 00:16:35 +08:00
|
|
|
|
|
|
|
case 'destructive':
|
2021-04-27 18:03:06 +08:00
|
|
|
return getButtonVariantStyles(theme, theme.colors.error, buttonFill);
|
2021-01-19 00:16:35 +08:00
|
|
|
|
|
|
|
case 'primary':
|
|
|
|
default:
|
2021-04-27 18:03:06 +08:00
|
|
|
return getButtonVariantStyles(theme, theme.colors.primary, buttonFill);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function deprecatedPropWarning(test: boolean, message: string) {
|
|
|
|
if (process.env.NODE_ENV === 'development' && test) {
|
|
|
|
console.warn(`@grafana/ui ${message}`);
|
2021-01-19 00:16:35 +08:00
|
|
|
}
|
|
|
|
}
|