Buttons: Active style for buttons (#111235)

* chore: initial tweaks

* chore: remove shadow

* chore: add reduced motion support, clean up

* chore: clean

* chore: active states tweaks

* Buttons: Add active state / style

* Update ToolbarButton and IconButton

* Update

* Update

* Get rid of important on disabled styles

* Only color for active

* Remove transform bits

---------

Co-authored-by: Galen <galen.kistler@grafana.com>
This commit is contained in:
Torkel Ödegaard
2025-09-19 16:10:33 +02:00
committed by GitHub
co-authored by Galen
parent c20fdbe39e
commit 3dc30b5acb
4 changed files with 78 additions and 35 deletions
@@ -5,7 +5,7 @@ import * as React from 'react';
import { GrafanaTheme2, ThemeRichColor } from '@grafana/data';
import { useTheme2 } from '../../themes/ThemeContext';
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
import { getButtonFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
import { IconName, IconSize, IconType } from '../../types/icon';
import { ComponentSize } from '../../types/size';
import { getPropertiesForButtonSize } from '../Forms/commonStyles';
@@ -234,7 +234,7 @@ export const getButtonStyles = (props: StyleProps) => {
const { height, padding, fontSize } = getPropertiesForButtonSize(size, theme);
const variantStyles = getPropertiesForVariant(theme, variant, fill);
const disabledStyles = getPropertiesForDisabled(theme, variant, fill);
const focusStyle = getFocusStyles(theme);
const focusStyle = getButtonFocusStyles(theme);
const paddingMinusBorder = theme.spacing.gridSize * padding - 1;
return {
@@ -263,6 +263,12 @@ export const getButtonStyles = (props: StyleProps) => {
...variantStyles,
':disabled': disabledStyles,
'&[disabled]': disabledStyles,
[theme.transitions.handleMotion('no-preference', 'reduce')]: {
transition: theme.transitions.create(['background-color', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
},
}),
disabled: css(disabledStyles, {
'&:hover': css(disabledStyles),
@@ -290,12 +296,18 @@ export const getButtonStyles = (props: StyleProps) => {
};
};
function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fill: ButtonFill) {
export function getActiveButtonStyles(color: ThemeRichColor, fill: ButtonFill) {
return {
background: fill === 'solid' ? color.main : 'transparent',
};
}
export function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fill: ButtonFill) {
let outlineBorderColor = color.border;
let borderColor = 'transparent';
let hoverBorderColor = 'transparent';
// Secondary button has some special rules as we lack theem color token to
// Secondary button has some special rules as we lack the color token to
// specify border color for normal button vs border color for outline button
if (color.name === 'secondary') {
borderColor = color.border;
@@ -308,15 +320,16 @@ function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fil
background: 'transparent',
color: color.text,
border: `1px solid ${outlineBorderColor}`,
transition: theme.transitions.create(['background-color', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
'&:hover': {
'&:hover, &:focus': {
background: color.transparent,
borderColor: theme.colors.emphasize(outlineBorderColor, 0.25),
color: color.text,
},
'&:active': {
...getActiveButtonStyles(color, fill),
},
};
}
@@ -325,18 +338,15 @@ function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fil
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': {
'&:hover, &:focus': {
background: color.transparent,
textDecoration: 'none',
outline: 'none',
},
'&:active': {
...getActiveButtonStyles(color, fill),
},
};
}
@@ -345,9 +355,6 @@ function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fil
background: color.main,
color: color.contrastText,
border: `1px solid ${borderColor}`,
transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
'&:hover': {
background: color.shade,
@@ -355,6 +362,15 @@ function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fil
boxShadow: theme.shadows.z1,
borderColor: hoverBorderColor,
},
'&:focus': {
background: color.shade,
color: color.contrastText,
},
'&:active': {
...getActiveButtonStyles(color, fill),
},
};
}
@@ -364,6 +380,7 @@ function getPropertiesForDisabled(theme: GrafanaTheme2, variant: ButtonVariant,
boxShadow: 'none',
color: theme.colors.text.disabled,
transition: 'none',
background: theme.colors.action.disabledBackground,
};
if (fill === 'text') {
@@ -1,13 +1,13 @@
import { css, cx } from '@emotion/css';
import * as React from 'react';
import { GrafanaTheme2, colorManipulator, deprecationWarning } from '@grafana/data';
import { GrafanaTheme2, deprecationWarning } from '@grafana/data';
import { useStyles2 } from '../../themes/ThemeContext';
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
import { IconName, IconSize, IconType } from '../../types/icon';
import { ComponentSize } from '../../types/size';
import { IconRenderer } from '../Button/Button';
import { getActiveButtonStyles, IconRenderer } from '../Button/Button';
import { getSvgSize } from '../Icon/utils';
import { Tooltip } from '../Tooltip/Tooltip';
import { PopoverContent, TooltipPlacement } from '../Tooltip/types';
@@ -107,13 +107,17 @@ const getStyles = (theme: GrafanaTheme2, size: IconSize, variant: IconButtonVari
// overall size of the IconButton on hover
// theme.spacing.gridSize originates from 2*4px for padding and letting the IconSize generally decide on the hoverSize
const hoverSize = getSvgSize(size) + theme.spacing.gridSize;
const activeButtonStyle = getActiveButtonStyles(theme.colors.secondary, 'text');
let iconColor = theme.colors.text.primary;
let iconColor = theme.colors.primary.text;
let hoverColor = theme.colors.primary.transparent;
if (variant === 'primary') {
iconColor = theme.colors.primary.text;
if (variant === 'secondary') {
iconColor = theme.colors.secondary.text;
hoverColor = theme.colors.secondary.transparent;
} else if (variant === 'destructive') {
iconColor = theme.colors.error.text;
hoverColor = theme.colors.error.transparent;
}
return {
@@ -129,11 +133,21 @@ const getStyles = (theme: GrafanaTheme2, size: IconSize, variant: IconButtonVari
alignItems: 'center',
padding: 0,
color: iconColor,
borderRadius: theme.shape.radius.default,
'&:active': {
'&:before, &:hover:before': {
backgroundColor: activeButtonStyle.background,
},
},
'&[disabled], &:disabled': {
cursor: 'not-allowed',
color: theme.colors.action.disabledText,
opacity: 0.65,
'&:hover:before': {
backgroundColor: 'transparent',
},
},
'&:before': {
@@ -155,12 +169,9 @@ const getStyles = (theme: GrafanaTheme2, size: IconSize, variant: IconButtonVari
'&:focus:not(:focus-visible)': getMouseFocusStyles(theme),
'&:hover': {
'&:before': {
backgroundColor:
variant === 'secondary' ? theme.colors.action.hover : colorManipulator.alpha(iconColor, 0.12),
opacity: 1,
},
'&:hover:before': {
backgroundColor: hoverColor,
opacity: 1,
},
}),
icon: css({
@@ -8,7 +8,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { useStyles2 } from '../../themes/ThemeContext';
import { getFocusStyles, getMouseFocusStyles, mediaUp } from '../../themes/mixins';
import { IconSize } from '../../types/icon';
import { getPropertiesForVariant } from '../Button/Button';
import { getActiveButtonStyles, getPropertiesForVariant } from '../Button/Button';
import { Icon } from '../Icon/Icon';
import { Tooltip } from '../Tooltip/Tooltip';
@@ -134,11 +134,15 @@ const getStyles = (theme: GrafanaTheme2) => {
color: theme.colors.text.primary,
background: theme.colors.secondary.main,
'&:hover': {
'&:hover, &:focus': {
color: theme.colors.text.primary,
background: theme.colors.secondary.shade,
border: `1px solid ${theme.colors.border.medium}`,
},
'&:active': {
...getActiveButtonStyles(theme.colors.secondary, 'solid'),
},
});
return {
@@ -155,7 +159,7 @@ const getStyles = (theme: GrafanaTheme2) => {
border: `1px solid ${theme.colors.secondary.border}`,
whiteSpace: 'nowrap',
[theme.transitions.handleMotion('no-preference', 'reduce')]: {
transition: theme.transitions.create(['background', 'box-shadow', 'border-color', 'color'], {
transition: theme.transitions.create(['background-color', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
},
@@ -189,10 +193,14 @@ const getStyles = (theme: GrafanaTheme2) => {
background: 'transparent',
border: `1px solid transparent`,
'&:hover': {
'&:hover, &:focus': {
color: theme.colors.text.primary,
background: theme.colors.action.hover,
},
'&:active': {
...getActiveButtonStyles(theme.colors.secondary, 'solid'),
},
}),
canvas: defaultOld,
active: cx(
+7
View File
@@ -72,6 +72,13 @@ export function getFocusStyles(theme: GrafanaTheme2) {
};
}
export function getButtonFocusStyles(theme: GrafanaTheme2) {
return {
...getFocusStyles(theme),
transitionProperty: undefined,
};
}
// max-width is set up based on .grafana-tooltip class that's used in dashboard
export const getTooltipContainerStyles = (theme: GrafanaTheme2) => ({
overflow: 'hidden',