diff --git a/styles/src/buildTokens.ts b/styles/src/buildTokens.ts index 81ffd3f3cbf7c36f92742bdbacd61dff6cbb1a53..0cf1ea037e779818d88d05383b1b8fffd6337ade 100644 --- a/styles/src/buildTokens.ts +++ b/styles/src/buildTokens.ts @@ -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); diff --git a/styles/src/theme/tokens/colorScheme.ts b/styles/src/theme/tokens/colorScheme.ts index eaf0705e14f92011efa75b400c67d4d5e10eb0e3..84b8db5ce00a0c1767c0b7326444176f594f56fa 100644 --- a/styles/src/theme/tokens/colorScheme.ts +++ b/styles/src/theme/tokens/colorScheme.ts @@ -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 +} + +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 + +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; + 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), } } diff --git a/styles/src/theme/tokens/layer.ts b/styles/src/theme/tokens/layer.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ee813b8c4917d733c3bd91de0728de084f81524 --- /dev/null +++ b/styles/src/theme/tokens/layer.ts @@ -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; +} diff --git a/styles/src/theme/tokens/players.ts b/styles/src/theme/tokens/players.ts index c65fb6885ec3b0c1e10386f1b9a1dc07ffdb28b8..b5f5538b2eae54c68ca2a49ac7a1e2523ed1251b 100644 --- a/styles/src/theme/tokens/players.ts +++ b/styles/src/theme/tokens/players.ts @@ -4,7 +4,7 @@ import { colorToken } from "./token" export type PlayerToken = Record<"selection" | "cursor", SingleColorToken> -export type PlayerTokens = Record +export type PlayersToken = Record 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),