Skip to content

Commit

Permalink
FIDEFE-4631 - Output tokens in pre-defined order (#12)
Browse files Browse the repository at this point in the history
Sort tokens when exporting in CSS and add section headers to match existing tokens-*.css files in mozilla-central
  • Loading branch information
mstriemer authored and TGiles committed Mar 8, 2024
1 parent 62142b3 commit e8ee798
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@

:root,
:host(.anonymous-content-host) {
--text-color-deemphasized: light-dark(color-mix(in srgb, currentColor 69%, transparent), color-mix(in srgb, currentColor 75%, transparent));
--text-color: light-dark(var(--color-gray-100), var(--color-gray-05));
/* Application tokens */
/** Border **/
--border-interactive-color: light-dark(var(--color-gray-60), var(--color-gray-50));

/** Text **/
--text-color: light-dark(var(--color-gray-100), var(--color-gray-05));
--text-color-deemphasized: light-dark(color-mix(in srgb, currentColor 69%, transparent), color-mix(in srgb, currentColor 75%, transparent));
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

:root,
:host(.anonymous-content-host) {
--text-color: currentColor;
/* Application tokens */
/** Border **/
--border-interactive-color: color-mix(in srgb, currentColor 15%, var(--color-gray-60));

/** Text **/
--text-color: currentColor;
}
93 changes: 54 additions & 39 deletions toolkit/themes/shared/design-system/build/css/tokens-shared.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,60 +11,75 @@

:root,
:host(.anonymous-content-host) {
--text-color-deemphasized: color-mix(in srgb, currentColor 60%, transparent);
/* Base tokens */
/** Color **/
--color-black-a10: rgba(0, 0, 0, 0.1);
--color-blue-05: #deeafc;
--color-blue-30: #73a7f3;
--color-blue-50: #0060df;
--color-blue-60: #0250bb;
--color-blue-70: #054096;
--color-blue-80: #003070;
--color-cyan-20: #aaf2ff;
--color-cyan-30: #aaf2ff;
--color-cyan-50: #00ddff;
--color-gray-05: #fbfbfe;
--color-gray-50: #bfbfc9;
--color-gray-60: #8f8f9d;
--color-gray-70: #5b5b66;
--color-gray-80: #23222b;
--color-gray-90: #1c1b22;
--color-gray-100: #15141a;
--color-green-05: #d8eedc;
--color-green-30: #4dbc87;
--color-green-50: #017a40;
--color-green-80: #004725;
--color-red-05: #ffe8e8;
--color-red-30: #f37f98;
--color-red-50: #d7264c;
--color-red-80: #690f22;
--color-white: #ffffff;
--color-yellow-05: #ffebcd;
--color-yellow-80: #5a3100;
--color-yellow-50: #cd411e;
--color-yellow-30: #e49c49;
--color-red-05: #ffe8e8;
--color-red-80: #690f22;
--color-red-50: #d7264c;
--color-red-30: #f37f98;
--color-green-05: #d8eedc;
--color-green-80: #004725;
--color-green-50: #017a40;
--color-green-30: #4dbc87;
--color-gray-05: #fbfbfe;
--color-gray-100: #15141a;
--color-gray-90: #1c1b22;
--color-gray-80: #23222b;
--color-gray-70: #5b5b66;
--color-gray-60: #8f8f9d;
--color-gray-50: #bfbfc9;
--color-cyan-50: #00ddff;
--color-cyan-30: #aaf2ff;
--color-cyan-20: #aaf2ff;
--color-blue-05: #deeafc;
--color-blue-80: #003070;
--color-blue-70: #054096;
--color-blue-60: #0250bb;
--color-blue-50: #0060df;
--color-blue-30: #73a7f3;
--color-black-a10: rgba(0, 0, 0, 0.1);
--border-width: 1px;
--color-yellow-50: #cd411e;
--color-yellow-80: #5a3100;

/* Application tokens */
/** Border **/
--border-radius-circle: 9999px;
--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));
--border-width: 1px;

/** Color **/
--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));

/** Text **/
--text-color-deemphasized: color-mix(in srgb, currentColor 60%, transparent);

@media (prefers-contrast) {
--text-color-deemphasized: inherit;
--border-interactive-color-disabled: GrayText;
/* Application tokens */
/** Border **/
--border-color: var(--text-color);
--border-interactive-color: AccentColor;
--border-interactive-color-active: AccentColor;
--border-interactive-color-disabled: GrayText;
--border-interactive-color-hover: SelectedItem;

/** Text **/
--text-color: CanvasText;
--border-interactive-color: AccentColor;
--border-color: var(--text-color);
--text-color-deemphasized: inherit;
}

@media (forced-colors) {
--border-interactive-color-disabled: GrayText;
/* Application tokens */
/** Border **/
--border-interactive-color: ButtonText;
--border-interactive-color-active: ButtonText;
--border-interactive-color-disabled: GrayText;
--border-interactive-color-hover: ButtonText;
--border-interactive-color: ButtonText;
}
}
144 changes: 124 additions & 20 deletions toolkit/themes/shared/design-system/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,25 @@
/* eslint-env node */

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

const TOKEN_SECTIONS = {
"Base tokens/Color": [/^color-[^-]*$/, /^color-.*-a?\d+$/],
"Application tokens/Border": "border",
"Application tokens/Box": "box",
"Application tokens/Color": "color",
"Application tokens/Font weight": "font-weight",
"Application tokens/Focus outline": "focus-outline",
"Application tokens/Icon": "icon",
"Application tokens/Input/Button": "button",
"Application tokens/Input/Checkbox": "checkbox",
"Application tokens/Input/Text": "input-text",
"Application tokens/Link": "link",
"Application tokens/Text": "text",
"Application tokens/Size": "size",
"Application tokens/Space": "space",
Unspecified: "",
};

/**
* Adds the Mozilla Public License header in one comment and
Expand Down Expand Up @@ -48,6 +66,10 @@ const MEDIA_QUERY_PROPERTY_MAP = {

const BASE_SELECTOR = ":root,\n" + ":host(.anonymous-content-host) {\n";

function formatBaseTokenNames(str) {
return str.replaceAll(/(?<tokenName>\w+)-base(?=\b)/g, "$<tokenName>");
}

/**
* Creates a surface-specific formatter. The formatter is used to build
* our different CSS files, including "prefers-contrast" and "forced-colors"
Expand All @@ -60,25 +82,25 @@ const BASE_SELECTOR = ":root,\n" + ":host(.anonymous-content-host) {\n";
* @returns {Function} - Formatter function that returns a CSS string.
*/
const createDesktopFormat = surface => args => {
return (
return formatBaseTokenNames(
customFileHeader(surface) +
BASE_SELECTOR +
formatTokens({
surface,
args,
}) +
formatTokens({
mediaQuery: "prefers-contrast",
surface,
args,
}) +
formatTokens({
mediaQuery: "forced-colors",
surface,
args,
}) +
"}\n"
).replaceAll(/(?<tokenName>\w+)-base(?=\b)/g, "$<tokenName>");
BASE_SELECTOR +
formatTokens({
surface,
args,
}) +
formatTokens({
mediaQuery: "prefers-contrast",
surface,
args,
}) +
formatTokens({
mediaQuery: "forced-colors",
surface,
args,
}) +
"}\n"
);
};

/**
Expand Down Expand Up @@ -115,7 +137,7 @@ function formatTokens({ mediaQuery, surface, args }) {

dictionary.allTokens = dictionary.allProperties = tokens;

let formattedVars = formattedVariables({
let formattedVars = formatVariables({
format: "css",
dictionary,
outputReferences: args.options.outputReferences,
Expand Down Expand Up @@ -222,6 +244,88 @@ const createLightDarkTransform = surface => {
return name;
};

/**
* Format the tokens dictionary to a string. This mostly defers to
* StyleDictionary.createPropertyFormatter but first it sorts the tokens based
* on the groupings in TOKEN_SECTIONS and adds comment headers to CSS output.
*
* @param {object} options
* Options for tokens to format.
* @param {string} options.format
* The format to output. Supported: "css"
* @param {object} options.dictionary
* The tokens dictionary.
* @param {string} options.outputReferences
* Whether to output variable references.
* @param {object} options.formatting
* The formatting settings to be passed to createPropertyFormatter.
* @returns {string} The formatted tokens.
*/
function formatVariables({ format, dictionary, outputReferences, formatting }) {
let lastSection = [];
let propertyFormatter = createPropertyFormatter({
outputReferences,
dictionary,
format,
formatting,
});

let outputParts = [];
let remainingTokens = [...dictionary.allTokens];
let isFirst = true;

for (let [label, selector] of Object.entries(TOKEN_SECTIONS)) {
let sectionMatchers = Array.isArray(selector) ? selector : [selector];
let sectionParts = [];

remainingTokens = remainingTokens.filter(token => {
if (
sectionMatchers.some(m =>
m.test ? m.test(token.name) : token.name.startsWith(m)
)
) {
sectionParts.push(token);
return false;
}
return true;
});

if (sectionParts.length) {
sectionParts.sort((a, b) =>
formatBaseTokenNames(a.name).localeCompare(
formatBaseTokenNames(b.name),
undefined,
{ numeric: true }
)
);

let headingParts = [];
if (!isFirst) {
headingParts.push("");
}
isFirst = false;

let sectionLevel = "*";
let labelParts = label.split("/");
for (let i = 0; i < labelParts.length; i++) {
if (labelParts[i] != lastSection[i]) {
headingParts.push(
`${formatting.indentation}/${sectionLevel} ${labelParts[i]} ${sectionLevel}/`
);
}
sectionLevel += "*";
}
lastSection = labelParts;

outputParts = outputParts.concat(
headingParts.concat(sectionParts.map(propertyFormatter))
);
}
}

return outputParts.join("\n");
}

module.exports = {
source: ["design-tokens.json"],
format: {
Expand Down

0 comments on commit e8ee798

Please sign in to comment.