diff --git a/change/@fluentui-web-components-c290858c-65d5-4885-9194-0c9a7fa3c13a.json b/change/@fluentui-web-components-c290858c-65d5-4885-9194-0c9a7fa3c13a.json new file mode 100644 index 00000000000000..36677d231fb8b9 --- /dev/null +++ b/change/@fluentui-web-components-c290858c-65d5-4885-9194-0c9a7fa3c13a.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Fixed foregroundOnAccent recipe to work in all states", + "packageName": "@fluentui/web-components", + "email": "47367562+bheston@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/docs/api-report.md b/packages/web-components/docs/api-report.md index 7b977be3bbdaa8..4447767beeafc2 100644 --- a/packages/web-components/docs/api-report.md +++ b/packages/web-components/docs/api-report.md @@ -575,6 +575,24 @@ export const focusStrokeOuterRecipe: DesignToken; // @public (undocumented) export const focusStrokeWidth: import("@microsoft/fast-foundation").CSSDesignToken; +// @public (undocumented) +export const foregroundOnAccentActive: import("@microsoft/fast-foundation").CSSDesignToken; + +// @public (undocumented) +export const foregroundOnAccentActiveLarge: import("@microsoft/fast-foundation").CSSDesignToken; + +// @public (undocumented) +export const foregroundOnAccentFocus: import("@microsoft/fast-foundation").CSSDesignToken; + +// @public (undocumented) +export const foregroundOnAccentFocusLarge: import("@microsoft/fast-foundation").CSSDesignToken; + +// @public (undocumented) +export const foregroundOnAccentHover: import("@microsoft/fast-foundation").CSSDesignToken; + +// @public (undocumented) +export const foregroundOnAccentHoverLarge: import("@microsoft/fast-foundation").CSSDesignToken; + // @public (undocumented) export const foregroundOnAccentLargeRecipe: DesignToken; diff --git a/packages/web-components/src/color/recipes/accent-fill.spec.ts b/packages/web-components/src/color/recipes/accent-fill.spec.ts deleted file mode 100644 index 03145f17599a85..00000000000000 --- a/packages/web-components/src/color/recipes/accent-fill.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { parseColorHexRGB } from '@microsoft/fast-colors'; -import { expect } from 'chai'; -import { PaletteRGB } from '../palette'; -import { SwatchRGB } from '../swatch'; -import { accentFill } from './accent-fill'; -import { foregroundOnAccent as foregroundOnAccentNew } from './foreground-on-accent'; - -describe('accentFill', (): void => { - const neutralPalette = PaletteRGB.create(SwatchRGB.create(0.5, 0.5, 0.5)); - - it('should have accessible rest and hover colors against accentForegroundCut', (): void => { - const accentColors = [ - SwatchRGB.from(parseColorHexRGB('#0078D4')!), - SwatchRGB.from(parseColorHexRGB('#107C10')!), - SwatchRGB.from(parseColorHexRGB('#5C2D91')!), - SwatchRGB.from(parseColorHexRGB('#D83B01')!), - SwatchRGB.from(parseColorHexRGB('#F2C812')!), - ]; - - accentColors.forEach((accent: SwatchRGB): void => { - const accentPalette = PaletteRGB.create(accent); - - neutralPalette.swatches.forEach((swatch: SwatchRGB): void => { - const accentForegroundCutColor = foregroundOnAccentNew(accentPalette.source, 4.5); - const accentFillColors = accentFill( - accentPalette, - neutralPalette, - swatch, - accentForegroundCutColor, - 4.5, - 4, - -5, - 0, - 7, - 10, - 5, - ); - const accentFillLargeColors = accentFill( - accentPalette, - neutralPalette, - swatch, - accentForegroundCutColor, - 3, - 4, - -5, - 0, - 7, - 10, - 5, - ); - - expect(accentForegroundCutColor.contrast(accentFillColors.rest)).to.be.gte(4.5); - expect(accentForegroundCutColor.contrast(accentFillColors.hover)).to.be.gte(4.5); - expect(accentForegroundCutColor.contrast(accentFillLargeColors.rest)).to.be.gte(3); - expect(accentForegroundCutColor.contrast(accentFillLargeColors.hover)).to.be.gte(3); - }); - }); - }); -}); diff --git a/packages/web-components/src/color/recipes/accent-fill.ts b/packages/web-components/src/color/recipes/accent-fill.ts index 65337500a2d6c7..dae18c6a995464 100644 --- a/packages/web-components/src/color/recipes/accent-fill.ts +++ b/packages/web-components/src/color/recipes/accent-fill.ts @@ -1,4 +1,3 @@ -import { inRange } from 'lodash-es'; import { Palette } from '../palette'; import { InteractiveSwatchSet } from '../recipe'; import { Swatch } from '../swatch'; @@ -10,8 +9,6 @@ export function accentFill( palette: Palette, neutralPalette: Palette, reference: Swatch, - textColor: Swatch, - contrastTarget: number, hoverDelta: number, activeDelta: number, focusDelta: number, @@ -23,21 +20,9 @@ export function accentFill( const referenceIndex = neutralPalette.closestIndexOf(reference); const swapThreshold = Math.max(neutralFillRestDelta, neutralFillHoverDelta, neutralFillActiveDelta); const direction = referenceIndex >= swapThreshold ? -1 : 1; - const paletteLength = palette.swatches.length; - const maxIndex = paletteLength - 1; const accentIndex = palette.closestIndexOf(accent); - let accessibleOffset = 0; - while ( - accessibleOffset < direction * hoverDelta && - inRange(accentIndex + accessibleOffset + direction, 0, paletteLength) && - textColor.contrast(palette.get(accentIndex + accessibleOffset + direction)) >= contrastTarget && - inRange(accentIndex + accessibleOffset + direction + direction, 0, maxIndex) - ) { - accessibleOffset += direction; - } - - const hoverIndex = accentIndex + accessibleOffset; + const hoverIndex = accentIndex; const restIndex = hoverIndex + direction * -1 * hoverDelta; const activeIndex = restIndex + direction * activeDelta; const focusIndex = restIndex + direction * focusDelta; diff --git a/packages/web-components/src/design-tokens.ts b/packages/web-components/src/design-tokens.ts index 30ce40796acff4..8a1812010463d1 100644 --- a/packages/web-components/src/design-tokens.ts +++ b/packages/web-components/src/design-tokens.ts @@ -231,47 +231,12 @@ enum ContrastTarget { large = 7, } -// Foreground On Accent -const foregroundOnAccentByContrast = (contrast: number) => (element: HTMLElement, reference?: Swatch) => - foregroundOnAccentAlgorithm(reference || fillColor.getValueFor(element), contrast); -/** @public */ -export const foregroundOnAccentRecipe = create({ - name: 'foreground-on-accent-recipe', - cssCustomPropertyName: null, -}).withDefault({ - evaluate: (element: HTMLElement, reference?: Swatch): Swatch => - foregroundOnAccentByContrast(ContrastTarget.normal)(element, reference), -}); -/** @public */ -export const foregroundOnAccentLargeRecipe = create({ - name: 'foreground-on-accent-large-recipe', - cssCustomPropertyName: null, -}).withDefault({ - evaluate: (element: HTMLElement, reference?: Swatch): Swatch => - foregroundOnAccentByContrast(ContrastTarget.large)(element, reference), -}); - -/** @public */ -export const foregroundOnAccentRest = create('foreground-on-accent-rest').withDefault((element: HTMLElement) => - foregroundOnAccentRecipe.getValueFor(element).evaluate(element), -); -/** @public @deprecated Use foregroundOnAccentRest */ -export const accentForegroundCut = foregroundOnAccentRest; -/** @public */ -export const foregroundOnAccentRestLarge = create( - 'foreground-on-accent-rest-large', -).withDefault((element: HTMLElement) => foregroundOnAccentLargeRecipe.getValueFor(element).evaluate(element)); -/** @public @deprecated Use foregroundOnAccentRestLarge */ -export const accentForegroundCutLarge = foregroundOnAccentRestLarge; - // Accent Fill const accentFillByContrast = (contrast: number) => (element: HTMLElement, reference?: Swatch) => accentFillAlgorithm( accentPalette.getValueFor(element), neutralPalette.getValueFor(element), reference || fillColor.getValueFor(element), - foregroundOnAccentRest.getValueFor(element), - contrast, accentFillHoverDelta.getValueFor(element), accentFillActiveDelta.getValueFor(element), accentFillFocusDelta.getValueFor(element), @@ -305,6 +270,75 @@ export const accentFillFocus = create('accent-fill-focus').withDefault(( return accentFillRecipe.getValueFor(element).evaluate(element).focus; }); +// Foreground On Accent +const foregroundOnAccentByContrast = (contrast: number) => (element: HTMLElement, reference?: Swatch) => + foregroundOnAccentAlgorithm(reference || accentFillRest.getValueFor(element), contrast); +/** @public */ +export const foregroundOnAccentRecipe = create({ + name: 'foreground-on-accent-recipe', + cssCustomPropertyName: null, +}).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + foregroundOnAccentByContrast(ContrastTarget.normal)(element, reference), +}); +/** @public */ +export const foregroundOnAccentRest = create('foreground-on-accent-rest').withDefault((element: HTMLElement) => + foregroundOnAccentRecipe.getValueFor(element).evaluate(element, accentFillRest.getValueFor(element)), +); +/** @public @deprecated Use foregroundOnAccentRest */ +export const accentForegroundCut = foregroundOnAccentRest; +/** @public */ +export const foregroundOnAccentHover = create( + 'foreground-on-accent-hover', +).withDefault((element: HTMLElement) => + foregroundOnAccentRecipe.getValueFor(element).evaluate(element, accentFillHover.getValueFor(element)), +); +/** @public */ +export const foregroundOnAccentActive = create( + 'foreground-on-accent-active', +).withDefault((element: HTMLElement) => + foregroundOnAccentRecipe.getValueFor(element).evaluate(element, accentFillActive.getValueFor(element)), +); +/** @public */ +export const foregroundOnAccentFocus = create( + 'foreground-on-accent-focus', +).withDefault((element: HTMLElement) => + foregroundOnAccentRecipe.getValueFor(element).evaluate(element, accentFillFocus.getValueFor(element)), +); + +/** @public */ +export const foregroundOnAccentLargeRecipe = create({ + name: 'foreground-on-accent-large-recipe', + cssCustomPropertyName: null, +}).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + foregroundOnAccentByContrast(ContrastTarget.large)(element, reference), +}); +/** @public */ +export const foregroundOnAccentRestLarge = create( + 'foreground-on-accent-rest-large', +).withDefault((element: HTMLElement) => foregroundOnAccentLargeRecipe.getValueFor(element).evaluate(element)); +/** @public @deprecated Use foregroundOnAccentRestLarge */ +export const accentForegroundCutLarge = foregroundOnAccentRestLarge; +/** @public */ +export const foregroundOnAccentHoverLarge = create( + 'foreground-on-accent-hover-large', +).withDefault((element: HTMLElement) => + foregroundOnAccentLargeRecipe.getValueFor(element).evaluate(element, accentFillHover.getValueFor(element)), +); +/** @public */ +export const foregroundOnAccentActiveLarge = create( + 'foreground-on-accent-active-large', +).withDefault((element: HTMLElement) => + foregroundOnAccentLargeRecipe.getValueFor(element).evaluate(element, accentFillActive.getValueFor(element)), +); +/** @public */ +export const foregroundOnAccentFocusLarge = create( + 'foreground-on-accent-focus-large', +).withDefault((element: HTMLElement) => + foregroundOnAccentLargeRecipe.getValueFor(element).evaluate(element, accentFillFocus.getValueFor(element)), +); + // Accent Foreground const accentForegroundByContrast = (contrast: number) => (element: HTMLElement, reference?: Swatch) => accentForegroundAlgorithm( diff --git a/packages/web-components/src/listbox-option/listbox-option.styles.ts b/packages/web-components/src/listbox-option/listbox-option.styles.ts index 4a122135da91ae..2fbce09827a6e3 100644 --- a/packages/web-components/src/listbox-option/listbox-option.styles.ts +++ b/packages/web-components/src/listbox-option/listbox-option.styles.ts @@ -1,10 +1,19 @@ import { css, ElementStyles } from '@microsoft/fast-element'; -import { disabledCursor, display, ElementDefinitionContext, focusVisible, forcedColorsStylesheetBehavior, FoundationElementDefinition } from '@microsoft/fast-foundation'; +import { + disabledCursor, + display, + ElementDefinitionContext, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementDefinition, +} from '@microsoft/fast-foundation'; import { SystemColors } from '@microsoft/fast-web-utilities'; import { heightNumber } from '../styles/size'; import { accentFillActive, + accentFillFocus, accentFillHover, + accentFillRest, bodyFont, controlCornerRadius, designUnit, @@ -12,6 +21,9 @@ import { focusStrokeInner, focusStrokeOuter, focusStrokeWidth, + foregroundOnAccentActive, + foregroundOnAccentFocus, + foregroundOnAccentHover, foregroundOnAccentRest, neutralFillActive, neutralFillHover, @@ -20,7 +32,10 @@ import { typeRampBaseLineHeight, } from '../design-tokens'; -export const optionStyles: (context: ElementDefinitionContext, definition: FoundationElementDefinition) => ElementStyles = (context: ElementDefinitionContext, definition: FoundationElementDefinition) => +export const optionStyles: ( + context: ElementDefinitionContext, + definition: FoundationElementDefinition, +) => ElementStyles = (context: ElementDefinitionContext, definition: FoundationElementDefinition) => css` ${display('inline-flex')} :host { font-family: ${bodyFont}; @@ -45,18 +60,23 @@ export const optionStyles: (context: ElementDefinitionContext, definition: Found :host(:${focusVisible}) { box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) inset ${focusStrokeInner}; border-color: ${focusStrokeOuter}; - background: ${accentFillHover}; - color: ${foregroundOnAccentRest}; + background: ${accentFillFocus}; + color: ${foregroundOnAccentFocus}; } :host([aria-selected="true"]) { - background: ${accentFillHover}; + background: ${accentFillRest}; color: ${foregroundOnAccentRest}; } + :host(:hover) { + background: ${accentFillHover}; + color: ${foregroundOnAccentHover}; + } + :host(:active) { background: ${accentFillActive}; - color: ${foregroundOnAccentRest}; + color: ${foregroundOnAccentActive}; } :host(:not([aria-selected="true"]):hover) { diff --git a/packages/web-components/src/select/select.styles.ts b/packages/web-components/src/select/select.styles.ts index 12fbb92507a2bb..79716c68a86acb 100644 --- a/packages/web-components/src/select/select.styles.ts +++ b/packages/web-components/src/select/select.styles.ts @@ -1,18 +1,25 @@ import { css, ElementStyles } from '@microsoft/fast-element'; -import { disabledCursor, display, ElementDefinitionContext, focusVisible, forcedColorsStylesheetBehavior, SelectOptions } from '@microsoft/fast-foundation'; +import { + disabledCursor, + display, + ElementDefinitionContext, + focusVisible, + forcedColorsStylesheetBehavior, + SelectOptions, +} from '@microsoft/fast-foundation'; import { SystemColors } from '@microsoft/fast-web-utilities'; import { elevation } from '../styles/elevation'; import { heightNumber } from '../styles/size'; import { appearanceBehavior } from '../utilities/behaviors'; import { - accentFillHover, - accentForegroundCut, + accentFillFocus, bodyFont, controlCornerRadius, designUnit, disabledOpacity, focusStrokeInner, focusStrokeOuter, + foregroundOnAccentFocus, neutralFillHover, neutralFillInputActive, neutralFillInputHover, @@ -29,7 +36,10 @@ import { typeRampBaseLineHeight, } from '../design-tokens'; -export const selectFilledStyles: (context: ElementDefinitionContext, definition: SelectOptions) => ElementStyles = (context: ElementDefinitionContext, definition: SelectOptions) => css` +export const selectFilledStyles: (context: ElementDefinitionContext, definition: SelectOptions) => ElementStyles = ( + context: ElementDefinitionContext, + definition: SelectOptions, +) => css` :host([appearance="filled"]) { background: ${neutralFillRest}; border-color: transparent; @@ -121,8 +131,8 @@ export const selectStyles = (context, definition) => :host(:${focusVisible}) ::slotted([aria-selected="true"][role="option"]:not([disabled])) { box-shadow: 0 0 0 calc(var(--focus-outline-width) * 1px) inset ${focusStrokeInner}; border-color: ${focusStrokeOuter}; - background: ${accentFillHover}; - color: ${accentForegroundCut}; + background: ${accentFillFocus}; + color: ${foregroundOnAccentFocus}; } :host([disabled]) { diff --git a/packages/web-components/src/styles/patterns/button.ts b/packages/web-components/src/styles/patterns/button.ts index 19f34d6a45e84d..c0e8eca60375e0 100644 --- a/packages/web-components/src/styles/patterns/button.ts +++ b/packages/web-components/src/styles/patterns/button.ts @@ -7,7 +7,6 @@ import { accentFillHover, accentFillRest, accentForegroundActive, - accentForegroundCut, accentForegroundHover, accentForegroundRest, bodyFont, @@ -17,6 +16,9 @@ import { focusStrokeInner, focusStrokeOuter, focusStrokeWidth, + foregroundOnAccentActive, + foregroundOnAccentHover, + foregroundOnAccentRest, neutralFillActive, neutralFillHover, neutralFillRest, @@ -184,15 +186,17 @@ export const baseButtonStyles = (context, definition) => export const AccentButtonStyles = css` :host([appearance="accent"]) { background: ${accentFillRest}; - color: ${accentForegroundCut}; + color: ${foregroundOnAccentRest}; } :host([appearance="accent"]:hover) { background: ${accentFillHover}; + color: ${foregroundOnAccentHover}; } :host([appearance="accent"]:active) .control:active { background: ${accentFillActive}; + color: ${foregroundOnAccentActive}; } :host([appearance="accent"]) .control:${focusVisible} { diff --git a/packages/web-components/src/switch/switch.styles.ts b/packages/web-components/src/switch/switch.styles.ts index d7a490e30adbb5..87c23955726c2e 100644 --- a/packages/web-components/src/switch/switch.styles.ts +++ b/packages/web-components/src/switch/switch.styles.ts @@ -11,6 +11,8 @@ import { disabledOpacity, fillColor, focusStrokeOuter, + foregroundOnAccentActive, + foregroundOnAccentHover, foregroundOnAccentRest, neutralFillInputActive, neutralFillInputHover, @@ -129,10 +131,18 @@ export const switchStyles: (context: ElementDefinitionContext, defintiion: Switc background: ${accentFillHover}; } + :host([aria-checked="true"]:enabled) .switch:hover .checked-indicator { + background: ${foregroundOnAccentHover}; + } + :host([aria-checked="true"]:enabled) .switch:active { background: ${accentFillActive}; } + :host([aria-checked="true"]:enabled) .switch:active .checked-indicator { + background: ${foregroundOnAccentActive}; + } + :host([aria-checked="true"]:${focusVisible}:enabled) .switch { box-shadow: 0 0 0 2px ${fillColor}, 0 0 0 4px ${focusStrokeOuter}; border-color: transparent;