Skip to content

Commit

Permalink
fix(react-button): apply hover only on devices that support hover
Browse files Browse the repository at this point in the history
  • Loading branch information
mainframev committed Jun 13, 2024
1 parent a9fdb46 commit 81628a4
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 243 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { renderButton_unstable } from './renderButton';
import { useButton_unstable } from './useButton';
import { useButtonStyles_unstable } from './useButtonStyles.styles';
import { useButtonStyles_unstable } from './useButtonStyles';
import type { ButtonProps } from './Button.types';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,65 +1,2 @@
import { mergeClasses } from '@griffel/react';
import type { ButtonState } from '../Button.types';
import { buttonClassNames } from './consts';
import { useRootBaseClassName } from './useRootBaseClassName.styles';
import { useIconBaseClassName } from './useIconBaseClassName.styles';
import { useRootStyles } from './useRootStyles.styles';
import { useRootDisabledStyles } from './useRootDisabledStyles.styles';
import { useRootFocusStyles } from './useRootFocusStyles.styles';
import { useRootIconOnlyStyles } from './useRootIconOnlyStates.styles';
import { useIconStyles } from './useIconStyles.styles';

export const useButtonStyles_unstable = (state: ButtonState): ButtonState => {
const rootBaseClassName = useRootBaseClassName();
const iconBaseClassName = useIconBaseClassName();

const rootStyles = useRootStyles();
const rootDisabledStyles = useRootDisabledStyles();
const rootFocusStyles = useRootFocusStyles();
const rootIconOnlyStyles = useRootIconOnlyStyles();
const iconStyles = useIconStyles();

const { appearance, disabled, disabledFocusable, icon, iconOnly, iconPosition, shape, size } = state;

state.root.className = mergeClasses(
buttonClassNames.root,
rootBaseClassName,

appearance && rootStyles[appearance],

rootStyles[size],
icon && size === 'small' && rootStyles.smallWithIcon,
icon && size === 'large' && rootStyles.largeWithIcon,
rootStyles[shape],

// Disabled styles
(disabled || disabledFocusable) && rootDisabledStyles.base,
(disabled || disabledFocusable) && rootDisabledStyles.highContrast,
appearance && (disabled || disabledFocusable) && rootDisabledStyles[appearance],

// Focus styles
appearance === 'primary' && rootFocusStyles.primary,
rootFocusStyles[size],
rootFocusStyles[shape],

// Icon-only styles
iconOnly && rootIconOnlyStyles[size],

// User provided class name
state.root.className,
);

if (state.icon) {
state.icon.className = mergeClasses(
buttonClassNames.icon,
iconBaseClassName,
!!state.root.children && iconStyles[iconPosition],
iconStyles[size],
state.icon.className,
);
}

return state;
};

export { buttonClassNames };
export { useButtonStyles_unstable } from './useButtonStyles.styles';
export { buttonClassNames } from './constants';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { mergeClasses } from '@griffel/react';
import type { ButtonState } from '../Button.types';
import { buttonClassNames } from './constants';
import { useRootBaseClassName } from './useRootBaseClassName.styles';
import { useIconBaseClassName } from './useIconBaseClassName.styles';
import { useRootStyles } from './useRootStyles.styles';
import { useRootDisabledStyles } from './useRootDisabledStyles.styles';
import { useRootFocusStyles } from './useRootFocusStyles.styles';
import { useRootIconOnlyStyles } from './useRootIconOnlyStates.styles';
import { useIconStyles } from './useIconStyles.styles';

export const useButtonStyles_unstable = (state: ButtonState): ButtonState => {
const rootBaseClassName = useRootBaseClassName();
const iconBaseClassName = useIconBaseClassName();

const rootStyles = useRootStyles();
const rootDisabledStyles = useRootDisabledStyles();
const rootFocusStyles = useRootFocusStyles();
const rootIconOnlyStyles = useRootIconOnlyStyles();
const iconStyles = useIconStyles();

const { appearance, disabled, disabledFocusable, icon, iconOnly, iconPosition, shape, size } = state;

state.root.className = mergeClasses(
buttonClassNames.root,
rootBaseClassName,

appearance && rootStyles[appearance],

rootStyles[size],
icon && size === 'small' && rootStyles.smallWithIcon,
icon && size === 'large' && rootStyles.largeWithIcon,
rootStyles[shape],

// Disabled styles
(disabled || disabledFocusable) && rootDisabledStyles.base,
(disabled || disabledFocusable) && rootDisabledStyles.highContrast,
appearance && (disabled || disabledFocusable) && rootDisabledStyles[appearance],

// Focus styles
appearance === 'primary' && rootFocusStyles.primary,
rootFocusStyles[size],
rootFocusStyles[shape],

// Icon-only styles
iconOnly && rootIconOnlyStyles[size],

// User provided class name
state.root.className,
);

if (state.icon) {
state.icon.className = mergeClasses(
buttonClassNames.icon,
iconBaseClassName,
!!state.root.children && iconStyles[iconPosition],
iconStyles[size],
state.icon.className,
);
}

return state;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { makeResetStyles } from '@griffel/react';
import { iconSpacingVar } from './consts';
import { iconSpacingVar } from './constants';
import { tokens } from '@fluentui/react-theme';

export const useIconBaseClassName = makeResetStyles({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { tokens } from '@fluentui/react-theme';
import { makeStyles } from '@griffel/react';
import { iconSpacingVar } from './consts';
import { iconSpacingVar } from './constants';

export const useIconStyles = makeStyles({
// Size variations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { makeResetStyles } from '@griffel/react';
import { createCustomFocusIndicatorStyle } from '@fluentui/react-tabster';
import { tokens } from '@fluentui/react-theme';
import { boxShadowStrokeWidthThinMoz, buttonSpacingMedium } from './consts';
import { boxShadowStrokeWidthThinMoz, buttonSpacingMedium } from './constants';

export const useRootBaseClassName = makeResetStyles({
alignItems: 'center',
Expand All @@ -21,13 +21,15 @@ export const useRootBaseClassName = makeResetStyles({
fontFamily: tokens.fontFamilyBase,
outlineStyle: 'none',

// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#hover
':hover': {
backgroundColor: tokens.colorNeutralBackground1Hover,
borderColor: tokens.colorNeutralStroke1Hover,
color: tokens.colorNeutralForeground1Hover,
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
backgroundColor: tokens.colorNeutralBackground1Hover,
borderColor: tokens.colorNeutralStroke1Hover,
color: tokens.colorNeutralForeground1Hover,

cursor: 'pointer',
cursor: 'pointer',
},
},

':hover:active': {
Expand Down Expand Up @@ -62,11 +64,14 @@ export const useRootBaseClassName = makeResetStyles({
borderColor: 'ButtonText',
},

':hover': {
backgroundColor: 'HighlightText',
borderColor: 'Highlight',
color: 'Highlight',
forcedColorAdjust: 'none',
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
backgroundColor: 'HighlightText',
borderColor: 'Highlight',
color: 'Highlight',
forcedColorAdjust: 'none',
},
},

':hover:active': {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { tokens } from '@fluentui/react-theme';
import { shorthands, makeStyles } from '@griffel/react';
import { iconFilledClassName, iconRegularClassName } from '@fluentui/react-icons';
import { buttonClassNames } from './consts';
import { buttonClassNames } from './constants';

export const useRootDisabledStyles = makeStyles({
// Base styles
Expand All @@ -15,21 +15,24 @@ export const useRootDisabledStyles = makeStyles({
color: tokens.colorNeutralForegroundDisabled,
},

':hover': {
backgroundColor: tokens.colorNeutralBackgroundDisabled,
...shorthands.borderColor(tokens.colorNeutralStrokeDisabled),
color: tokens.colorNeutralForegroundDisabled,

cursor: 'not-allowed',

[`& .${iconFilledClassName}`]: {
display: 'none',
},
[`& .${iconRegularClassName}`]: {
display: 'inline',
},
[`& .${buttonClassNames.icon}`]: {
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
backgroundColor: tokens.colorNeutralBackgroundDisabled,
...shorthands.borderColor(tokens.colorNeutralStrokeDisabled),
color: tokens.colorNeutralForegroundDisabled,

cursor: 'not-allowed',

[`& .${iconFilledClassName}`]: {
display: 'none',
},
[`& .${iconRegularClassName}`]: {
display: 'inline',
},
[`& .${buttonClassNames.icon}`]: {
color: tokens.colorNeutralForegroundDisabled,
},
},
},

Expand Down Expand Up @@ -63,10 +66,13 @@ export const useRootDisabledStyles = makeStyles({
...shorthands.borderColor('GrayText'),
},

':hover': {
backgroundColor: 'ButtonFace',
...shorthands.borderColor('GrayText'),
color: 'GrayText',
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
backgroundColor: 'ButtonFace',
...shorthands.borderColor('GrayText'),
color: 'GrayText',
},
},

':hover:active': {
Expand All @@ -80,8 +86,11 @@ export const useRootDisabledStyles = makeStyles({
// Appearance variations
outline: {
backgroundColor: tokens.colorTransparentBackground,
':hover': {
backgroundColor: tokens.colorTransparentBackground,
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
backgroundColor: tokens.colorTransparentBackground,
},
},

':hover:active': {
Expand All @@ -90,8 +99,11 @@ export const useRootDisabledStyles = makeStyles({
},
primary: {
...shorthands.borderColor('transparent'),
':hover': {
...shorthands.borderColor('transparent'),
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
...shorthands.borderColor('transparent'),
},
},

':hover:active': {
Expand All @@ -104,9 +116,12 @@ export const useRootDisabledStyles = makeStyles({
subtle: {
backgroundColor: tokens.colorTransparentBackground,
...shorthands.borderColor('transparent'),
':hover': {
backgroundColor: tokens.colorTransparentBackground,
...shorthands.borderColor('transparent'),
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
backgroundColor: tokens.colorTransparentBackground,
...shorthands.borderColor('transparent'),
},
},

':hover:active': {
Expand All @@ -117,9 +132,12 @@ export const useRootDisabledStyles = makeStyles({
transparent: {
backgroundColor: tokens.colorTransparentBackground,
...shorthands.borderColor('transparent'),
':hover': {
backgroundColor: tokens.colorTransparentBackground,
...shorthands.borderColor('transparent'),
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
backgroundColor: tokens.colorTransparentBackground,
...shorthands.borderColor('transparent'),
},
},

':hover:active': {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { makeStyles, shorthands } from '@griffel/react';
import { createCustomFocusIndicatorStyle } from '@fluentui/react-tabster';
import { tokens } from '@fluentui/react-theme';
import { boxShadowStrokeWidthThinMoz } from './consts';
import { boxShadowStrokeWidthThinMoz } from './constants';

export const useRootFocusStyles = makeStyles({
// Shape variations
Expand All @@ -16,18 +16,24 @@ export const useRootFocusStyles = makeStyles({
...createCustomFocusIndicatorStyle({
...shorthands.borderColor(tokens.colorStrokeFocus2),
boxShadow: `${tokens.shadow2}, 0 0 0 ${tokens.strokeWidthThin} ${tokens.colorStrokeFocus2} inset, 0 0 0 ${tokens.strokeWidthThick} ${tokens.colorNeutralForegroundOnBrand} inset`,
':hover': {
boxShadow: `${tokens.shadow2}, 0 0 0 ${tokens.strokeWidthThin} ${tokens.colorStrokeFocus2} inset`,
...shorthands.borderColor(tokens.colorStrokeFocus2),
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
boxShadow: `${tokens.shadow2}, 0 0 0 ${tokens.strokeWidthThin} ${tokens.colorStrokeFocus2} inset`,
...shorthands.borderColor(tokens.colorStrokeFocus2),
},
},
}),

// BUGFIX: Mozilla specific styles (Mozilla BugID: 1857642)
'@supports (-moz-appearance:button)': {
...createCustomFocusIndicatorStyle({
boxShadow: `${tokens.shadow2}, 0 0 0 ${boxShadowStrokeWidthThinMoz} ${tokens.colorStrokeFocus2} inset, 0 0 0 ${tokens.strokeWidthThick} ${tokens.colorNeutralForegroundOnBrand} inset`,
':hover': {
boxShadow: `${tokens.shadow2}, 0 0 0 ${boxShadowStrokeWidthThinMoz} ${tokens.colorStrokeFocus2} inset`,
// Query devices that support hover: https://www.w3.org/TR/mediaqueries-4/#any-input
'@media (any-hover: hover)': {
':hover': {
boxShadow: `${tokens.shadow2}, 0 0 0 ${boxShadowStrokeWidthThinMoz} ${tokens.colorStrokeFocus2} inset`,
},
},
}),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { makeStyles } from '@griffel/react';
import { buttonSpacingLargeWithIcon, buttonSpacingMedium, buttonSpacingSmallWithIcon } from './consts';
import { buttonSpacingLargeWithIcon, buttonSpacingMedium, buttonSpacingSmallWithIcon } from './constants';

export const useRootIconOnlyStyles = makeStyles({
// Size variations
Expand Down
Loading

0 comments on commit 81628a4

Please sign in to comment.