unholy-design-tokens 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/lint-format-build.yml +35 -0
- package/.github/workflows/publish_release.yml +18 -0
- package/.prettierrc +9 -0
- package/ANALOGY_HOUSE.md +139 -0
- package/ANALOGY_I18NEXT.md +160 -0
- package/LICENSE +201 -0
- package/README.md +90 -0
- package/WHY_THIS_EXISTS.md +74 -0
- package/bin/build-tokens.ts +34 -0
- package/build/css/bg/bg.css +60 -0
- package/build/css/border/border.css +51 -0
- package/build/css/colors.css +204 -0
- package/build/css/conditional.css +8 -0
- package/build/css/cube/cube.block.css +18 -0
- package/build/css/cube/cube.composition.css +16 -0
- package/build/css/cube/cube.utility.css +185 -0
- package/build/css/font/font.css +24 -0
- package/build/css/space.css +20 -0
- package/build/css/text/text.css +48 -0
- package/build/css/themes/private-theme.css +228 -0
- package/build/css/themes/public-theme.css +228 -0
- package/build/css/variant/variant.css +42 -0
- package/build/css/variants.css +167 -0
- package/build/types/theme.d.ts +932 -0
- package/build/types/tokens.ts +653 -0
- package/dist/bin/build-tokens.js +27 -0
- package/dist/scripts/build-style-dictionary.js +32 -0
- package/dist/scripts/generate-typography-tokens.js +125 -0
- package/dist/src/colors/color.config.js +45 -0
- package/dist/src/colors/color.filter.js +19 -0
- package/dist/src/colors/color.formatter.js +25 -0
- package/dist/src/colors/index.js +2 -0
- package/dist/src/cube-css/cube.config.js +42 -0
- package/dist/src/cube-css/cube.formatter.js +89 -0
- package/dist/src/style-dictionary.config.js +143 -0
- package/dist/src/type-declarations/type-declarations.config.js +29 -0
- package/dist/src/type-declarations/type-declarations.formatter.js +111 -0
- package/dist/src/utils/helpers.js +9 -0
- package/dist/src/utils/index.js +4 -0
- package/dist/src/utils/template.js +83 -0
- package/dist/src/utils/tokens.js +80 -0
- package/dist/src/utils/utopia.js +19 -0
- package/eslint.config.js +67 -0
- package/package.json +60 -0
- package/scripts/build-style-dictionary.ts +44 -0
- package/scripts/generate-typography-tokens.ts +138 -0
- package/src/LICENSE +201 -0
- package/src/README.md +88 -0
- package/src/colors/color.config.ts +48 -0
- package/src/colors/color.filter.ts +28 -0
- package/src/colors/color.formatter.ts +43 -0
- package/src/colors/index.ts +6 -0
- package/src/cube-css/cube.config.ts +50 -0
- package/src/cube-css/cube.formatter.ts +104 -0
- package/src/formatters/spacing.js +95 -0
- package/src/style-dictionary.config.ts +151 -0
- package/src/theme/README.md +256 -0
- package/src/theme/cube-theme-addon.js +44 -0
- package/src/theme/helper.js +38 -0
- package/src/theme/index.js +6 -0
- package/src/theme/theme.config.js +42 -0
- package/src/theme/theme.filter.js +42 -0
- package/src/theme/theme.formatter.js +71 -0
- package/src/tokens/1 - primitives/README.md +58 -0
- package/src/tokens/1 - primitives/border.json +54 -0
- package/src/tokens/1 - primitives/breakpoint.json +10 -0
- package/src/tokens/1 - primitives/color-pool.json +266 -0
- package/src/tokens/1 - primitives/color.json +266 -0
- package/src/tokens/1 - primitives/font-scale.json +27 -0
- package/src/tokens/1 - primitives/font.json +23 -0
- package/src/tokens/1 - primitives/shadow.json +26 -0
- package/src/tokens/1 - primitives/space.json +27 -0
- package/src/tokens/2 - semantic/README.md +49 -0
- package/src/tokens/2 - semantic/border.json +27 -0
- package/src/tokens/2 - semantic/color.json +263 -0
- package/src/tokens/2 - semantic/details.md +1 -0
- package/src/tokens/2 - semantic/layout.json +52 -0
- package/src/tokens/2 - semantic/radius.json +13 -0
- package/src/tokens/2 - semantic/shadow.json +19 -0
- package/src/tokens/2 - semantic/spacing.json +25 -0
- package/src/tokens/3 - intent/README.md +43 -0
- package/src/tokens/3 - intent/background.json +135 -0
- package/src/tokens/3 - intent/color.json +265 -0
- package/src/tokens/3 - intent/font.json +61 -0
- package/src/tokens/3 - intent/text +67 -0
- package/src/tokens/README.md +176 -0
- package/src/tokens/color/brand.json +316 -0
- package/src/tokens/component/theming.json +69 -0
- package/src/tokens/conditional.json +40 -0
- package/src/tokens/custom/4 - (OPTIONAL) cube css/README.md +38 -0
- package/src/tokens/custom/4 - (OPTIONAL) cube css/block.json +24 -0
- package/src/tokens/custom/4 - (OPTIONAL) cube css/composition.json +26 -0
- package/src/tokens/custom/4 - (OPTIONAL) cube css/global.json +15 -0
- package/src/tokens/custom/4 - (OPTIONAL) cube css/utility.json +224 -0
- package/src/tokens/custom/OKlch/color.json +61 -0
- package/src/tokens/custom/OKlch/state.json +107 -0
- package/src/tokens/custom/OKlch/theme-color.json +34 -0
- package/src/tokens/custom/OKlch/variant.json +67 -0
- package/src/tokens/custom/components/highlighted.json +16 -0
- package/src/tokens/state.js +29 -0
- package/src/tokens/theme-color.json +34 -0
- package/src/type-declarations/type-declarations.config.ts +34 -0
- package/src/type-declarations/type-declarations.formatter.ts +122 -0
- package/src/utils/helpers.ts +11 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/template.ts +110 -0
- package/src/utils/tokens.ts +95 -0
- package/src/utils/utopia.ts +36 -0
- package/tailwind.md +720 -0
- package/tsconfig.json +19 -0
- package/turbowatch.ts +14 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import StyleDictionary from 'style-dictionary';
|
|
3
|
+
import { logVerbosityLevels } from 'style-dictionary/enums';
|
|
4
|
+
import styleDictionaryConfig from '../src/style-dictionary.config';
|
|
5
|
+
import { tokensDeclarationFormatter, typesDeclarationFormatter, } from '../src/type-declarations/type-declarations.formatter.js';
|
|
6
|
+
import { generateTypographyTokens } from './generate-typography-tokens';
|
|
7
|
+
function ensureTrailingSlash(p) {
|
|
8
|
+
// Style Dictionary wants POSIX-ish trailing slash, even on Windows it accepts `/`
|
|
9
|
+
return p.endsWith(path.sep) ? p : p + path.sep;
|
|
10
|
+
}
|
|
11
|
+
export async function buildStyleDictionary(outDir) {
|
|
12
|
+
// 1️⃣ Generate typography tokens FIRST
|
|
13
|
+
generateTypographyTokens();
|
|
14
|
+
// clone config so we can override buildPath safely
|
|
15
|
+
const config = {
|
|
16
|
+
...styleDictionaryConfig,
|
|
17
|
+
platforms: Object.fromEntries(Object.entries(styleDictionaryConfig.platforms).map(([name, platform]) => [
|
|
18
|
+
name,
|
|
19
|
+
{ ...platform },
|
|
20
|
+
])),
|
|
21
|
+
};
|
|
22
|
+
for (const platform of Object.values(config.platforms)) {
|
|
23
|
+
platform.buildPath = ensureTrailingSlash(path.resolve(outDir, platform.buildPath ?? './build/'));
|
|
24
|
+
}
|
|
25
|
+
let sd = new StyleDictionary(config, {
|
|
26
|
+
verbosity: logVerbosityLevels.verbose,
|
|
27
|
+
});
|
|
28
|
+
// Types formatters
|
|
29
|
+
StyleDictionary.registerFormat(typesDeclarationFormatter);
|
|
30
|
+
StyleDictionary.registerFormat(tokensDeclarationFormatter);
|
|
31
|
+
await sd.buildAllPlatforms();
|
|
32
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// scripts/generate-typography-tokens.js
|
|
2
|
+
// ---------------------------------------------------------
|
|
3
|
+
// Generate ALL typography tokens from primitives/font.json
|
|
4
|
+
// ---------------------------------------------------------
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { generateUtopiaScale } from '../src/utils';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const INPUT_FILE = path.join(__dirname, `../src/tokens/1 - primitives/font.json`);
|
|
12
|
+
const PRIMITIVES_OUT = path.join(__dirname, `../src/tokens/1 - primitives/font-scale.json`);
|
|
13
|
+
const INTENT_OUT = path.join(__dirname, '../src/tokens/3 - intent/font.json');
|
|
14
|
+
// -----------------------------------
|
|
15
|
+
// Load primitive inputs for typography
|
|
16
|
+
// -----------------------------------
|
|
17
|
+
export function generateTypographyTokens() {
|
|
18
|
+
if (!fs.existsSync(INPUT_FILE)) {
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
let configToken;
|
|
22
|
+
try {
|
|
23
|
+
configToken = JSON.parse(fs.readFileSync(INPUT_FILE, 'utf8')).font;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const config = {
|
|
29
|
+
minFont: configToken.minFont?.value ?? 16,
|
|
30
|
+
maxFont: configToken.maxFont?.value ?? 20,
|
|
31
|
+
minViewport: configToken.minViewport?.value ?? 360,
|
|
32
|
+
maxViewport: configToken.maxViewport?.value ?? 1280,
|
|
33
|
+
scaleMin: configToken.scaleMin?.value ?? 1.2,
|
|
34
|
+
scaleMax: configToken.scaleMax?.value ?? 1.25,
|
|
35
|
+
steps: configToken.steps?.value ?? [0, 1, 2, 3, 4, 5, 6],
|
|
36
|
+
};
|
|
37
|
+
// -----------------------------------
|
|
38
|
+
// Build Utopia scale
|
|
39
|
+
// -----------------------------------
|
|
40
|
+
const scale = generateUtopiaScale(config);
|
|
41
|
+
// -----------------------------------
|
|
42
|
+
// Output primitive scale tokens
|
|
43
|
+
// -----------------------------------
|
|
44
|
+
const primitives = {
|
|
45
|
+
font: {
|
|
46
|
+
scale: Object.fromEntries(Object.entries(scale).map(([step, value]) => [step, { value }])),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
// -----------------------------------
|
|
50
|
+
// Define intent tokens
|
|
51
|
+
// -----------------------------------
|
|
52
|
+
const intent = {
|
|
53
|
+
text: {
|
|
54
|
+
body: {
|
|
55
|
+
size: {
|
|
56
|
+
value: 'var(--font-scale-0)',
|
|
57
|
+
},
|
|
58
|
+
lineHeight: {
|
|
59
|
+
value: '1.5',
|
|
60
|
+
},
|
|
61
|
+
weight: {
|
|
62
|
+
value: 'var(--font-weight-regular)',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
caption: {
|
|
66
|
+
size: {
|
|
67
|
+
value: 'var(--font-scale--2)',
|
|
68
|
+
},
|
|
69
|
+
lineHeight: {
|
|
70
|
+
value: '1.4',
|
|
71
|
+
},
|
|
72
|
+
weight: {
|
|
73
|
+
value: 'var(--font-weight-regular)',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
small: {
|
|
77
|
+
size: {
|
|
78
|
+
value: 'var(--font-scale--1)',
|
|
79
|
+
},
|
|
80
|
+
lineHeight: {
|
|
81
|
+
value: '1.4',
|
|
82
|
+
},
|
|
83
|
+
weight: {
|
|
84
|
+
value: 'var(--font-weight-regular)',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
heading: {
|
|
89
|
+
h1: {
|
|
90
|
+
size: {
|
|
91
|
+
value: 'var(--font-scale-4)',
|
|
92
|
+
},
|
|
93
|
+
lineHeight: {
|
|
94
|
+
value: '1.1',
|
|
95
|
+
},
|
|
96
|
+
weight: {
|
|
97
|
+
value: 'var(--font-weight-bold)',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
h2: {
|
|
101
|
+
size: {
|
|
102
|
+
value: 'var(--font-scale-3)',
|
|
103
|
+
},
|
|
104
|
+
lineHeight: {
|
|
105
|
+
value: '1.15',
|
|
106
|
+
},
|
|
107
|
+
weight: {
|
|
108
|
+
value: 'var(--font-weight-bold)',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
// -----------------------------------
|
|
114
|
+
// Write JSON files
|
|
115
|
+
// -----------------------------------
|
|
116
|
+
function writeJSON(file, data) {
|
|
117
|
+
const dir = path.dirname(file);
|
|
118
|
+
if (!fs.existsSync(dir))
|
|
119
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
120
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2));
|
|
121
|
+
}
|
|
122
|
+
writeJSON(PRIMITIVES_OUT, primitives);
|
|
123
|
+
// writeJSON(SEMANTIC_OUT, semantic);
|
|
124
|
+
writeJSON(INTENT_OUT, intent);
|
|
125
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import StyleDictionary from 'style-dictionary';
|
|
2
|
+
import { isColorToken } from './color.filter';
|
|
3
|
+
import { colorFormatter } from './color.formatter';
|
|
4
|
+
StyleDictionary.registerFormat(colorFormatter);
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
destination: 'colors.css',
|
|
8
|
+
transformGroup: 'css',
|
|
9
|
+
format: 'format/color',
|
|
10
|
+
// format: "css/variables",
|
|
11
|
+
filter: (token) => isColorToken(token),
|
|
12
|
+
options: {
|
|
13
|
+
// outputReferences: true,
|
|
14
|
+
// fileHeader: () => {
|
|
15
|
+
// return ["Collection name: Color Primitives", "Mode: Light"];
|
|
16
|
+
// },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
// {
|
|
20
|
+
// destination: "colors.semantic.css",
|
|
21
|
+
// transformGroup: "css",
|
|
22
|
+
// // format: "css/variables",
|
|
23
|
+
// format: "format/color",
|
|
24
|
+
// filter: (token) => isSemanticColor(token),
|
|
25
|
+
// options: {
|
|
26
|
+
// outputReferences: true,
|
|
27
|
+
// fileHeader: () => {
|
|
28
|
+
// return ["Collection name: Color Semantics", "Mode: Light"];
|
|
29
|
+
// },
|
|
30
|
+
// },
|
|
31
|
+
// },
|
|
32
|
+
// {
|
|
33
|
+
// destination: "colors.intent.css",
|
|
34
|
+
// transformGroup: "css",
|
|
35
|
+
// // format: "css/variables",
|
|
36
|
+
// format: "format/color",
|
|
37
|
+
// filter: (token) => isIntentColor(token),
|
|
38
|
+
// options: {
|
|
39
|
+
// outputReferences: true,
|
|
40
|
+
// fileHeader: () => {
|
|
41
|
+
// return ["Collection name: Color Intents", "Mode: Light"];
|
|
42
|
+
// },
|
|
43
|
+
// },
|
|
44
|
+
// },
|
|
45
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function isPrimitiveColor(token) {
|
|
2
|
+
if (token.attributes?.tokenTier)
|
|
3
|
+
return token.attributes?.tokenTier === 'primitive' && token.attributes?.category === 'color';
|
|
4
|
+
}
|
|
5
|
+
export function isSemanticColor(token) {
|
|
6
|
+
return token.attributes?.tokenTier === 'semantic' && token.attributes?.category === 'color';
|
|
7
|
+
}
|
|
8
|
+
export function isIntentColor(token) {
|
|
9
|
+
return token.attributes?.tokenTier === 'intent' && token.attributes?.category === 'color';
|
|
10
|
+
}
|
|
11
|
+
export function isPoolColor(token) {
|
|
12
|
+
return token.attributes?.tokenTier === 'color-pool' && token.attributes?.category === 'color-pool';
|
|
13
|
+
}
|
|
14
|
+
export function isColorToken(token) {
|
|
15
|
+
return (isPoolColor(token) || isPrimitiveColor(token) || isSemanticColor(token) || isIntentColor(token));
|
|
16
|
+
}
|
|
17
|
+
export function filterThemeTokens(token) {
|
|
18
|
+
return isPrimitiveColor(token);
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { capitalizeFirstLetter, generateFigmaHeaderReference, generateHeader, resolveTokenReferences, toKebab, tokenName, } from '../utils';
|
|
2
|
+
export const colorFormatter = {
|
|
3
|
+
name: 'format/color',
|
|
4
|
+
format: ({ dictionary, options }) => {
|
|
5
|
+
// Get color tier from first token
|
|
6
|
+
// e.g. 'primitive', 'semantic', 'intent'
|
|
7
|
+
const colorTier = dictionary.allTokens[0].attributes?.tokenTier;
|
|
8
|
+
let content = '';
|
|
9
|
+
const header = generateFigmaHeaderReference(`Color ${capitalizeFirstLetter(colorTier)}`, options?.mode || 'Light');
|
|
10
|
+
let currentTier = null;
|
|
11
|
+
dictionary.allTokens.forEach((token) => {
|
|
12
|
+
const value = resolveTokenReferences(token, options, dictionary);
|
|
13
|
+
let usedSubsections = new Set(); // resets per category
|
|
14
|
+
const tier = token?.attributes?.tokenTier;
|
|
15
|
+
if (tier && tier !== currentTier) {
|
|
16
|
+
currentTier = tier;
|
|
17
|
+
usedSubsections = new Set();
|
|
18
|
+
content += generateHeader(tier);
|
|
19
|
+
}
|
|
20
|
+
const name = `--${toKebab(tokenName(token))}`;
|
|
21
|
+
content += `${name}: ${value};\n`;
|
|
22
|
+
});
|
|
23
|
+
return `${header}\n:root {\n${content}}`;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// cube.files.js
|
|
2
|
+
import StyleDictionary from 'style-dictionary';
|
|
3
|
+
import { formats } from 'style-dictionary/enums';
|
|
4
|
+
import { cubeUtilityFormatter } from './cube.formatter';
|
|
5
|
+
const cubeBuildPath = 'cube';
|
|
6
|
+
// registers "cube/utility"
|
|
7
|
+
StyleDictionary.registerFormat(cubeUtilityFormatter);
|
|
8
|
+
export default [
|
|
9
|
+
// Composition → @layer objects
|
|
10
|
+
{
|
|
11
|
+
destination: `${cubeBuildPath}/cube.composition.css`,
|
|
12
|
+
format: formats.cssVariables,
|
|
13
|
+
filter: (token) => token.attributes?.category === 'cube' && token.attributes?.type === 'composition',
|
|
14
|
+
options: {
|
|
15
|
+
layerName: 'objects',
|
|
16
|
+
selector: ':root',
|
|
17
|
+
// if your cube values are already "var(...)" and not "{...}",
|
|
18
|
+
// you don't need SD reference output:
|
|
19
|
+
outputReferences: false,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
// Block → @layer components
|
|
23
|
+
{
|
|
24
|
+
destination: `${cubeBuildPath}/cube.block.css`,
|
|
25
|
+
format: formats.cssVariables,
|
|
26
|
+
filter: (token) => token.attributes?.category === 'cube' && token.attributes?.type === 'block',
|
|
27
|
+
options: {
|
|
28
|
+
layerName: 'components',
|
|
29
|
+
selector: ':root',
|
|
30
|
+
outputReferences: false,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
// Utility → custom formatter
|
|
34
|
+
{
|
|
35
|
+
destination: `${cubeBuildPath}/cube.utility.css`,
|
|
36
|
+
format: 'cube/utility',
|
|
37
|
+
filter: (token) => token.attributes?.category === 'cube' && token.attributes?.type === 'utility',
|
|
38
|
+
options: {
|
|
39
|
+
outputReferences: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { generateHeader, toKebab } from '../utils';
|
|
2
|
+
/**
|
|
3
|
+
* Build public/private CSS var names from tokens.
|
|
4
|
+
*
|
|
5
|
+
* Rules:
|
|
6
|
+
* - For "normal" tokens (color, border, space, etc.), keep:
|
|
7
|
+
* --{type}-{item}[-{subitem}]
|
|
8
|
+
* e.g. --border-width-default, --space-md
|
|
9
|
+
*
|
|
10
|
+
* - For cube tokens (category: "cube"):
|
|
11
|
+
* - type = "block", "composition", "utility", ...
|
|
12
|
+
* - item = e.g. "card", "stack", "cluster"
|
|
13
|
+
* - subitem = e.g. "background", "borderColor", "gap"
|
|
14
|
+
*
|
|
15
|
+
* Option A mapping:
|
|
16
|
+
* - block/card/background → --block-card-background
|
|
17
|
+
* - composition/stack/gap → --stack-gap
|
|
18
|
+
* - utility/spacing/mt-0 → --utility-spacing-mt-0
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
export function generateThemeCubeCSS(tokens) {
|
|
22
|
+
const order = ['global', 'composition', 'block', 'utility'];
|
|
23
|
+
return tokens
|
|
24
|
+
.filter((t) => t.attributes?.category === 'cube')
|
|
25
|
+
.sort((a, b) => {
|
|
26
|
+
const aType = a.attributes?.type;
|
|
27
|
+
const bType = b.attributes?.type;
|
|
28
|
+
const aIndex = order.indexOf(aType);
|
|
29
|
+
const bIndex = order.indexOf(bType);
|
|
30
|
+
return aIndex - bIndex;
|
|
31
|
+
})
|
|
32
|
+
.map((t) => generateThemeCubeCSSVariables(t));
|
|
33
|
+
}
|
|
34
|
+
export function generateThemeCubeCSSVariables(token) {
|
|
35
|
+
const attrs = token.attributes || {};
|
|
36
|
+
const path = token.path || [];
|
|
37
|
+
const item = attrs.item || path[1] || 'default';
|
|
38
|
+
const subitem = attrs.subitem;
|
|
39
|
+
const base = subitem ? `${item}-${subitem}` : `${item}`; // no dangling "-"
|
|
40
|
+
const publicName = `--${toKebab(base)}`;
|
|
41
|
+
const privateName = publicName.replace(/^--/, '--_');
|
|
42
|
+
return {
|
|
43
|
+
publicName,
|
|
44
|
+
privateName,
|
|
45
|
+
value: token.value,
|
|
46
|
+
category: attrs.category, // "cube"
|
|
47
|
+
subsection: attrs.type, // ✅ "block" | "composition" | ...
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* CUBE: Utility → @layer utilities
|
|
52
|
+
* Generate utility classes from tokens.
|
|
53
|
+
*/
|
|
54
|
+
export const cubeUtilityFormatter = {
|
|
55
|
+
name: 'cube/utility',
|
|
56
|
+
format: ({ dictionary }) => {
|
|
57
|
+
let content = '';
|
|
58
|
+
const usedItems = new Set();
|
|
59
|
+
dictionary.allTokens.forEach((token) => {
|
|
60
|
+
const item = token.attributes?.item;
|
|
61
|
+
if (item && !usedItems.has(item)) {
|
|
62
|
+
usedItems.add(item);
|
|
63
|
+
content += generateHeader(item);
|
|
64
|
+
}
|
|
65
|
+
content += generateCubeUtilityClass(token);
|
|
66
|
+
});
|
|
67
|
+
return content;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
export function generateCubeUtilityClass(token) {
|
|
71
|
+
// fallback for other cube types, e.g. utility/spacing/mt-0 → --utility-spacing-mt-0
|
|
72
|
+
const attrs = token.attributes || {};
|
|
73
|
+
const path = token.path || [];
|
|
74
|
+
const item = attrs.item || path[1] || '';
|
|
75
|
+
let className = '';
|
|
76
|
+
const hideItems = ['display'];
|
|
77
|
+
if (!hideItems.includes(item)) {
|
|
78
|
+
className = `${item}-`;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
className = ``;
|
|
82
|
+
}
|
|
83
|
+
const subitem = attrs.subitem ? `${attrs.subitem}` : '';
|
|
84
|
+
const state = attrs.state ? `-${attrs.state}` : '';
|
|
85
|
+
className = `${className}${subitem}${state}`;
|
|
86
|
+
className = `.${toKebab(className)}`;
|
|
87
|
+
const classCssContent = `${token.value}`;
|
|
88
|
+
return `${className} { ${classCssContent} } \n`;
|
|
89
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import StyleDictionary from 'style-dictionary';
|
|
2
|
+
import { formats, transformGroups } from 'style-dictionary/enums';
|
|
3
|
+
// @ts-expect-error - JS module without type declarations
|
|
4
|
+
import { themeConfig } from './theme/index.js';
|
|
5
|
+
import typeDeclarationsConfig from './type-declarations/type-declarations.config.js';
|
|
6
|
+
// import { cubeCssVariablesLayerFormatter } from './cube-css/formatters/cube-css.js';
|
|
7
|
+
import cubeConfig from './cube-css/cube.config.js';
|
|
8
|
+
// @ts-expect-error - JS module without type declarations
|
|
9
|
+
import { spacingFluid } from './formatters/spacing.js';
|
|
10
|
+
// import "./theme/formatters/theme.format.js"; // your file shown above (theme formats)
|
|
11
|
+
import colorConfig from './colors/color.config.js';
|
|
12
|
+
// import themeConfig from './theme/theme.config.js';
|
|
13
|
+
StyleDictionary.registerFormat(spacingFluid);
|
|
14
|
+
function generateThemeFiles(directories) {
|
|
15
|
+
const genericAttributes = {
|
|
16
|
+
format: formats.cssVariables,
|
|
17
|
+
options: {
|
|
18
|
+
// outputReferences: true,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
return directories.map((dir) => {
|
|
22
|
+
return {
|
|
23
|
+
...genericAttributes,
|
|
24
|
+
// output the dironent tokens in the right folder and file e.g. dironents/button/button-vars.css
|
|
25
|
+
destination: `${dir}/${dir}.css`,
|
|
26
|
+
format: formats.cssVariables,
|
|
27
|
+
// only include the tokens that are inside this dironent token group
|
|
28
|
+
filter: (token) => {
|
|
29
|
+
return token.path[0] === dir || token.attributes?.type === dir;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
StyleDictionary.registerFormat({
|
|
35
|
+
name: 'conditional-css',
|
|
36
|
+
format: function ({ dictionary }) {
|
|
37
|
+
let output = `:root {\n`;
|
|
38
|
+
output += dictionary.allTokens
|
|
39
|
+
.map((token) => {
|
|
40
|
+
const { type } = token.attributes || {};
|
|
41
|
+
return ` --${type}: ${token.value};`;
|
|
42
|
+
})
|
|
43
|
+
.join('\n');
|
|
44
|
+
output += `\n}\n`;
|
|
45
|
+
return output;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
export default {
|
|
49
|
+
// parser: 'initial-theme-parser',
|
|
50
|
+
source: ['src/tokens/**/*.json'],
|
|
51
|
+
// action: ['generate-utopia-typography'],
|
|
52
|
+
platforms: {
|
|
53
|
+
css: {
|
|
54
|
+
transformGroup: transformGroups.css,
|
|
55
|
+
transforms: [
|
|
56
|
+
'attribute/cti', // pick up category/type/item
|
|
57
|
+
],
|
|
58
|
+
buildPath: 'css/',
|
|
59
|
+
clearBuildPath: true,
|
|
60
|
+
outputReferences: true,
|
|
61
|
+
files: [
|
|
62
|
+
{
|
|
63
|
+
destination: 'tokens.css',
|
|
64
|
+
format: 'css/variables',
|
|
65
|
+
filter: (token) => {
|
|
66
|
+
return token.attributes?.category === 'typography';
|
|
67
|
+
},
|
|
68
|
+
actions: ['generate-utopia-typography'], // <- here
|
|
69
|
+
options: {
|
|
70
|
+
outputReferences: true,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
destination: 'conditional.css',
|
|
75
|
+
filter: (token) => token.attributes?.category === 'conditional',
|
|
76
|
+
format: 'conditional-css',
|
|
77
|
+
transformGroup: 'css',
|
|
78
|
+
options: {
|
|
79
|
+
outputReferences: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
...themeConfig,
|
|
83
|
+
{
|
|
84
|
+
destination: 'space.css',
|
|
85
|
+
// format: "css/spacing-fluid",
|
|
86
|
+
format: formats.cssVariables,
|
|
87
|
+
filter: (token) => {
|
|
88
|
+
return token.attributes?.category === 'space' || token.attributes?.type === 'space';
|
|
89
|
+
},
|
|
90
|
+
options: {
|
|
91
|
+
outputReferences: true,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
// -------------------------------------------------------
|
|
95
|
+
/**
|
|
96
|
+
* OKlch tokens
|
|
97
|
+
*/
|
|
98
|
+
// {
|
|
99
|
+
// destination: 'primitives.css',
|
|
100
|
+
// format: formats.cssVariables,
|
|
101
|
+
// filter: (token) => {
|
|
102
|
+
// return token.attributes?.type === 'primitive'
|
|
103
|
+
// },
|
|
104
|
+
// },
|
|
105
|
+
// {
|
|
106
|
+
// destination: 'variant.css',
|
|
107
|
+
// format: formats.cssVariables,
|
|
108
|
+
// filter: (token) => {
|
|
109
|
+
// return token.attributes?.category === 'variant'
|
|
110
|
+
// },
|
|
111
|
+
// options: {
|
|
112
|
+
// fileHeader: (defaultMessage: string[]) => {
|
|
113
|
+
// return [...defaultMessage, 'Variant tokens']
|
|
114
|
+
// },
|
|
115
|
+
// },
|
|
116
|
+
// },
|
|
117
|
+
{
|
|
118
|
+
destination: 'variants.css',
|
|
119
|
+
format: formats.cssVariables,
|
|
120
|
+
filter: (token) => {
|
|
121
|
+
const variants = ['variant', 'state', 'color'];
|
|
122
|
+
const category = token.attributes?.category;
|
|
123
|
+
return (variants.includes(category) &&
|
|
124
|
+
token.attributes?.tokenTier !== 'intent' &&
|
|
125
|
+
token.attributes?.tokenTier !== 'semantic' &&
|
|
126
|
+
token.attributes?.tokenTier !== 'primitive');
|
|
127
|
+
},
|
|
128
|
+
options: {
|
|
129
|
+
fileHeader: (defaultMessage) => {
|
|
130
|
+
return [...defaultMessage, 'Variant tokens'];
|
|
131
|
+
},
|
|
132
|
+
outputReferences: true,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
...colorConfig,
|
|
136
|
+
...cubeConfig,
|
|
137
|
+
...generateThemeFiles(['components', 'font', 'bg', 'border', 'text', 'variant']),
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
// Type declarations
|
|
141
|
+
...typeDeclarationsConfig,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import StyleDictionary from 'style-dictionary';
|
|
2
|
+
import { tokensDeclarationFormatter, typesDeclarationFormatter, } from './type-declarations.formatter';
|
|
3
|
+
StyleDictionary.registerFormat(typesDeclarationFormatter);
|
|
4
|
+
StyleDictionary.registerFormat(tokensDeclarationFormatter);
|
|
5
|
+
export default {
|
|
6
|
+
ts: {
|
|
7
|
+
// transformGroup: "js",
|
|
8
|
+
// transformGroup: "js",
|
|
9
|
+
buildPath: 'types/',
|
|
10
|
+
files: [
|
|
11
|
+
{
|
|
12
|
+
destination: 'theme.d.ts',
|
|
13
|
+
format: 'typescript/types-declaration',
|
|
14
|
+
transformGroup: 'js',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
// Tokens declarations
|
|
19
|
+
tokens: {
|
|
20
|
+
transformGroup: 'css',
|
|
21
|
+
buildPath: 'types/',
|
|
22
|
+
files: [
|
|
23
|
+
{
|
|
24
|
+
destination: 'tokens.ts',
|
|
25
|
+
format: 'typescript/tokens-declaration',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import StyleDictionary from 'style-dictionary';
|
|
2
|
+
/**
|
|
3
|
+
* Generates a TypeScript interface declaration for all design tokens.
|
|
4
|
+
*
|
|
5
|
+
* @type {{ name: string; format: ({ dictionary }: FormatFnArguments) => string; }}
|
|
6
|
+
*
|
|
7
|
+
* Will return:
|
|
8
|
+
* export interface YThemeToken {
|
|
9
|
+
* tokenName1: string;
|
|
10
|
+
* tokenName2: string;
|
|
11
|
+
* ...
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
StyleDictionary.registerFormat({
|
|
15
|
+
name: 'typescript/types-declaration',
|
|
16
|
+
format: function ({ dictionary }) {
|
|
17
|
+
const props = dictionary.tokens;
|
|
18
|
+
console.log('📟 - props → ', props);
|
|
19
|
+
// const types = props.map((token) => ` '${token.name}': string;`).join("\n");
|
|
20
|
+
// return `export interface YThemeToken {\n${types}\n}\n`;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Generates a TypeScript interface declaration for all design tokens.
|
|
25
|
+
*
|
|
26
|
+
* Will return:
|
|
27
|
+
* export interface YThemeToken {
|
|
28
|
+
* tokenName1: string;
|
|
29
|
+
* tokenName2: string;
|
|
30
|
+
* ...
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* where tokenName is the name of the token e.g. 'color-primary-100'
|
|
34
|
+
*/
|
|
35
|
+
export const tokensDeclarationFormatter = {
|
|
36
|
+
name: 'typescript/tokens-declaration',
|
|
37
|
+
format: function ({ dictionary }) {
|
|
38
|
+
const myMap = dictionary.tokenMap;
|
|
39
|
+
let types = '';
|
|
40
|
+
for (const value of myMap.values()) {
|
|
41
|
+
const name = value.name;
|
|
42
|
+
types += ` '${name}': string;\n`;
|
|
43
|
+
}
|
|
44
|
+
return `export interface YThemeToken {\n${types}\n}\n`;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* @type {{ name: string; format: ({ dictionary }: FormatFnArguments) => string; }}
|
|
49
|
+
*
|
|
50
|
+
* Will return:
|
|
51
|
+
* export interface DesignTokens {
|
|
52
|
+
* border: {
|
|
53
|
+
* width: {
|
|
54
|
+
* base: string;
|
|
55
|
+
* scale: string;
|
|
56
|
+
* };
|
|
57
|
+
* };
|
|
58
|
+
* ...
|
|
59
|
+
* }
|
|
60
|
+
*/
|
|
61
|
+
export const typesDeclarationFormatter = {
|
|
62
|
+
name: 'typescript/types-declaration',
|
|
63
|
+
format: function ({ dictionary }) {
|
|
64
|
+
const interfaceBody = generateNestedInterface(dictionary.tokens, 1);
|
|
65
|
+
return `/**
|
|
66
|
+
* Do not edit directly, this file was auto-generated.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
export interface DesignTokens {
|
|
70
|
+
${interfaceBody}}
|
|
71
|
+
`;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Check if an object is a leaf token (has $value or value property)
|
|
76
|
+
*/
|
|
77
|
+
function isToken(obj) {
|
|
78
|
+
return obj && typeof obj === 'object' && ('$value' in obj || 'value' in obj);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Escape property name if it contains special characters
|
|
82
|
+
*/
|
|
83
|
+
function formatKey(key) {
|
|
84
|
+
// If key starts with a number or contains special chars, quote it
|
|
85
|
+
if (/^[0-9]/.test(key) || /[^a-zA-Z0-9_$]/.test(key)) {
|
|
86
|
+
return `'${key}'`;
|
|
87
|
+
}
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Recursively generate nested interface from token tree
|
|
92
|
+
*/
|
|
93
|
+
function generateNestedInterface(obj, depth = 0) {
|
|
94
|
+
const indent = ' '.repeat(depth);
|
|
95
|
+
let result = '';
|
|
96
|
+
for (const key of Object.keys(obj)) {
|
|
97
|
+
const value = obj[key];
|
|
98
|
+
if (isToken(value)) {
|
|
99
|
+
// Leaf token - output as string type
|
|
100
|
+
result += `${indent}${formatKey(key)}: string;\n`;
|
|
101
|
+
}
|
|
102
|
+
else if (typeof value === 'object' && value !== null) {
|
|
103
|
+
// Nested object - recurse
|
|
104
|
+
const nested = generateNestedInterface(value, depth + 1);
|
|
105
|
+
if (nested.trim()) {
|
|
106
|
+
result += `${indent}${formatKey(key)}: {\n${nested}${indent}};\n`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|