From e2f46d54483a2792a4991bfc7eeb7d86b4548550 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 8 Jun 2023 11:15:59 -0400 Subject: [PATCH 1/6] Build layer tokens for each theme --- styles/src/theme/tokens/colorScheme.ts | 7 +++ styles/src/theme/tokens/layer.ts | 60 ++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 styles/src/theme/tokens/layer.ts diff --git a/styles/src/theme/tokens/colorScheme.ts b/styles/src/theme/tokens/colorScheme.ts index eaf0705e14f92011efa75b400c67d4d5e10eb0e3..0c31a68c0efe3ffac85ea10fe17187a71e95c0e7 100644 --- a/styles/src/theme/tokens/colorScheme.ts +++ b/styles/src/theme/tokens/colorScheme.ts @@ -1,12 +1,19 @@ import { ColorScheme } from "../colorScheme" +import { LayerToken, layerToken } from "./layer" import { PlayerTokens, players } from "./players" interface ColorSchemeTokens { + lowest: LayerToken + middle: LayerToken + highest: LayerToken players: PlayerTokens } export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { return { + lowest: layerToken(colorScheme.lowest, "lowest"), + middle: layerToken(colorScheme.middle, "middle"), + highest: layerToken(colorScheme.highest, "highest"), players: players(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; +} From 999b2365a8322f7e1f09790a22c6effa5d9d079d Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 8 Jun 2023 12:21:51 -0400 Subject: [PATCH 2/6] Export additional tokens, standardize naming convention --- styles/src/theme/tokens/colorScheme.ts | 54 ++++++++++++++++++++++++-- styles/src/theme/tokens/players.ts | 4 +- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/styles/src/theme/tokens/colorScheme.ts b/styles/src/theme/tokens/colorScheme.ts index 0c31a68c0efe3ffac85ea10fe17187a71e95c0e7..1874fbcd95203630ed49f956c3fba198b6ef71fd 100644 --- a/styles/src/theme/tokens/colorScheme.ts +++ b/styles/src/theme/tokens/colorScheme.ts @@ -1,19 +1,65 @@ -import { ColorScheme } from "../colorScheme" +import { SingleBoxShadowToken, SingleColorToken, SingleOtherToken, TokenTypes, TokenTypographyValue } from "@tokens-studio/types" +import { ColorScheme, Shadow, ThemeSyntax } from "../colorScheme" import { LayerToken, layerToken } from "./layer" -import { PlayerTokens, players } from "./players" +import { PlayersToken, playersToken } from "./players" interface ColorSchemeTokens { + name: SingleOtherToken + appearance: SingleOtherToken lowest: LayerToken middle: LayerToken highest: LayerToken - players: PlayerTokens + players: PlayersToken + popoverShadow: SingleBoxShadowToken + modalShadow: SingleBoxShadowToken + syntax?: ThemeSyntaxToken } +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"); +}; + +interface SyntaxHighlightStyleToken { + color: SingleColorToken + weight: TokenTypographyValue['fontWeight'] + underline: TokenTypographyValue['textDecoration'] + italic: SingleOtherToken +} + +// TODO: Implement exporting syntax tokens +type ThemeSyntaxToken = Record + export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { return { + 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"), - players: players(colorScheme), + popoverShadow: popoverShadowToken(colorScheme), + modalShadow: modalShadowToken(colorScheme), + players: playersToken(colorScheme), } } 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), From 91e1bb8fd413436a088b5d0d82c8b6a90bcb76f6 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 8 Jun 2023 15:12:11 -0400 Subject: [PATCH 3/6] WIP syntax tokens --- styles/src/theme/tokens/colorScheme.ts | 70 ++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/styles/src/theme/tokens/colorScheme.ts b/styles/src/theme/tokens/colorScheme.ts index 1874fbcd95203630ed49f956c3fba198b6ef71fd..dcaab85816c2129e13ea5b0e3305f34b7a380c1b 100644 --- a/styles/src/theme/tokens/colorScheme.ts +++ b/styles/src/theme/tokens/colorScheme.ts @@ -1,7 +1,10 @@ import { SingleBoxShadowToken, SingleColorToken, SingleOtherToken, TokenTypes, TokenTypographyValue } from "@tokens-studio/types" -import { ColorScheme, Shadow, ThemeSyntax } from "../colorScheme" +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 { name: SingleOtherToken @@ -12,7 +15,7 @@ interface ColorSchemeTokens { players: PlayersToken popoverShadow: SingleBoxShadowToken modalShadow: SingleBoxShadowToken - syntax?: ThemeSyntaxToken + syntax?: Partial & Partial } const createShadowToken = (shadow: Shadow, tokenName: string): SingleBoxShadowToken => { @@ -33,15 +36,63 @@ const modalShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => { return createShadowToken(shadow, "modalShadow"); }; -interface SyntaxHighlightStyleToken { - color: SingleColorToken - weight: TokenTypographyValue['fontWeight'] - underline: TokenTypographyValue['textDecoration'] - italic: SingleOtherToken +type ThemeSyntaxColorTokens = Record + +function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens | null { + 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); } -// TODO: Implement exporting syntax tokens -type ThemeSyntaxToken = Record +function syntaxHighlightStyleTypographyToken(highlightStyle: SyntaxHighlightStyle): TokenTypographyValue | null { + const { weight, underline, italic } = highlightStyle + + let w = weight ? weight : "extended" + + if (italic) { + w = `${w} Italic` + } + + const fontWeight = w as TokenTypographyValue['fontWeight'] + + return { + fontWeight, + textDecoration: underline ? "underline" : "none", + } +} + +type ThemeSyntaxTypographyTokens = Record + +function syntaxHighlightStyleTypographyTokens(syntax: Syntax): ThemeSyntaxTypographyTokens | null { + 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 syntaxHighlightStyle = syntax[styleKey] as Required; + return { ...acc, [styleKey]: syntaxHighlightStyleTypographyToken(syntaxHighlightStyle) }; + }, {} as ThemeSyntaxTypographyTokens); +} + +const syntaxTokens = (colorScheme: ColorScheme): ColorSchemeTokens['syntax'] => { + const syntax = editor(colorScheme).syntax + + return { + ...syntaxHighlightStyleColorTokens(syntax), + // ...syntaxHighlightStyleTypographyTokens(syntax), + } +} export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { return { @@ -61,5 +112,6 @@ export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { popoverShadow: popoverShadowToken(colorScheme), modalShadow: modalShadowToken(colorScheme), players: playersToken(colorScheme), + syntax: syntaxTokens(colorScheme), } } From 3719c206c9886ccb378795747e342ec5e8d86737 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 8 Jun 2023 15:14:59 -0400 Subject: [PATCH 4/6] Only export color tokens for syntax --- styles/src/theme/tokens/colorScheme.ts | 44 +++----------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/styles/src/theme/tokens/colorScheme.ts b/styles/src/theme/tokens/colorScheme.ts index dcaab85816c2129e13ea5b0e3305f34b7a380c1b..84b8db5ce00a0c1767c0b7326444176f594f56fa 100644 --- a/styles/src/theme/tokens/colorScheme.ts +++ b/styles/src/theme/tokens/colorScheme.ts @@ -1,4 +1,4 @@ -import { SingleBoxShadowToken, SingleColorToken, SingleOtherToken, TokenTypes, TokenTypographyValue } from "@tokens-studio/types" +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" @@ -15,7 +15,7 @@ interface ColorSchemeTokens { players: PlayersToken popoverShadow: SingleBoxShadowToken modalShadow: SingleBoxShadowToken - syntax?: Partial & Partial + syntax?: Partial } const createShadowToken = (shadow: Shadow, tokenName: string): SingleBoxShadowToken => { @@ -38,7 +38,7 @@ const modalShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => { type ThemeSyntaxColorTokens = Record -function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens | null { +function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens { const styleKeys = Object.keys(syntax) as (keyof Syntax)[] return styleKeys.reduce((acc, styleKey) => { @@ -52,46 +52,10 @@ function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens }, {} as ThemeSyntaxColorTokens); } -function syntaxHighlightStyleTypographyToken(highlightStyle: SyntaxHighlightStyle): TokenTypographyValue | null { - const { weight, underline, italic } = highlightStyle - - let w = weight ? weight : "extended" - - if (italic) { - w = `${w} Italic` - } - - const fontWeight = w as TokenTypographyValue['fontWeight'] - - return { - fontWeight, - textDecoration: underline ? "underline" : "none", - } -} - -type ThemeSyntaxTypographyTokens = Record - -function syntaxHighlightStyleTypographyTokens(syntax: Syntax): ThemeSyntaxTypographyTokens | null { - 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 syntaxHighlightStyle = syntax[styleKey] as Required; - return { ...acc, [styleKey]: syntaxHighlightStyleTypographyToken(syntaxHighlightStyle) }; - }, {} as ThemeSyntaxTypographyTokens); -} - const syntaxTokens = (colorScheme: ColorScheme): ColorSchemeTokens['syntax'] => { const syntax = editor(colorScheme).syntax - return { - ...syntaxHighlightStyleColorTokens(syntax), - // ...syntaxHighlightStyleTypographyTokens(syntax), - } + return syntaxHighlightStyleColorTokens(syntax) } export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { From e4cbc29f9861f92b9278a95b2f3237a3cfd0841d Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 8 Jun 2023 16:19:28 -0400 Subject: [PATCH 5/6] Update buildTokens to export `$metadata.json` and `$themes.json` --- styles/src/buildTokens.ts | 78 +++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/styles/src/buildTokens.ts b/styles/src/buildTokens.ts index 81ffd3f3cbf7c36f92742bdbacd61dff6cbb1a53..bb4a2c84a6a6cba5cc154a77bdcbba1a77e4358e 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, "$tokens.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); From ddcbc774ab74cf94ad4d477eff65d40034f6cf2d Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 8 Jun 2023 16:36:15 -0400 Subject: [PATCH 6/6] `$tokens.json` => `$themes.json` --- styles/src/buildTokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/src/buildTokens.ts b/styles/src/buildTokens.ts index bb4a2c84a6a6cba5cc154a77bdcbba1a77e4358e..0cf1ea037e779818d88d05383b1b8fffd6337ade 100644 --- a/styles/src/buildTokens.ts +++ b/styles/src/buildTokens.ts @@ -6,7 +6,7 @@ import { slugify } from "./utils/slugify"; import { colorSchemeTokens } from "./theme/tokens/colorScheme"; const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens"); -const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$tokens.json"); +const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json"); const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json"); function clearTokens(tokensDirectory: string) {