Build tokens for each theme (#2590)
This PR adds the ability to export tokens for each theme. You can export tokens by: 1. `cd styles` 2. `npm run build-tokens` 3. Tokens will be output in the target folder (`styles/target`) The tokens match the ColorScheme object. In the future we may also export tokens for our styleTrees. Release Notes: - N/A (No public facing changes) --- TODO: - [x] Generate Token Studio theme index file - [x] ColorScheme - [x] name: - [x] isLight - [x] lowest - [x] middle - [x] highest - [x] popoverShadow - [x] modalShadow - [x] players - [x] syntax
This commit is contained in:
commit
85b049f250
4 changed files with 197 additions and 22 deletions
|
@ -1,11 +1,13 @@
|
|||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import { ColorScheme, createColorScheme } from "./common"
|
||||
import { themes } from "./themes"
|
||||
import { slugify } from "./utils/slugify"
|
||||
import { colorSchemeTokens } from "./theme/tokens/colorScheme"
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { ColorScheme, createColorScheme } from "./common";
|
||||
import { themes } from "./themes";
|
||||
import { slugify } from "./utils/slugify";
|
||||
import { colorSchemeTokens } from "./theme/tokens/colorScheme";
|
||||
|
||||
const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens")
|
||||
const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens");
|
||||
const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json");
|
||||
const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json");
|
||||
|
||||
function clearTokens(tokensDirectory: string) {
|
||||
if (!fs.existsSync(tokensDirectory)) {
|
||||
|
@ -19,21 +21,65 @@ function clearTokens(tokensDirectory: string) {
|
|||
}
|
||||
}
|
||||
|
||||
type TokenSet = {
|
||||
id: string;
|
||||
name: string;
|
||||
selectedTokenSets: { [key: string]: "enabled" };
|
||||
};
|
||||
|
||||
function buildTokenSetOrder(colorSchemes: ColorScheme[]): { tokenSetOrder: string[] } {
|
||||
const tokenSetOrder: string[] = colorSchemes.map(
|
||||
(scheme) => scheme.name.toLowerCase().replace(/\s+/g, "_")
|
||||
);
|
||||
return { tokenSetOrder };
|
||||
}
|
||||
|
||||
function buildThemesIndex(colorSchemes: ColorScheme[]): TokenSet[] {
|
||||
const themesIndex: TokenSet[] = colorSchemes.map((scheme, index) => {
|
||||
const id = `${scheme.isLight ? "light" : "dark"}_${scheme.name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "_")}_${index}`;
|
||||
const selectedTokenSets: { [key: string]: "enabled" } = {};
|
||||
const tokenSet = scheme.name.toLowerCase().replace(/\s+/g, "_");
|
||||
selectedTokenSets[tokenSet] = "enabled";
|
||||
|
||||
return {
|
||||
id,
|
||||
name: `${scheme.name} - ${scheme.isLight ? "Light" : "Dark"}`,
|
||||
selectedTokenSets,
|
||||
};
|
||||
});
|
||||
|
||||
return themesIndex;
|
||||
}
|
||||
|
||||
function writeTokens(colorSchemes: ColorScheme[], tokensDirectory: string) {
|
||||
clearTokens(tokensDirectory)
|
||||
clearTokens(tokensDirectory);
|
||||
|
||||
for (const colorScheme of colorSchemes) {
|
||||
const fileName = slugify(colorScheme.name)
|
||||
const tokens = colorSchemeTokens(colorScheme)
|
||||
const tokensJSON = JSON.stringify(tokens, null, 2)
|
||||
const outPath = path.join(tokensDirectory, `${fileName}.json`)
|
||||
fs.writeFileSync(outPath, tokensJSON)
|
||||
console.log(`- ${outPath} created`)
|
||||
const fileName = slugify(colorScheme.name) + ".json";
|
||||
const tokens = colorSchemeTokens(colorScheme);
|
||||
const tokensJSON = JSON.stringify(tokens, null, 2);
|
||||
const outPath = path.join(tokensDirectory, fileName);
|
||||
fs.writeFileSync(outPath, tokensJSON, { mode: 0o644 });
|
||||
console.log(`- ${outPath} created`);
|
||||
}
|
||||
|
||||
const themeIndexData = buildThemesIndex(colorSchemes);
|
||||
|
||||
const themesJSON = JSON.stringify(themeIndexData, null, 2);
|
||||
fs.writeFileSync(TOKENS_FILE, themesJSON, { mode: 0o644 });
|
||||
console.log(`- ${TOKENS_FILE} created`);
|
||||
|
||||
const tokenSetOrderData = buildTokenSetOrder(colorSchemes);
|
||||
|
||||
const metadataJSON = JSON.stringify(tokenSetOrderData, null, 2);
|
||||
fs.writeFileSync(METADATA_FILE, metadataJSON, { mode: 0o644 });
|
||||
console.log(`- ${METADATA_FILE} created`);
|
||||
}
|
||||
|
||||
const colorSchemes: ColorScheme[] = themes.map((theme) =>
|
||||
createColorScheme(theme)
|
||||
)
|
||||
);
|
||||
|
||||
writeTokens(colorSchemes, TOKENS_DIRECTORY)
|
||||
writeTokens(colorSchemes, TOKENS_DIRECTORY);
|
||||
|
|
|
@ -1,12 +1,81 @@
|
|||
import { ColorScheme } from "../colorScheme"
|
||||
import { PlayerTokens, players } from "./players"
|
||||
import { SingleBoxShadowToken, SingleColorToken, SingleOtherToken, TokenTypes } from "@tokens-studio/types"
|
||||
import { ColorScheme, Shadow, SyntaxHighlightStyle, ThemeSyntax } from "../colorScheme"
|
||||
import { LayerToken, layerToken } from "./layer"
|
||||
import { PlayersToken, playersToken } from "./players"
|
||||
import { colorToken } from "./token"
|
||||
import { Syntax } from "../syntax";
|
||||
import editor from "../../styleTree/editor"
|
||||
|
||||
interface ColorSchemeTokens {
|
||||
players: PlayerTokens
|
||||
name: SingleOtherToken
|
||||
appearance: SingleOtherToken
|
||||
lowest: LayerToken
|
||||
middle: LayerToken
|
||||
highest: LayerToken
|
||||
players: PlayersToken
|
||||
popoverShadow: SingleBoxShadowToken
|
||||
modalShadow: SingleBoxShadowToken
|
||||
syntax?: Partial<ThemeSyntaxColorTokens>
|
||||
}
|
||||
|
||||
const createShadowToken = (shadow: Shadow, tokenName: string): SingleBoxShadowToken => {
|
||||
return {
|
||||
name: tokenName,
|
||||
type: TokenTypes.BOX_SHADOW,
|
||||
value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`
|
||||
};
|
||||
};
|
||||
|
||||
const popoverShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => {
|
||||
const shadow = colorScheme.popoverShadow;
|
||||
return createShadowToken(shadow, "popoverShadow");
|
||||
};
|
||||
|
||||
const modalShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => {
|
||||
const shadow = colorScheme.modalShadow;
|
||||
return createShadowToken(shadow, "modalShadow");
|
||||
};
|
||||
|
||||
type ThemeSyntaxColorTokens = Record<keyof ThemeSyntax, SingleColorToken>
|
||||
|
||||
function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens {
|
||||
const styleKeys = Object.keys(syntax) as (keyof Syntax)[]
|
||||
|
||||
return styleKeys.reduce((acc, styleKey) => {
|
||||
// Hack: The type of a style could be "Function"
|
||||
// This can happen because we have a "constructor" property on the syntax object
|
||||
// and a "constructor" property on the prototype of the syntax object
|
||||
// To work around this just assert that the type of the style is not a function
|
||||
if (!syntax[styleKey] || typeof syntax[styleKey] === 'function') return acc;
|
||||
const { color } = syntax[styleKey] as Required<SyntaxHighlightStyle>;
|
||||
return { ...acc, [styleKey]: colorToken(styleKey, color) };
|
||||
}, {} as ThemeSyntaxColorTokens);
|
||||
}
|
||||
|
||||
const syntaxTokens = (colorScheme: ColorScheme): ColorSchemeTokens['syntax'] => {
|
||||
const syntax = editor(colorScheme).syntax
|
||||
|
||||
return syntaxHighlightStyleColorTokens(syntax)
|
||||
}
|
||||
|
||||
export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens {
|
||||
return {
|
||||
players: players(colorScheme),
|
||||
name: {
|
||||
name: "themeName",
|
||||
value: colorScheme.name,
|
||||
type: TokenTypes.OTHER,
|
||||
},
|
||||
appearance: {
|
||||
name: "themeAppearance",
|
||||
value: colorScheme.isLight ? "light" : "dark",
|
||||
type: TokenTypes.OTHER,
|
||||
},
|
||||
lowest: layerToken(colorScheme.lowest, "lowest"),
|
||||
middle: layerToken(colorScheme.middle, "middle"),
|
||||
highest: layerToken(colorScheme.highest, "highest"),
|
||||
popoverShadow: popoverShadowToken(colorScheme),
|
||||
modalShadow: modalShadowToken(colorScheme),
|
||||
players: playersToken(colorScheme),
|
||||
syntax: syntaxTokens(colorScheme),
|
||||
}
|
||||
}
|
||||
|
|
60
styles/src/theme/tokens/layer.ts
Normal file
60
styles/src/theme/tokens/layer.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { SingleColorToken } from "@tokens-studio/types";
|
||||
import { Layer, Style, StyleSet } from "../colorScheme";
|
||||
import { colorToken } from "./token";
|
||||
|
||||
interface StyleToken {
|
||||
background: SingleColorToken,
|
||||
border: SingleColorToken,
|
||||
foreground: SingleColorToken,
|
||||
}
|
||||
|
||||
interface StyleSetToken {
|
||||
default: StyleToken
|
||||
active: StyleToken
|
||||
disabled: StyleToken
|
||||
hovered: StyleToken
|
||||
pressed: StyleToken
|
||||
inverted: StyleToken
|
||||
}
|
||||
|
||||
export interface LayerToken {
|
||||
base: StyleSetToken
|
||||
variant: StyleSetToken
|
||||
on: StyleSetToken
|
||||
accent: StyleSetToken
|
||||
positive: StyleSetToken
|
||||
warning: StyleSetToken
|
||||
negative: StyleSetToken
|
||||
}
|
||||
|
||||
export const styleToken = (style: Style, name: string): StyleToken => {
|
||||
const token = {
|
||||
background: colorToken(`${name}Background`, style.background),
|
||||
border: colorToken(`${name}Border`, style.border),
|
||||
foreground: colorToken(`${name}Foreground`, style.foreground),
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
export const styleSetToken = (styleSet: StyleSet, name: string): StyleSetToken => {
|
||||
const token: StyleSetToken = {} as StyleSetToken;
|
||||
|
||||
for (const style in styleSet) {
|
||||
const s = style as keyof StyleSet;
|
||||
token[s] = styleToken(styleSet[s], `${name}${style}`);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export const layerToken = (layer: Layer, name: string): LayerToken => {
|
||||
const token: LayerToken = {} as LayerToken;
|
||||
|
||||
for (const styleSet in layer) {
|
||||
const s = styleSet as keyof Layer;
|
||||
token[s] = styleSetToken(layer[s], `${name}${styleSet}`);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
|
@ -4,7 +4,7 @@ import { colorToken } from "./token"
|
|||
|
||||
export type PlayerToken = Record<"selection" | "cursor", SingleColorToken>
|
||||
|
||||
export type PlayerTokens = Record<keyof Players, PlayerToken>
|
||||
export type PlayersToken = Record<keyof Players, PlayerToken>
|
||||
|
||||
function buildPlayerToken(colorScheme: ColorScheme, index: number): PlayerToken {
|
||||
|
||||
|
@ -16,7 +16,7 @@ function buildPlayerToken(colorScheme: ColorScheme, index: number): PlayerToken
|
|||
}
|
||||
}
|
||||
|
||||
export const players = (colorScheme: ColorScheme): PlayerTokens => ({
|
||||
export const playersToken = (colorScheme: ColorScheme): PlayersToken => ({
|
||||
"0": buildPlayerToken(colorScheme, 0),
|
||||
"1": buildPlayerToken(colorScheme, 1),
|
||||
"2": buildPlayerToken(colorScheme, 2),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue