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

FIDEFE-4626 - Add HCM media queries to built CSS #3

Merged
merged 3 commits into from
Jan 29, 2024
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
30 changes: 26 additions & 4 deletions toolkit/themes/shared/design-system/build/css/tokens-shared.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
:root {
--text-deemphasized: color-mix(in srgb, currentColor 60%, transparent);
--text-platform: currentColor;
--text-color: CanvasText;
--text-color-deemphasized: color-mix(in srgb, currentColor 60%, transparent);
--color-white: #ffffff;
--color-yellow-05: #ffebcd;
--color-yellow-80: #5a3100;
Expand Down Expand Up @@ -32,9 +30,33 @@
--color-blue-50: #0060df;
--color-blue-30: #73a7f3;
--color-black-a10: rgba(0, 0, 0, 0.1);
--text-brand: light-dark(var(--color-gray-100), var(--color-gray-05));
--border-width: 1px;
--border-radius-medium: 8px;
--border-radius-small: 4px;
--border-radius-circle: 9999px;
--color-background-warning: light-dark(var(--color-yellow-05), var(--color-blue-80));
--color-background-success: light-dark(var(--color-green-05), var(--color-yellow-80));
--color-background-information: light-dark(var(--color-blue-05), var(--color-blue-80));
--color-background-critical: light-dark(var(--color-red-05), var(--color-red-80));
}

@media (prefers-contrast) {
:root {
--text-color-deemphasized: inherit;
--text-color-default: CanvasText;
--border-interactive-color-disabled: GrayText;
--border-interactive-color-active: AccentColor;
--border-interactive-color-hover: SelectedItem;
--border-interactive-color-default: AccentColor;
--border-color: var(--text-color-default);
}
}

@media (forced-colors) {
:root {
--border-interactive-color-disabled: GrayText;
--border-interactive-color-active: ButtonText;
--border-interactive-color-hover: ButtonText;
--border-interactive-color-default: ButtonText;
}
}
103 changes: 100 additions & 3 deletions toolkit/themes/shared/design-system/config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,105 @@

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env node */

const StyleDictionary = require("style-dictionary");
const { formattedVariables } = StyleDictionary.formatHelpers;

const MEDIA_QUERY_PROPERTY_MAP = {
"forced-colors": "forcedColors",
"prefers-contrast": "prefersContrast",
};

/**
* Formats built CSS to include "prefers-contrast" and "forced-colors" media
* queries.
*
* @param {object} args
* Formatter arguments provided by style-dictionary. See more at
* https://amzn.github.io/style-dictionary/#/formats?id=formatter
* @returns {string}
* Formatted CSS including media queries.
*/
function hcmFormatter(args) {
return (
formatTokens({ args }) +
formatTokens({ mediaQuery: "prefers-contrast", args }) +
formatTokens({ mediaQuery: "forced-colors", args })
);
}

/**
* Formats a subset of tokens into CSS. Wraps token CSS in a media query when
* applicable.
*
* @param {object} tokenArgs
* @param {string} [tokenArgs.mediaQuery]
* Media query formatted CSS should be wrapped in. This is used
* to determine what property we are parsing from the token values.
* @param {object} tokenArgs.args
* Formatter arguments provided by style-dictionary. See more at
* https://amzn.github.io/style-dictionary/#/formats?id=formatter
* @returns {string} Tokens formatted into a CSS string.
*/
function formatTokens({ mediaQuery, args }) {
let prop = MEDIA_QUERY_PROPERTY_MAP[mediaQuery] ?? "value";
let dictionary = Object.assign({}, args.dictionary);
let tokens = [];

dictionary.allTokens.forEach(token => {
let value = token[prop] || token.original.value[prop]
if (value && typeof value !== "object") {
let formattedToken = transformTokenValue(token, prop, dictionary);
tokens.push(formattedToken);
}
});

dictionary.allTokens = dictionary.allProperties = tokens;

let formattedVars = formattedVariables({
format: "css",
hannajones marked this conversation as resolved.
Show resolved Hide resolved
dictionary,
outputReferences: args.options.outputReferences,
formatting: {
indentation: mediaQuery ? " " : " ",
},
});

// Weird spacing below is unfortunately necessary formatting the built CSS.
if (mediaQuery) {
return `
@media (${mediaQuery}) {
:root {
${formattedVars}
}
}
`;
}

return `:root {\n${formattedVars}\n}\n`;
}

/**
* Takes a token object and changes "value" based on the supplied prop. Also
* preserves variable references when necessary.
*
* @param {object} token - Token object parsed from JSON by style-dictionary.
* @param {string} prop
* Name of the property used to get the token's new value.
* @param {object} dictionary
* Object of transformed tokens and helper fns provided by style-dictionary.
* @returns {object} Token object with an updated value.
*/
function transformTokenValue(token, prop, dictionary) {
let originalVal = token.original.value[prop];
if (dictionary.usesReference(originalVal)) {
let refs = dictionary.getReferences(originalVal);
return { ...token, value: `var(--${refs[0].name})` };
hannajones marked this conversation as resolved.
Show resolved Hide resolved
}
return { ...token, value: token[prop] || originalVal};
}

module.exports = {
source: ["design-tokens.json"],
Expand All @@ -15,7 +109,7 @@ module.exports = {
transitive: true,
name: "defaultTransform",
matcher: token => token.original.value.default,
transformer: token => token.original.value.default
transformer: token => token.original.value.default,
},
lightDarkTransform: {
type: "value",
Expand All @@ -27,6 +121,9 @@ module.exports = {
},
},
},
format: {
"css/variables/hcm": hcmFormatter,
},
platforms: {
css: {
// The ordering of transforms matter, so if we encountered
Expand All @@ -42,7 +139,7 @@ module.exports = {
files: [
{
destination: "tokens-shared.css",
format: "css/variables",
format: "css/variables/hcm",
options: {
outputReferences: true,
showFileHeader: false,
Expand Down
74 changes: 59 additions & 15 deletions toolkit/themes/shared/design-system/design-tokens.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
{
"border": {
"color": {
"value": {
"prefersContrast": "{text.color.default}"
}
},
"interactive": {
"color": {
"default": {
"value": {
"prefersContrast": "AccentColor",
"forcedColors": "ButtonText"
}
},
"hover": {
"value": {
"forcedColors": "ButtonText",
"prefersContrast": "SelectedItem"
}
},
"active": {
"value": {
"prefersContrast": "AccentColor",
"forcedColors": "ButtonText"
}
},
"disabled": {
"value": {
"prefersContrast": "GrayText",
"forcedColors": "GrayText"
}
}
}
},
"radius": {
"circle": {
"value": "9999px"
},
"small": {
"value": "4px"
},
"medium": {
"value": "8px"
}
},
"width": {
"value": "1px"
}
},
"color": {
"black": {
"a10": {
Expand Down Expand Up @@ -133,21 +182,16 @@
},
"text": {
"color": {
"value": "CanvasText"
},
"brand": {
"value": {
"light": "{color.gray.100}",
"dark": "{color.gray.05}"
}
},
"platform": {
"value": "currentColor"
},
"deemphasized": {
"value": {
"default": "color-mix(in srgb, currentColor 60%, transparent)",
"prefersContrast": "inherit"
"default": {
"value": {
"prefersContrast": "CanvasText"
}
},
"deemphasized": {
"value": {
"default": "color-mix(in srgb, currentColor 60%, transparent)",
"prefersContrast": "inherit"
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion toolkit/themes/shared/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"jest/globals": true
},
"scripts": {
"test": "jest --testPathPattern=tests",
"test": "jest --testPathPattern=tests --silent",
"build": "style-dictionary build"
},
"author": "",
Expand Down
84 changes: 64 additions & 20 deletions toolkit/themes/shared/design-system/tests/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ const config = require("../config");

const TEST_BUILD_PATH = "tests/build/css/";

const EXPECTED_CSS_RULES = {
"--color-background-critical": "light-dark(var(--color-red-05), var(--color-red-80))",
"--color-background-information": "light-dark(var(--color-blue-05), var(--color-blue-80))",
"--color-background-success": "light-dark(var(--color-green-05), var(--color-yellow-80))",
"--color-background-warning": "light-dark(var(--color-yellow-05), var(--color-blue-80))",
const BASE_CSS_RULES = {
"--color-background-critical":
"light-dark(var(--color-red-05), var(--color-red-80))",
"--color-background-information":
"light-dark(var(--color-blue-05), var(--color-blue-80))",
"--color-background-success":
"light-dark(var(--color-green-05), var(--color-yellow-80))",
"--color-background-warning":
"light-dark(var(--color-yellow-05), var(--color-blue-80))",
"--color-black-a10": "rgba(0, 0, 0, 0.1)",
"--color-blue-05": "#deeafc",
"--color-blue-30": "#73a7f3",
Expand Down Expand Up @@ -42,10 +46,35 @@ const EXPECTED_CSS_RULES = {
"--color-yellow-30": "#e49c49",
"--color-yellow-50": "#cd411e",
"--color-yellow-80": "#5a3100",
"--text-brand": "light-dark(var(--color-gray-100), var(--color-gray-05))",
"--text-color": "CanvasText",
"--text-deemphasized": "color-mix(in srgb, currentColor 60%, transparent)",
"--text-platform": "currentColor",
"--text-color-deemphasized":
"color-mix(in srgb, currentColor 60%, transparent)",
"--border-radius-circle": "9999px",
"--border-radius-small": "4px",
"--border-radius-medium": "8px",
"--border-width": "1px",
};

const PREFERS_CONTRAST_CSS_RULES = {
"--text-color-deemphasized": "inherit",
"--text-color-default": "CanvasText",
"--border-interactive-color-disabled": "GrayText",
"--border-interactive-color-active": "AccentColor",
"--border-interactive-color-hover": "SelectedItem",
"--border-interactive-color-default": "AccentColor",
"--border-color": "var(--text-color-default)",
};

const FORCED_COLORS_CSS_RULES = {
"--border-interactive-color-disabled": "GrayText",
"--border-interactive-color-active": "ButtonText",
"--border-interactive-color-hover": "ButtonText",
"--border-interactive-color-default": "ButtonText",
};

const FIXTURE_BY_QUERY = {
base: BASE_CSS_RULES,
"prefers-contrast": PREFERS_CONTRAST_CSS_RULES,
"forced-colors": FORCED_COLORS_CSS_RULES,
};

// Use our real config, just modify some values for the test.
Expand All @@ -56,21 +85,36 @@ testConfig.platforms.css.buildPath = TEST_BUILD_PATH;
describe("generated CSS", () => {
StyleDictionary.extend(testConfig).buildAllPlatforms();

describe("css/variables", () => {
describe("css/variables/hcm format", () => {
const output = fs.readFileSync(`${TEST_BUILD_PATH}tokens-shared.css`, {
encoding: "UTF-8",
});

it("should produce the expected CSS", () => {
let formattedCSS = output.split("\n").reduce((rulesObj, rule) => {
let [key, val] = rule.split(":");
if (key && val) {
return { ...rulesObj, [key.trim()]: val.trim().replace(";", "") };
}
return rulesObj;
}, {});

expect(formattedCSS).toMatchObject(EXPECTED_CSS_RULES);
let rulesByMediaQuery = output.split("@media");

it("should contain three blocks of CSS, including media queries", () => {
expect(rulesByMediaQuery.length).toBe(3);
expect(rulesByMediaQuery[1]).toEqual(
expect.stringContaining("prefers-contrast")
);
expect(rulesByMediaQuery[2]).toEqual(
expect.stringContaining("forced-colors")
);
});

rulesByMediaQuery.forEach(ruleSet => {
let queryName = ruleSet.trim().match(/(?<=\().+?(?=\) \{)/) || "base";
it(`should produce the expected ${queryName} CSS rules`, () => {
let formattedCSS = ruleSet.split("\n").reduce((rulesObj, rule) => {
let [key, val] = rule.split(":");
if (key.trim() && val) {
return { ...rulesObj, [key.trim()]: val.trim().replace(";", "") };
}
return rulesObj;
}, {});

expect(formattedCSS).toStrictEqual(FIXTURE_BY_QUERY[queryName]);
});
});
});
});
Expand Down