Skip to content

Commit 6cabf9e

Browse files
authored
AUI: Updated layer fill interactive to invert colors to avoid white on white scenario (#289)
1 parent 46beeb4 commit 6cabf9e

7 files changed

Lines changed: 112 additions & 13 deletions

File tree

.changeset/upset-ties-spend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@adaptive-web/adaptive-ui": patch
3+
---
4+
5+
AUI: Updated layer fill interactive to invert colors to avoid white on white scenario

packages/adaptive-ui-explorer/src/components/color-block.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
highlightFillSubtleInverseControlStyles,
2020
highlightForegroundReadableControlStyles,
2121
highlightOutlineDiscernibleControlStyles,
22+
layerFillInteractiveControlStyles,
2223
neutralDividerDiscernibleElementStyles,
2324
neutralDividerSubtleElementStyles,
2425
neutralFillDiscernibleControlStyles,
@@ -82,6 +83,10 @@ const backplateComponents = html<ColorBlock>`
8283
Accent subtle inverse
8384
</app-style-example>
8485
86+
<app-style-example :disabledState=${x => x.disabledState} :showSwatches=${x => x.showSwatches} :styles="${x => layerFillInteractiveControlStyles}">
87+
Layer interactive
88+
</app-style-example>
89+
8590
<app-style-example :disabledState=${x => x.disabledState} :showSwatches=${x => x.showSwatches} :styles="${x => neutralFillIdealControlStyles}">
8691
Neutral ideal
8792
</app-style-example>

packages/adaptive-ui/docs/api-report.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,9 @@ export type InteractivityDefinition = {
466466
interactive?: string;
467467
};
468468

469+
// @public
470+
export function invertingPaletteDeltasForSet(palette: Palette, reference: RelativeLuminance, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta: number): InteractiveValues<number>;
471+
469472
// @public
470473
export function isDark(color: RelativeLuminance): boolean;
471474

packages/adaptive-ui/src/core/color/recipes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from "./contrast-and-delta-swatch-set.js";
44
export * from "./contrast-swatch.js";
55
export * from "./delta-swatch-set.js";
66
export * from "./delta-swatch.js";
7+
export * from "./inverting-palette-deltas-for-set.js";
78
export * from "./hue-shift-gradient.js";
89
export * from "./ideal-color-delta-swatch-set.js";
910
export * from "./two-palette-gradient.js";
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { InteractiveValues } from "../../types.js";
2+
import { Palette } from "../palette.js";
3+
import { RelativeLuminance } from "../utilities/relative-luminance.js";
4+
5+
/**
6+
* Checks if the supplied delta set for palette access is valid, or flips sign if not to keep deltas in bounds.
7+
*
8+
* Returns a new set of deltas so the indices for all states (rest, hover, active, focus) are in bounds.
9+
* Flipping is always based on the rest delta; the other deltas are adjusted to preserve their *relative* difference to rest.
10+
*
11+
* @param palette - The Palette used to find the Colors
12+
* @param reference - The reference color
13+
* @param restDelta - The rest state offset from `reference`
14+
* @param hoverDelta - The hover state offset from `reference`
15+
* @param activeDelta - The active state offset from `reference`
16+
* @param focusDelta - The focus state offset from `reference`
17+
* @param disabledDelta - The disabled state offset from `reference`
18+
* @returns An interactive set of deltas with possibly flipped and shifted values
19+
*
20+
* @public
21+
*/
22+
export function invertingPaletteDeltasForSet(palette: Palette, reference: RelativeLuminance, restDelta: number, hoverDelta: number, activeDelta: number, focusDelta: number, disabledDelta: number): InteractiveValues<number> {
23+
const referenceIndex = palette.closestIndexOf(reference);
24+
const deltas = [restDelta, hoverDelta, activeDelta, focusDelta];
25+
26+
// Compute the indices they'll hit
27+
const indices = deltas.map(d => referenceIndex + d);
28+
29+
// Check if all indices are within palette bounds
30+
const withinBounds = indices.every(idx => idx >= 0 && idx < palette.swatches.length);
31+
32+
if (withinBounds) {
33+
// All are in bounds as-is
34+
return {
35+
rest: restDelta,
36+
hover: hoverDelta,
37+
active: activeDelta,
38+
focus: focusDelta,
39+
disabled: disabledDelta,
40+
};
41+
}
42+
43+
// The offset of other deltas from restDelta
44+
const hoverOffset = hoverDelta - restDelta;
45+
const activeOffset = activeDelta - restDelta;
46+
const focusOffset = focusDelta - restDelta;
47+
const disabledOffset = disabledDelta - restDelta;
48+
49+
const flippedRestDelta = restDelta * -1;
50+
51+
return {
52+
rest: flippedRestDelta,
53+
hover: flippedRestDelta + hoverOffset,
54+
active: flippedRestDelta + activeOffset,
55+
focus: flippedRestDelta + focusOffset,
56+
disabled: flippedRestDelta + disabledOffset,
57+
};
58+
}

packages/adaptive-ui/src/reference/layer.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DesignTokenResolver } from "@microsoft/fast-foundation";
22
import { DesignTokenType } from "../core/adaptive-design-tokens.js";
33
import { Palette, PaletteDirectionValue } from "../core/color/palette.js";
44
import { ColorRecipeParams, InteractivePaintSet } from "../core/color/recipe.js";
5-
import { deltaSwatch, deltaSwatchSet } from "../core/color/recipes/index.js";
5+
import { deltaSwatch, deltaSwatchSet, invertingPaletteDeltasForSet } from "../core/color/recipes/index.js";
66
import { Color } from "../core/color/color.js";
77
import { luminanceSwatch } from "../core/color/utilities/luminance-swatch.js";
88
import { StyleProperty } from "../core/modules/types.js";
@@ -50,7 +50,7 @@ export const layerFillRestDelta = createTokenDelta(layerFillName, "layer", -2);
5050

5151
/**
5252
* @public
53-
* @deprecated Use `layerFillLayerDelta` instead.
53+
* @deprecated Use `layerFillRestDelta` instead.
5454
*/
5555
export const layerFillDelta = layerFillRestDelta;
5656

@@ -246,18 +246,27 @@ export const layerFillDisabledDelta = createTokenDelta(layerFillInteractiveName,
246246
* @public
247247
*/
248248
export const layerFillInteractiveRecipe = createTokenColorRecipe<InteractivePaintSet>(layerFillInteractiveName, StyleProperty.backgroundFill,
249-
(resolve: DesignTokenResolver, params?: ColorRecipeParams): InteractivePaintSet =>
250-
deltaSwatchSet(
251-
resolve(layerPalette),
252-
params?.reference || resolve(colorContext),
253-
resolve(layerFillRestDelta),
254-
resolve(layerFillHoverDelta),
255-
resolve(layerFillActiveDelta),
256-
resolve(layerFillFocusDelta),
257-
resolve(layerFillDisabledDelta),
249+
(resolve: DesignTokenResolver, params?: ColorRecipeParams): InteractivePaintSet => {
250+
const palette = resolve(layerPalette);
251+
const reference = params?.reference || resolve(colorContext);
252+
const restDelta = resolve(layerFillRestDelta);
253+
const hoverDelta = resolve(layerFillHoverDelta);
254+
const activeDelta = resolve(layerFillActiveDelta);
255+
const focusDelta = resolve(layerFillFocusDelta);
256+
const disabledDelta = resolve(layerFillDisabledDelta);
257+
const deltas = invertingPaletteDeltasForSet(palette, reference, restDelta, hoverDelta, activeDelta, focusDelta, disabledDelta);
258+
return deltaSwatchSet(
259+
palette,
260+
reference,
261+
deltas.rest,
262+
deltas.hover,
263+
deltas.active,
264+
deltas.focus,
265+
deltas.disabled,
258266
undefined,
259267
PaletteDirectionValue.darker,
260-
),
268+
);
269+
}
261270
);
262271

263272
export const layerFillInteractive = createTokenColorSet(layerFillInteractiveRecipe);

packages/adaptive-ui/src/reference/modules.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ import {
8181
} from "./color.js";
8282
import { densityControl, densityControlList, densityItemContainer, densityLayer, densityText } from "./density.js";
8383
import { elevationCardInteractive, elevationCardRest, elevationDialog, elevationFlyout, elevationTooltip } from "./elevation.js";
84-
import { layerFillFixedPlus1 } from "./layer.js";
84+
import { layerFillFixedPlus1, layerFillInteractive } from "./layer.js";
8585
import {
8686
fontFamily,
8787
fontWeight,
@@ -1137,6 +1137,24 @@ export const criticalForegroundReadableControlStyles: Styles = Styles.fromProper
11371137
"color.critical-foreground-readable-control",
11381138
);
11391139

1140+
/**
1141+
* Convenience style module for a layer-filled control (interactive).
1142+
*
1143+
* By default, only the foreground color meets accessibility, useful for a button or similar:
1144+
* - layer interactive background
1145+
* - neutral strong foreground (a11y)
1146+
* - transparent border
1147+
*
1148+
* @public
1149+
*/
1150+
export const layerFillInteractiveControlStyles: Styles = Styles.fromProperties(
1151+
{
1152+
...Fill.backgroundAndForeground(layerFillInteractive, neutralStrokeStrongRecipe),
1153+
...densityBorderStyles(transparent),
1154+
},
1155+
"color.layer-fill-interactive-control",
1156+
);
1157+
11401158
/**
11411159
* Convenience style module for a neutral-filled stealth control (interactive).
11421160
*

0 commit comments

Comments
 (0)