Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated to generalized color recipes, mostly from Fluent UI #5470

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions packages/web-components/fast-components/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -1456,7 +1456,22 @@ export const neutralFillInputRest: import("@microsoft/fast-foundation").CSSDesig
export const neutralFillInputRestDelta: DesignToken<number>;

// @public (undocumented)
export const neutralFillLayerRecipe: DesignToken<ColorRecipe>;
export const neutralFillLayerActive: import("@microsoft/fast-foundation").CSSDesignToken<Swatch>;

// @public (undocumented)
export const neutralFillLayerActiveDelta: DesignToken<number>;

// @public (undocumented)
export const neutralFillLayerFocus: import("@microsoft/fast-foundation").CSSDesignToken<Swatch>;

// @public (undocumented)
export const neutralFillLayerHover: import("@microsoft/fast-foundation").CSSDesignToken<Swatch>;

// @public (undocumented)
export const neutralFillLayerHoverDelta: DesignToken<number>;

// @public (undocumented)
export const neutralFillLayerRecipe: DesignToken<InteractiveColorRecipe>;

// @public (undocumented)
export const neutralFillLayerRest: import("@microsoft/fast-foundation").CSSDesignToken<Swatch>;
Expand Down Expand Up @@ -1600,7 +1615,7 @@ export const neutralStrokeDividerRecipe: DesignToken<ColorRecipe>;
export const neutralStrokeDividerRest: import("@microsoft/fast-foundation").CSSDesignToken<Swatch>;

// @public (undocumented)
export const neutralStrokeDividerRestDelta: import("@microsoft/fast-foundation").CSSDesignToken<number>;
export const neutralStrokeDividerRestDelta: DesignToken<number>;

// @public (undocumented)
export const neutralStrokeFocus: import("@microsoft/fast-foundation").CSSDesignToken<Swatch>;
Expand Down Expand Up @@ -1639,7 +1654,7 @@ export const neutralStrokeInputFilledRecipe: DesignToken<InteractiveColorRecipe>
export const neutralStrokeInputFilledRest: import("@microsoft/fast-foundation").CSSDesignToken<Swatch>;

// @public (undocumented)
export const neutralStrokeInputFilledRestDelta: import("@microsoft/fast-foundation").CSSDesignToken<number>;
export const neutralStrokeInputFilledRestDelta: DesignToken<number>;

// @public (undocumented)
export const neutralStrokeRecipe: DesignToken<InteractiveColorRecipe>;
Expand Down
10 changes: 3 additions & 7 deletions packages/web-components/fast-components/src/calendar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ import {
Calendar as FoundationCalendar,
} from "@microsoft/fast-foundation";
import { Swatch } from "../color/swatch";
import {
fillColor,
neutralFillLayerRecipe,
neutralFillLayerRest,
} from "../design-tokens";
import { fillColor, neutralFillLayerRecipe } from "../design-tokens";
import { CalendarStyles as styles } from "./calendar.styles";

/**
* The FAST listbox class
* The FAST Calendar class
* @public
*/
export class Calendar extends FoundationCalendar {
Expand All @@ -31,7 +27,7 @@ export class Calendar extends FoundationCalendar {
(target: HTMLElement): Swatch =>
neutralFillLayerRecipe
.getValueFor(target)
.evaluate(target, fillColor.getValueFor(parent))
.evaluate(target, fillColor.getValueFor(parent)).rest
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class Card extends FoundationCard {
(target: HTMLElement): Swatch =>
neutralFillLayerRecipe
.getValueFor(target)
.evaluate(target, fillColor.getValueFor(parent))
.evaluate(target, fillColor.getValueFor(parent)).rest
);
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Palette } from "../palette";
import { InteractiveSwatchSet } from "../recipe";
import { Swatch } from "../swatch";
import { directionByIsDark } from "../utilities/direction-by-is-dark";

/**
* @internal
*/
export function contrastAndDeltaSwatchSet(
palette: Palette,
reference: Swatch,
baseContrast: number,
restDelta: number,
hoverDelta: number,
activeDelta: number,
focusDelta: number,
direction?: -1 | 1 | null
): InteractiveSwatchSet {
if (direction === null || direction === void 0) {
direction = directionByIsDark(reference);
}
const baseIndex = palette.closestIndexOf(
palette.colorContrast(reference, baseContrast)
);

return {
rest: palette.get(baseIndex + direction * restDelta),
hover: palette.get(baseIndex + direction * hoverDelta),
active: palette.get(baseIndex + direction * activeDelta),
focus: palette.get(baseIndex + direction * focusDelta),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { directionByIsDark } from "../utilities/direction-by-is-dark";
/**
* @internal
*/
export function contrastSetRecipe(
export function contrastSwatchSet(
palette: Palette,
reference: Swatch,
baseContrast: number,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect } from "chai";
import { PaletteRGB } from "../palette";
import { SwatchRGB } from "../swatch";
import { accentBase, middleGrey } from "../utilities/color-constants";
import { contrastSwatch } from "./contrast-swatch";

describe("contrastSwatch", (): void => {
const neutralPalette = PaletteRGB.from(middleGrey);
const accentPalette = PaletteRGB.from(accentBase);

neutralPalette.swatches.concat(accentPalette.swatches).forEach((swatch): void => {
it(`${swatch} should resolve a color from the neutral palette`, (): void => {
expect(neutralPalette.swatches.indexOf(contrastSwatch(neutralPalette, swatch, 4.5) as SwatchRGB)).not.to.equal(
-1,
);
});
});

neutralPalette.swatches.concat(accentPalette.swatches).forEach((swatch): void => {
it(`${swatch} should always be at least 4.5 : 1 against the background`, (): void => {
expect(
swatch.contrast(contrastSwatch(neutralPalette, swatch, 4.5)),
// Because contrastSwatch follows the direction patterns of neutralForeground,
// a backgroundColor #777777 is impossible to hit 4.5 against.
).to.be.gte(swatch.toColorString().toUpperCase() === "#777777" ? 4.48 : 4.5);
expect(swatch.contrast(contrastSwatch(neutralPalette, swatch, 4.5))).to.be.lessThan(5);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Palette } from "../palette";
import { Swatch } from "../swatch";

/**
* Color algorithm using contrast from the reference color.
*
* @param palette - The palette to operate on
* @param reference - The reference color
* @param contrast - The desired minimum contrast
*
* @internal
*/
export function contrastSwatch(
palette: Palette,
reference: Swatch,
contrast: number
): Swatch {
return palette.colorContrast(reference, contrast);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Palette } from "../palette";
import { InteractiveSwatchSet } from "../recipe";
import { Swatch } from "../swatch";
import { directionByIsDark } from "../utilities/direction-by-is-dark";

/**
* Color algorithm using deltas from the reference color for states.
*
* @param palette The palette to operate on
* @param reference The reference color to calculate a color for
* @param restDelta The rest state offset from reference
* @param hoverDelta The hover state offset from reference
* @param activeDelta The active state offset from reference
* @param focusDelta The focus state offset from reference
* @param direction The direction the deltas move on the ramp, default goes darker for light references and lighter for dark references
*
* @internal
*/
export function deltaSwatchSet(
palette: Palette,
reference: Swatch,
restDelta: number,
hoverDelta: number,
activeDelta: number,
focusDelta: number,
direction?: -1 | 1 | null
): InteractiveSwatchSet {
const referenceIndex = palette.closestIndexOf(reference);
if (direction === null || direction === void 0) {
direction = directionByIsDark(reference);
}

return {
rest: palette.get(referenceIndex + direction * restDelta),
hover: palette.get(referenceIndex + direction * hoverDelta),
active: palette.get(referenceIndex + direction * activeDelta),
focus: palette.get(referenceIndex + direction * focusDelta),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ import { Palette } from "../palette";
import { directionByIsDark } from "../utilities/direction-by-is-dark";

/**
* The neutralStrokeDivider color recipe
* Color algorithm using a delta from the reference color.
*
* @param palette - The palette to operate on
* @param reference - The reference color
* @param delta - The offset from the reference
*
* @internal
*/
export function neutralStrokeDivider(
palette: Palette,
reference: Swatch,
delta: number
): Swatch {
export function deltaSwatch(palette: Palette, reference: Swatch, delta: number): Swatch {
return palette.get(
palette.closestIndexOf(reference) + directionByIsDark(reference) * delta
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from "chai";
import { SwatchRGB } from "../swatch";
import { black } from "../utilities/color-constants";
import { foregroundOnAccent } from './foreground-on-accent';
import { foregroundOnAccent } from "./foreground-on-accent";

describe("Cut text", (): void => {
it("should return black when background does not meet contrast ratio", (): void => {
Expand All @@ -16,4 +16,4 @@ describe("Cut text", (): void => {
expect(large.g).to.equal(black.g);
expect(large.b).to.equal(black.b);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ import { black, white } from "../utilities/color-constants";
/**
* @internal
*/
export function foregroundOnAccent(reference: Swatch, contrastTarget: number): Swatch {
export function foregroundOnAccent(reference: Swatch, contrastTarget: number) {
return reference.contrast(white) >= contrastTarget ? white : black;
}

export function foregroundOnAccentSet(
restFill: Swatch,
hoverFill: Swatch,
activeFill: Swatch,
focusFill: Swatch,
contrastTarget: number
) {
const defaultRule = fill => (fill.contrast(white) >= contrastTarget ? white : black);
const restForeground = defaultRule(restFill);
const hoverForeground = defaultRule(hoverFill);
// Active doe not have contrast requirements, so if rest and hover use the same color, use that for active even if it would not have passed the contrast check.
const activeForeground =
restForeground.relativeLuminance === hoverForeground.relativeLuminance
? restForeground
: defaultRule(activeFill);
const focusForeground = defaultRule(focusFill);
return {
rest: restForeground,
hover: hoverForeground,
active: activeForeground,
focus: focusForeground,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@ import { parseColorHexRGB } from "@microsoft/fast-colors";
import { PaletteRGB } from "../palette";
import { SwatchRGB } from "../swatch";
import { accentBase, black, middleGrey, white } from "../utilities/color-constants";
import { accentForeground } from "./accent-foreground";
import { idealColorDeltaSwatchSet } from "./ideal-color-delta-swatch-set";

describe("accentForeground", (): void => {
const neutralPalette = PaletteRGB.create(middleGrey)
const accentPalette = PaletteRGB.create(accentBase);
describe("idealColorDeltaSwatchSet", (): void => {
const neutralPalette = PaletteRGB.from(middleGrey)
const accentPalette = PaletteRGB.from(accentBase);

it("should increase contrast on hover state and decrease contrast on active state in either mode", (): void => {
const lightModeColors = accentForeground(
const lightModeColors = idealColorDeltaSwatchSet(
accentPalette,
accentPalette.source,
white,
4.5,
0,
6,
-4,
0
);
const darkModeColors = accentForeground(
const darkModeColors = idealColorDeltaSwatchSet(
accentPalette,
accentPalette.source,
black,
4.5,
0,
Expand Down Expand Up @@ -56,17 +58,19 @@ describe("accentForeground", (): void => {
const accentPalette = PaletteRGB.create(accent);

neutralPalette.swatches.forEach((swatch): void => {
const smallColors = accentForeground(
const smallColors = idealColorDeltaSwatchSet(
accentPalette,
accentPalette.source,
swatch,
4.5,
0,
6,
-4,
0
);
const largeColors = accentForeground(
const largeColors = idealColorDeltaSwatchSet(
accentPalette,
accentPalette.source,
swatch,
3,
0,
Expand All @@ -92,4 +96,4 @@ describe("accentForeground", (): void => {
}
);
});
});
});
Loading