Files
grafana/packages/grafana-ui/src/components/IconButton/IconButton.tsx
T
Uchechukwu Obasi 62d44fcffc InfoTooltip: refactor component to be accessible (#43613)
* InfoTooltip: refactor component to be accessible

* new: create ghostMode prop to allow turning on/off button styles

* update infoToolTip component to use ghostMode prop

* update story to show ghostMode state

* fix condition to work properly

* nit fix

* revert changes to former infoTooltip state

* InfoTooltip: use iconButton instead to achieve keyboard a11y

* fix minor type nit
2022-01-05 13:36:13 +01:00

161 lines
4.3 KiB
TypeScript

import React from 'react';
import { Icon, getSvgSize } from '../Icon/Icon';
import { IconName, IconSize, IconType } from '../../types/icon';
import { stylesFactory } from '../../themes/stylesFactory';
import { css, cx } from '@emotion/css';
import { useTheme2 } from '../../themes/ThemeContext';
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
import { PopoverContent, Tooltip } from '../Tooltip/Tooltip';
import { TooltipPlacement } from '../Tooltip/PopoverController';
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
export type IconButtonVariant = 'primary' | 'secondary' | 'destructive';
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Name of the icon **/
name: IconName;
/** Icon size */
size?: IconSize;
/** @deprecated */
surface?: SurfaceType;
/** Type od the icon - mono or default */
iconType?: IconType;
/** Tooltip content to display on hover */
tooltip?: PopoverContent;
/** Position of the tooltip */
tooltipPlacement?: TooltipPlacement;
/** Variant to change the color of the Icon */
variant?: IconButtonVariant;
/** Text avilable ony for screenscreen readers. Will use tooltip text as fallback. */
ariaLabel?: string;
}
type SurfaceType = 'dashboard' | 'panel' | 'header';
export const IconButton = React.forwardRef<HTMLButtonElement, Props>(
(
{
name,
size = 'md',
iconType,
tooltip,
tooltipPlacement,
ariaLabel,
className,
variant = 'secondary',
...restProps
},
ref
) => {
const theme = useTheme2();
const styles = getStyles(theme, size, variant);
const tooltipString = typeof tooltip === 'string' ? tooltip : '';
const button = (
<button ref={ref} aria-label={ariaLabel || tooltipString} {...restProps} className={cx(styles.button, className)}>
<Icon name={name} size={size} className={styles.icon} type={iconType} />
</button>
);
if (tooltip) {
return (
<Tooltip content={tooltip} placement={tooltipPlacement}>
{button}
</Tooltip>
);
}
return button;
}
);
IconButton.displayName = 'IconButton';
const getStyles = stylesFactory((theme: GrafanaTheme2, size: IconSize, variant: IconButtonVariant) => {
const pixelSize = getSvgSize(size);
const hoverSize = Math.max(pixelSize / 3, 8);
let iconColor = theme.colors.text.primary;
if (variant === 'primary') {
iconColor = theme.colors.primary.text;
} else if (variant === 'destructive') {
iconColor = theme.colors.error.text;
}
return {
button: css`
width: ${pixelSize}px;
height: ${pixelSize}px;
background: transparent;
border: none;
color: ${iconColor};
padding: 0;
margin: 0;
outline: none;
box-shadow: none;
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
border-radius: ${theme.shape.borderRadius()};
z-index: 0;
margin-right: ${theme.spacing(0.5)};
&[disabled],
&:disabled {
cursor: not-allowed;
color: ${theme.colors.action.disabledText};
opacity: 0.65;
box-shadow: none;
}
&:before {
content: '';
display: block;
opacity: 1;
position: absolute;
transition-duration: 0.2s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
z-index: -1;
bottom: -${hoverSize}px;
left: -${hoverSize}px;
right: -${hoverSize}px;
top: -${hoverSize}px;
background: none;
border-radius: 50%;
box-sizing: border-box;
transform: scale(0);
transition-property: transform, opacity;
}
&:focus,
&:focus-visible {
${getFocusStyles(theme)}
}
&:focus:not(:focus-visible) {
${getMouseFocusStyles(theme)}
}
&:hover {
color: ${iconColor};
&:before {
background-color: ${variant === 'secondary'
? theme.colors.action.hover
: colorManipulator.alpha(iconColor, 0.12)};
border: none;
box-shadow: none;
opacity: 1;
transform: scale(0.8);
}
}
`,
icon: css`
margin-bottom: 0;
vertical-align: baseline;
display: flex;
`,
};
});