@aziontech/theme 1.12.0 → 2.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/README.md +1 -1
- package/package.json +19 -21
- package/src/scripts/build-tokens.mjs +391 -0
- package/src/scripts/compile-primitives.js +165 -0
- package/src/scripts/compile-theme.js +117 -0
- package/src/{tokens/build → scripts}/css-vars.js +10 -9
- package/src/{tokens/build → scripts}/resolve.js +2 -0
- package/src/tests/primitives.html +293 -0
- package/src/tests/theme.html +310 -0
- package/src/tests/tokens.html +84 -0
- package/src/tokens/README.md +306 -272
- package/src/tokens/index.js +4 -12
- package/src/tokens/primitives/animations/animate.js +8 -0
- package/src/tokens/primitives/animations/ease.js +7 -0
- package/src/tokens/primitives/border-widths.js +9 -0
- package/src/tokens/primitives/breakpoints.js +9 -0
- package/src/tokens/primitives/colors/alpha.js +104 -0
- package/src/tokens/primitives/colors/brand.js +40 -0
- package/src/tokens/primitives/colors/colors.js +142 -0
- package/src/tokens/primitives/effects/blur.js +11 -0
- package/src/tokens/primitives/effects/drop-shadow.js +10 -0
- package/src/tokens/primitives/effects/inset-shadow.js +7 -0
- package/src/tokens/primitives/effects/opacity.js +8 -0
- package/src/tokens/primitives/effects/perspective.js +9 -0
- package/src/tokens/primitives/effects/shadow.js +11 -0
- package/src/tokens/primitives/ring-offset.js +5 -0
- package/src/tokens/primitives/shape/aspect-video.js +3 -0
- package/src/tokens/primitives/shape/container.js +17 -0
- package/src/tokens/primitives/shape/height.js +32 -0
- package/src/tokens/primitives/shape/radius.js +13 -0
- package/src/tokens/primitives/shape/shape.js +12 -0
- package/src/tokens/primitives/shape/size.js +32 -0
- package/src/tokens/primitives/shape/spacing.js +33 -0
- package/src/tokens/primitives/shape/width.js +19 -0
- package/src/tokens/primitives/typography/font-family.js +7 -0
- package/src/tokens/primitives/typography/font-size.js +30 -0
- package/src/tokens/primitives/typography/font-weight.js +13 -0
- package/src/tokens/primitives/typography/leading.js +9 -0
- package/src/tokens/primitives/typography/line-height.js +13 -0
- package/src/tokens/primitives/typography/tracking.js +10 -0
- package/src/{tailwind/semantic-animations-plugin.js → tokens/semantic/animations.js} +3 -21
- package/src/tokens/semantic/colors.js +138 -0
- package/src/tokens/semantic/containers.data.js +18 -0
- package/src/tokens/semantic/containers.js +70 -0
- package/src/tokens/semantic/spacings.data.js +18 -0
- package/src/tokens/semantic/spacings.js +163 -0
- package/src/tokens/semantic/texts.data.js +103 -0
- package/src/{tailwind/semantic-texts-plugin.js → tokens/semantic/texts.js} +91 -70
- package/src/tokens/theme/accent.js +18 -0
- package/src/tokens/theme/background.js +32 -0
- package/src/tokens/theme/border.js +18 -0
- package/src/tokens/theme/feedback/danger.js +16 -0
- package/src/tokens/theme/feedback/info.js +16 -0
- package/src/tokens/theme/feedback/success.js +16 -0
- package/src/tokens/theme/feedback/warning.js +16 -0
- package/src/tokens/theme/primary.js +18 -0
- package/src/tokens/theme/ring.js +12 -0
- package/src/tokens/theme/secondary.js +18 -0
- package/src/tokens/theme/surfaces.js +23 -0
- package/src/tokens/theme/text.js +18 -0
- package/src/tokens/theme.js +32 -0
- package/colors-brand.js +0 -24
- package/src/tailwind/semantic-colors-plugin.js +0 -91
- package/src/tailwind/semantic-spacing-plugin.js +0 -151
- package/src/tailwind/tailwind-theme.js +0 -54
- package/src/tokens/build/preset.js +0 -62
- package/src/tokens/build/tailwind-flat-colors.js +0 -75
- package/src/tokens/build/tailwind-helper.js +0 -12
- package/src/tokens/build/tailwind-plugin.js +0 -175
- package/src/tokens/colors-brand.js +0 -24
- package/src/tokens/primitives/brand.js +0 -69
- package/src/tokens/primitives/colors.js +0 -144
- package/src/tokens/semantic/backgrounds.js +0 -46
- package/src/tokens/semantic/borders.js +0 -44
- package/src/tokens/semantic/text.js +0 -48
- /package/src/{tokens/build → scripts}/refs.js +0 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A comprehensive design token system and theming solution for Azion's web applica
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Design Tokens**:
|
|
7
|
+
- **Design Tokens**: Primitives and semantic colors tokens generated from Figma;
|
|
8
8
|
- **Brand Colors**: Azion's official color palette with primary (orange), accent (violet), and surface colors;
|
|
9
9
|
- **Theme Support**: Built-in light and dark mode theming;
|
|
10
10
|
- **Tailwind Integration**: Plugin and preset for seamless Tailwind CSS integration;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aziontech/theme",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"author": "aziontech",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"pack:dry": "npm pack --dry-run",
|
|
14
|
-
"format": "prettier --write scripts/ src/"
|
|
14
|
+
"format": "prettier --write scripts/ src/",
|
|
15
|
+
"build:tokens": "node src/scripts/build-tokens.mjs",
|
|
16
|
+
"build:tokens:v3": "node src/scripts/build-tokens.mjs --target=v3",
|
|
17
|
+
"build:tokens:v4": "node src/scripts/build-tokens.mjs --target=v4",
|
|
18
|
+
"build:css:v3": "npm run build:tokens:v3 && tailwindcss -c dist/v3/tailwind.config.js -i dist/v3/globals.css -o dist/v3/output.css",
|
|
19
|
+
"test:tokens:v3": "npm run build:css:v3 && open src/tests/tokens.html"
|
|
15
20
|
},
|
|
16
21
|
"publishConfig": {
|
|
17
22
|
"access": "public"
|
|
@@ -40,30 +45,23 @@
|
|
|
40
45
|
},
|
|
41
46
|
"files": [
|
|
42
47
|
"src/",
|
|
48
|
+
"dist/",
|
|
43
49
|
"LICENSE",
|
|
44
|
-
"colors-brand.js",
|
|
45
50
|
"default.js",
|
|
46
51
|
"tokens.js",
|
|
47
|
-
"widget.js"
|
|
48
|
-
"tailwind-theme.js"
|
|
52
|
+
"widget.js"
|
|
49
53
|
],
|
|
50
54
|
"exports": {
|
|
51
55
|
".": "./default.js",
|
|
52
|
-
"./
|
|
53
|
-
"./
|
|
54
|
-
"./
|
|
55
|
-
"./
|
|
56
|
-
"./
|
|
57
|
-
"./
|
|
58
|
-
"./
|
|
59
|
-
"./
|
|
60
|
-
"./
|
|
61
|
-
"./
|
|
62
|
-
"./tokens/build/css-vars.js": "./src/tokens/build/css-vars.js",
|
|
63
|
-
"./tailwind/tailwind-theme": "./src/tailwind/tailwind-theme.js",
|
|
64
|
-
"./tailwind/semantic-colors-plugin": "./src/tailwind/semantic-colors-plugin.js",
|
|
65
|
-
"./tailwind/semantic-texts-plugin": "./src/tailwind/semantic-texts-plugin.js",
|
|
66
|
-
"./tailwind/semantic-spacing-plugin": "./src/tailwind/semantic-spacing-plugin.js",
|
|
67
|
-
"./tailwind/semantic-animations-plugin": "./src/tailwind/semantic-animations-plugin.js"
|
|
56
|
+
"./scripts/compile-primitives": "./src/scripts/compile-primitives.js",
|
|
57
|
+
"./scripts/compile-theme": "./src/scripts/compile-theme.js",
|
|
58
|
+
"./tailwind-preset": "./dist/v3/tailwind-preset.js",
|
|
59
|
+
"./tailwind-preset/v3": "./dist/v3/tailwind-preset.js",
|
|
60
|
+
"./globals.css": "./dist/v3/globals.css",
|
|
61
|
+
"./globals.scss": "./dist/v3/globals.scss",
|
|
62
|
+
"./v3/globals.css": "./dist/v3/globals.css",
|
|
63
|
+
"./v3/globals.scss": "./dist/v3/globals.scss",
|
|
64
|
+
"./v4/globals.css": "./dist/v4/globals.css",
|
|
65
|
+
"./v4/globals.scss": "./dist/v4/globals.scss"
|
|
68
66
|
}
|
|
69
67
|
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build design tokens into self-contained stylesheets and Tailwind configs.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node build-tokens.mjs # emit both v3 and v4
|
|
7
|
+
* node build-tokens.mjs --target=v3 # emit dist/v3/ only
|
|
8
|
+
* node build-tokens.mjs --target=v4 # emit dist/v4/ only
|
|
9
|
+
*
|
|
10
|
+
* v3 output (legacy preset model):
|
|
11
|
+
* - dist/v3/globals.css @tailwind directives + @layer + responsive vars
|
|
12
|
+
* - dist/v3/globals.scss identical bytes, .scss extension
|
|
13
|
+
* - dist/v3/tailwind.config.js full CJS config (content + theme.extend)
|
|
14
|
+
* - dist/v3/tailwind-preset.js ESM preset (theme.extend only)
|
|
15
|
+
*
|
|
16
|
+
* v4 output (CSS-first):
|
|
17
|
+
* - dist/v4/globals.css @import "tailwindcss" + @theme + :root @media
|
|
18
|
+
* - dist/v4/globals.scss identical bytes
|
|
19
|
+
*
|
|
20
|
+
* Shared schema: primitives come from `tokens/primitives/**` (already
|
|
21
|
+
* declarative). Semantic responsive tokens come from
|
|
22
|
+
* `tokens/semantic/{containers,spacings,texts}.data.js`.
|
|
23
|
+
*
|
|
24
|
+
* No external deps — node:fs/promises, node:path, node:url only.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
28
|
+
import { dirname, resolve } from 'node:path';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
|
|
31
|
+
import { breakpoints } from '../tokens/primitives/breakpoints.js';
|
|
32
|
+
import { buildTrees, flatten } from './compile-primitives.js';
|
|
33
|
+
import { containersData } from '../tokens/semantic/containers.data.js';
|
|
34
|
+
import { spacingsData } from '../tokens/semantic/spacings.data.js';
|
|
35
|
+
import { textsData } from '../tokens/semantic/texts.data.js';
|
|
36
|
+
import { semanticColors } from '../tokens/semantic/colors.js';
|
|
37
|
+
import { createCssVars } from './css-vars.js';
|
|
38
|
+
|
|
39
|
+
const BREAKPOINT_ORDER = ['sm', 'md', 'lg', 'xl', '2xl'];
|
|
40
|
+
const V3_SELECTOR = ':root, [data-theme=light], .azion.azion-light';
|
|
41
|
+
|
|
42
|
+
const kebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
43
|
+
|
|
44
|
+
// ─── 1. Primitives ──────────────────────────────────────────────────────────
|
|
45
|
+
const flattenPrimitives = () => {
|
|
46
|
+
const { refsTree, varsTree } = buildTrees();
|
|
47
|
+
return flatten(varsTree, refsTree);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ─── 2. Semantic flattening ─────────────────────────────────────────────────
|
|
51
|
+
const splitResponsive = (value) => {
|
|
52
|
+
if (value === null || typeof value !== 'object') return { _: String(value) };
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const [bp, v] of Object.entries(value)) out[bp] = String(v);
|
|
55
|
+
return out;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const flattenSingleValue = (data, varNameOf) => {
|
|
59
|
+
const byBp = {};
|
|
60
|
+
for (const [key, val] of Object.entries(data)) {
|
|
61
|
+
for (const [bp, v] of Object.entries(splitResponsive(val))) {
|
|
62
|
+
(byBp[bp] ||= {})[varNameOf(key)] = v;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return byBp;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const flattenBundle = (data) => {
|
|
69
|
+
const byBp = {};
|
|
70
|
+
for (const [key, bundle] of Object.entries(data)) {
|
|
71
|
+
for (const [prop, val] of Object.entries(bundle)) {
|
|
72
|
+
const varName = `--${key}-${kebab(prop)}`;
|
|
73
|
+
for (const [bp, v] of Object.entries(splitResponsive(val))) {
|
|
74
|
+
(byBp[bp] ||= {})[varName] = v;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return byBp;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const SEMANTIC_PREFIXES = ['--text-', '--background-', '--border-'];
|
|
82
|
+
const isSemanticVar = (key) => SEMANTIC_PREFIXES.some((p) => key.startsWith(p));
|
|
83
|
+
const pickSemantic = (vars) =>
|
|
84
|
+
Object.fromEntries(Object.entries(vars).filter(([k]) => isSemanticVar(k)));
|
|
85
|
+
|
|
86
|
+
const buildSemanticColorVars = () => {
|
|
87
|
+
const { light, dark } = createCssVars();
|
|
88
|
+
return { light: pickSemantic(light), dark: pickSemantic(dark) };
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const buildFlatModel = () => ({
|
|
92
|
+
primitives: flattenPrimitives(),
|
|
93
|
+
containers: flattenSingleValue(containersData, (k) => `--container-${k}`),
|
|
94
|
+
spacings: flattenSingleValue(spacingsData, (k) => `--${k}`),
|
|
95
|
+
texts: flattenBundle(textsData),
|
|
96
|
+
semanticColors: buildSemanticColorVars(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ─── 3. Shared formatting helpers ──────────────────────────────────────────
|
|
100
|
+
const formatVars = (vars, indent) =>
|
|
101
|
+
Object.entries(vars)
|
|
102
|
+
.map(([k, v]) => `${indent}${k}: ${v};`)
|
|
103
|
+
.join('\n');
|
|
104
|
+
|
|
105
|
+
/** Component classes (.text-*, .gap-*, .px-container) — identical in v3/v4. */
|
|
106
|
+
const emitComponentClasses = (indent = ' ') => {
|
|
107
|
+
const lines = [];
|
|
108
|
+
|
|
109
|
+
lines.push(`${indent}/* ── Containers ── */`);
|
|
110
|
+
lines.push(`${indent}.px-container { padding-left: var(--container-px); padding-right: var(--container-px); }`);
|
|
111
|
+
lines.push(`${indent}.py-container { padding-top: var(--container-py); padding-bottom: var(--container-py); }`);
|
|
112
|
+
lines.push(`${indent}.max-container-width { max-width: var(--container-max-width); }`);
|
|
113
|
+
|
|
114
|
+
lines.push('');
|
|
115
|
+
lines.push(`${indent}/* ── Spacings ── */`);
|
|
116
|
+
for (const key of Object.keys(spacingsData)) {
|
|
117
|
+
lines.push(`${indent}.gap-${key} { gap: var(--${key}); }`);
|
|
118
|
+
lines.push(`${indent}.p-${key} { padding: var(--${key}); }`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
lines.push('');
|
|
122
|
+
lines.push(`${indent}/* ── Texts ── */`);
|
|
123
|
+
for (const [key, bundle] of Object.entries(textsData)) {
|
|
124
|
+
const decls = Object.keys(bundle)
|
|
125
|
+
.map((prop) => `${kebab(prop)}: var(--${key}-${kebab(prop)});`)
|
|
126
|
+
.join(' ');
|
|
127
|
+
lines.push(`${indent}.${key} { ${decls} }`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ─── 4. v3 emission (@tailwind directives + @layer) ────────────────────────
|
|
134
|
+
const emitMediaBlockV3 = (bp, vars) =>
|
|
135
|
+
[
|
|
136
|
+
` @media (min-width: ${breakpoints[bp]}) {`,
|
|
137
|
+
` ${V3_SELECTOR} {`,
|
|
138
|
+
formatVars(vars, ' '),
|
|
139
|
+
` }`,
|
|
140
|
+
` }`,
|
|
141
|
+
].join('\n');
|
|
142
|
+
|
|
143
|
+
const V3_DARK_SELECTOR = '[data-theme=dark], .dark, .azion.azion-dark';
|
|
144
|
+
|
|
145
|
+
const emitCssV3 = () => {
|
|
146
|
+
const m = buildFlatModel();
|
|
147
|
+
|
|
148
|
+
const sections = [
|
|
149
|
+
{ title: 'Primitives', vars: m.primitives },
|
|
150
|
+
{ title: 'Containers', vars: m.containers._ || {} },
|
|
151
|
+
{ title: 'Spacings', vars: m.spacings._ || {} },
|
|
152
|
+
{ title: 'Texts', vars: m.texts._ || {} },
|
|
153
|
+
{ title: 'Semantic colors', vars: m.semanticColors.light },
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
const baseBody = sections
|
|
157
|
+
.filter((s) => Object.keys(s.vars).length > 0)
|
|
158
|
+
.map((s) => ` /* ── ${s.title} ── */\n${formatVars(s.vars, ' ')}`)
|
|
159
|
+
.join('\n\n');
|
|
160
|
+
|
|
161
|
+
const mediaBlocks = [];
|
|
162
|
+
for (const bp of BREAKPOINT_ORDER) {
|
|
163
|
+
const merged = {
|
|
164
|
+
...(m.containers[bp] || {}),
|
|
165
|
+
...(m.spacings[bp] || {}),
|
|
166
|
+
...(m.texts[bp] || {}),
|
|
167
|
+
};
|
|
168
|
+
if (Object.keys(merged).length > 0) mediaBlocks.push(emitMediaBlockV3(bp, merged));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [
|
|
172
|
+
'/* generated by build-tokens.mjs — target: v3 — do not edit */',
|
|
173
|
+
'',
|
|
174
|
+
'@tailwind base;',
|
|
175
|
+
'@tailwind components;',
|
|
176
|
+
'@tailwind utilities;',
|
|
177
|
+
'',
|
|
178
|
+
'@layer base {',
|
|
179
|
+
` ${V3_SELECTOR} {`,
|
|
180
|
+
baseBody,
|
|
181
|
+
' }',
|
|
182
|
+
'',
|
|
183
|
+
mediaBlocks.join('\n\n'),
|
|
184
|
+
'}',
|
|
185
|
+
'',
|
|
186
|
+
'@layer base {',
|
|
187
|
+
` ${V3_DARK_SELECTOR} {`,
|
|
188
|
+
` /* ── Semantic colors (dark) ── */`,
|
|
189
|
+
formatVars(m.semanticColors.dark, ' '),
|
|
190
|
+
' }',
|
|
191
|
+
'}',
|
|
192
|
+
'',
|
|
193
|
+
'@layer components {',
|
|
194
|
+
emitComponentClasses(),
|
|
195
|
+
'}',
|
|
196
|
+
'',
|
|
197
|
+
].join('\n');
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const treeToVars = (tree, prefix) => {
|
|
201
|
+
const out = {};
|
|
202
|
+
for (const [key, val] of Object.entries(tree)) {
|
|
203
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
204
|
+
out[key] = treeToVars(val, `${prefix}-${key}`);
|
|
205
|
+
} else {
|
|
206
|
+
out[key] = `var(${prefix}-${key})`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const buildPresetData = () => {
|
|
213
|
+
const { refsTree } = buildTrees();
|
|
214
|
+
const colors = treeToVars(refsTree.color, '--color');
|
|
215
|
+
const fontWeight = refsTree.typography?.['font-weight']
|
|
216
|
+
? treeToVars(refsTree.typography['font-weight'], '--font-weight')
|
|
217
|
+
: {};
|
|
218
|
+
const dropShadow = refsTree.effects?.['drop-shadow']
|
|
219
|
+
? treeToVars(refsTree.effects['drop-shadow'], '--drop-shadow')
|
|
220
|
+
: {};
|
|
221
|
+
const lineHeight = refsTree.typography?.leading
|
|
222
|
+
? treeToVars(refsTree.typography.leading, '--leading')
|
|
223
|
+
: {};
|
|
224
|
+
const letterSpacing = refsTree.typography?.tracking
|
|
225
|
+
? treeToVars(refsTree.typography.tracking, '--tracking')
|
|
226
|
+
: {};
|
|
227
|
+
|
|
228
|
+
const fontSize = {};
|
|
229
|
+
for (const [key, bundle] of Object.entries(textsData)) {
|
|
230
|
+
if (!('fontSize' in bundle)) continue;
|
|
231
|
+
const meta = {};
|
|
232
|
+
if ('lineHeight' in bundle) meta.lineHeight = `var(--${key}-line-height)`;
|
|
233
|
+
if ('letterSpacing' in bundle) meta.letterSpacing = `var(--${key}-letter-spacing)`;
|
|
234
|
+
if ('fontWeight' in bundle) meta.fontWeight = `var(--${key}-font-weight)`;
|
|
235
|
+
fontSize[key.replace(/^text-/, '')] = [`var(--${key}-font-size)`, meta];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
theme: {
|
|
240
|
+
extend: {
|
|
241
|
+
colors,
|
|
242
|
+
fontSize,
|
|
243
|
+
fontWeight,
|
|
244
|
+
dropShadow,
|
|
245
|
+
lineHeight,
|
|
246
|
+
letterSpacing,
|
|
247
|
+
textColor: semanticColors.text,
|
|
248
|
+
backgroundColor: semanticColors.background,
|
|
249
|
+
borderColor: semanticColors.border,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const emitPresetV3 = () => {
|
|
256
|
+
const data = buildPresetData();
|
|
257
|
+
return [
|
|
258
|
+
'/* generated by build-tokens.mjs — target: v3 — do not edit */',
|
|
259
|
+
`const preset = ${JSON.stringify(data, null, 2)};`,
|
|
260
|
+
'export default preset;',
|
|
261
|
+
'',
|
|
262
|
+
].join('\n');
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const emitTailwindConfigV3 = () => {
|
|
266
|
+
const data = buildPresetData();
|
|
267
|
+
const config = {
|
|
268
|
+
content: ['./src/tests/**/*.html'],
|
|
269
|
+
darkMode: ['class', '[data-theme="dark"]'],
|
|
270
|
+
theme: data.theme,
|
|
271
|
+
plugins: [],
|
|
272
|
+
};
|
|
273
|
+
return [
|
|
274
|
+
'/* generated by build-tokens.mjs — target: v3 — do not edit */',
|
|
275
|
+
`/** @type {import('tailwindcss').Config} */`,
|
|
276
|
+
`module.exports = ${JSON.stringify(config, null, 2)};`,
|
|
277
|
+
'',
|
|
278
|
+
].join('\n');
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// ─── 5. v4 emission (@import "tailwindcss" + @theme) ───────────────────────
|
|
282
|
+
/**
|
|
283
|
+
* v4 puts color primitives under `@theme` so Tailwind auto-generates
|
|
284
|
+
* `bg-gray-50`, `text-violet-500`, etc. Semantic tokens stay in plain `:root`
|
|
285
|
+
* (with `@media` overrides) so we keep full control of the component classes.
|
|
286
|
+
*
|
|
287
|
+
* Notes on v4 conventions used:
|
|
288
|
+
* - `@theme { --color-* }` → color utilities.
|
|
289
|
+
* - `@theme { --breakpoint-* }` → screens.
|
|
290
|
+
* - Semantic shape/typography tokens are NOT in @theme — we own their
|
|
291
|
+
* class definitions in @layer components and they swap responsively
|
|
292
|
+
* via `:root @media` overrides.
|
|
293
|
+
*/
|
|
294
|
+
const emitCssV4 = () => {
|
|
295
|
+
const m = buildFlatModel();
|
|
296
|
+
const primitivesEntries = Object.entries(m.primitives);
|
|
297
|
+
|
|
298
|
+
// Split primitives: color/breakpoint go in @theme; everything else (shape,
|
|
299
|
+
// typography, effects, border, ring-offset) stays in :root.
|
|
300
|
+
const themeVars = {};
|
|
301
|
+
const rootPrimitiveVars = {};
|
|
302
|
+
for (const [k, v] of primitivesEntries) {
|
|
303
|
+
if (k.startsWith('--color-') || k.startsWith('--breakpoint-')) themeVars[k] = v;
|
|
304
|
+
else rootPrimitiveVars[k] = v;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const semanticBase = {
|
|
308
|
+
...rootPrimitiveVars,
|
|
309
|
+
...(m.containers._ || {}),
|
|
310
|
+
...(m.spacings._ || {}),
|
|
311
|
+
...(m.texts._ || {}),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const mediaBlocks = [];
|
|
315
|
+
for (const bp of BREAKPOINT_ORDER) {
|
|
316
|
+
const merged = {
|
|
317
|
+
...(m.containers[bp] || {}),
|
|
318
|
+
...(m.spacings[bp] || {}),
|
|
319
|
+
...(m.texts[bp] || {}),
|
|
320
|
+
};
|
|
321
|
+
if (Object.keys(merged).length === 0) continue;
|
|
322
|
+
mediaBlocks.push(
|
|
323
|
+
[
|
|
324
|
+
` @media (min-width: ${breakpoints[bp]}) {`,
|
|
325
|
+
formatVars(merged, ' '),
|
|
326
|
+
` }`,
|
|
327
|
+
].join('\n'),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return [
|
|
332
|
+
'/* generated by build-tokens.mjs — target: v4 — do not edit */',
|
|
333
|
+
'',
|
|
334
|
+
'@import "tailwindcss";',
|
|
335
|
+
'',
|
|
336
|
+
'@theme {',
|
|
337
|
+
formatVars(themeVars, ' '),
|
|
338
|
+
'}',
|
|
339
|
+
'',
|
|
340
|
+
':root, [data-theme=light], .azion.azion-light {',
|
|
341
|
+
formatVars(semanticBase, ' '),
|
|
342
|
+
'',
|
|
343
|
+
mediaBlocks.join('\n\n'),
|
|
344
|
+
'}',
|
|
345
|
+
'',
|
|
346
|
+
'@layer components {',
|
|
347
|
+
emitComponentClasses(),
|
|
348
|
+
'}',
|
|
349
|
+
'',
|
|
350
|
+
].join('\n');
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// ─── 6. Write to disk ──────────────────────────────────────────────────────
|
|
354
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
355
|
+
const distRoot = resolve(__dirname, '../../dist');
|
|
356
|
+
|
|
357
|
+
const writeV3 = async () => {
|
|
358
|
+
const dir = resolve(distRoot, 'v3');
|
|
359
|
+
await mkdir(dir, { recursive: true });
|
|
360
|
+
const css = emitCssV3();
|
|
361
|
+
await writeFile(resolve(dir, 'globals.css'), css, 'utf8');
|
|
362
|
+
await writeFile(resolve(dir, 'globals.scss'), css, 'utf8');
|
|
363
|
+
await writeFile(resolve(dir, 'tailwind-preset.js'), emitPresetV3(), 'utf8');
|
|
364
|
+
await writeFile(resolve(dir, 'tailwind.config.js'), emitTailwindConfigV3(), 'utf8');
|
|
365
|
+
console.log(`✓ v3 → ${dir}`);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const writeV4 = async () => {
|
|
369
|
+
const dir = resolve(distRoot, 'v4');
|
|
370
|
+
await mkdir(dir, { recursive: true });
|
|
371
|
+
const css = emitCssV4();
|
|
372
|
+
await writeFile(resolve(dir, 'globals.css'), css, 'utf8');
|
|
373
|
+
await writeFile(resolve(dir, 'globals.scss'), css, 'utf8');
|
|
374
|
+
console.log(`✓ v4 → ${dir}`);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// ─── 7. CLI ────────────────────────────────────────────────────────────────
|
|
378
|
+
const parseTarget = () => {
|
|
379
|
+
const arg = process.argv.find((a) => a.startsWith('--target='));
|
|
380
|
+
if (!arg) return 'all';
|
|
381
|
+
const value = arg.slice('--target='.length).toLowerCase();
|
|
382
|
+
if (!['v3', 'v4', 'all'].includes(value)) {
|
|
383
|
+
console.error(`Unknown --target=${value}. Use v3, v4, or all.`);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
return value;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const target = parseTarget();
|
|
390
|
+
if (target === 'v3' || target === 'all') await writeV3();
|
|
391
|
+
if (target === 'v4' || target === 'all') await writeV4();
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile all primitives into a single CSS variables stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* Imports every primitive module (colors, shape, typography, effects,
|
|
5
|
+
* breakpoints, border-widths, ring-offset), resolves `tokenRef(...)` aliases,
|
|
6
|
+
* and produces a `:root { ... }` block. Mirrors the `injectCssVars` helper
|
|
7
|
+
* but for primitives only — no semantic light/dark split.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { primitives as colorPrimitives } from '../tokens/primitives/colors/colors.js';
|
|
11
|
+
import { animate } from '../tokens/primitives/animations/animate.js';
|
|
12
|
+
import { ease } from '../tokens/primitives/animations/ease.js';
|
|
13
|
+
import { borderWidths } from '../tokens/primitives/border-widths.js';
|
|
14
|
+
import { breakpoints } from '../tokens/primitives/breakpoints.js';
|
|
15
|
+
import { blur } from '../tokens/primitives/effects/blur.js';
|
|
16
|
+
import { dropShadow } from '../tokens/primitives/effects/drop-shadow.js';
|
|
17
|
+
import { insetShadow } from '../tokens/primitives/effects/inset-shadow.js';
|
|
18
|
+
import { opacity } from '../tokens/primitives/effects/opacity.js';
|
|
19
|
+
import { perspective } from '../tokens/primitives/effects/perspective.js';
|
|
20
|
+
import { shadow } from '../tokens/primitives/effects/shadow.js';
|
|
21
|
+
import { ringOffset } from '../tokens/primitives/ring-offset.js';
|
|
22
|
+
import { aspectVideo } from '../tokens/primitives/shape/aspect-video.js';
|
|
23
|
+
import { container } from '../tokens/primitives/shape/container.js';
|
|
24
|
+
import { height } from '../tokens/primitives/shape/height.js';
|
|
25
|
+
import { radius } from '../tokens/primitives/shape/radius.js';
|
|
26
|
+
import { shape as shapeAliases } from '../tokens/primitives/shape/shape.js';
|
|
27
|
+
import { size } from '../tokens/primitives/shape/size.js';
|
|
28
|
+
import { spacing } from '../tokens/primitives/shape/spacing.js';
|
|
29
|
+
import { width } from '../tokens/primitives/shape/width.js';
|
|
30
|
+
import { fontFamily } from '../tokens/primitives/typography/font-family.js';
|
|
31
|
+
import { fontSize } from '../tokens/primitives/typography/font-size.js';
|
|
32
|
+
import { fontWeight } from '../tokens/primitives/typography/font-weight.js';
|
|
33
|
+
import { leading } from '../tokens/primitives/typography/leading.js';
|
|
34
|
+
import { lineHeight } from '../tokens/primitives/typography/line-height.js';
|
|
35
|
+
import { tracking } from '../tokens/primitives/typography/tracking.js';
|
|
36
|
+
import { isTokenRef } from './refs.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns two trees:
|
|
40
|
+
* - `refsTree`: deep structure preserved so `tokenRef('shape.container.X')`
|
|
41
|
+
* keeps resolving via path lookup.
|
|
42
|
+
* - `varsTree`: same data with the `shape` / `typography` / `effects`
|
|
43
|
+
* wrappers removed, so emitted CSS vars are flat (no group prefix).
|
|
44
|
+
*/
|
|
45
|
+
export const buildTrees = () => {
|
|
46
|
+
const { brand, ...restColors } = colorPrimitives;
|
|
47
|
+
const color = {
|
|
48
|
+
...restColors,
|
|
49
|
+
primary: brand.primary,
|
|
50
|
+
accent: brand.accent,
|
|
51
|
+
absolute: brand.absolute,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const shape = {
|
|
55
|
+
container,
|
|
56
|
+
height,
|
|
57
|
+
radius,
|
|
58
|
+
size,
|
|
59
|
+
spacing,
|
|
60
|
+
width,
|
|
61
|
+
'aspect-video': aspectVideo,
|
|
62
|
+
...shapeAliases,
|
|
63
|
+
};
|
|
64
|
+
const typography = {
|
|
65
|
+
font: fontFamily,
|
|
66
|
+
text: fontSize,
|
|
67
|
+
'font-weight': fontWeight,
|
|
68
|
+
leading,
|
|
69
|
+
'line-height': lineHeight,
|
|
70
|
+
tracking,
|
|
71
|
+
};
|
|
72
|
+
const effects = {
|
|
73
|
+
blur,
|
|
74
|
+
'drop-shadow': dropShadow,
|
|
75
|
+
shadow,
|
|
76
|
+
'inset-shadow': insetShadow,
|
|
77
|
+
opacity,
|
|
78
|
+
perspective,
|
|
79
|
+
};
|
|
80
|
+
const animations = { ease, animate };
|
|
81
|
+
|
|
82
|
+
const refsTree = {
|
|
83
|
+
color,
|
|
84
|
+
breakpoint: breakpoints,
|
|
85
|
+
border: borderWidths,
|
|
86
|
+
shape,
|
|
87
|
+
typography,
|
|
88
|
+
effects,
|
|
89
|
+
animations,
|
|
90
|
+
'ring-offset': ringOffset['ring-offset'],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const varsTree = {
|
|
94
|
+
color,
|
|
95
|
+
breakpoint: breakpoints,
|
|
96
|
+
border: borderWidths,
|
|
97
|
+
...shape,
|
|
98
|
+
...typography,
|
|
99
|
+
...effects,
|
|
100
|
+
...animations,
|
|
101
|
+
'ring-offset': ringOffset['ring-offset'],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return { refsTree, varsTree };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const getValueByPath = (obj, path) =>
|
|
108
|
+
path.split('.').reduce((acc, key) => {
|
|
109
|
+
if (acc && typeof acc === 'object' && key in acc) return acc[key];
|
|
110
|
+
return undefined;
|
|
111
|
+
}, obj);
|
|
112
|
+
|
|
113
|
+
const resolveRef = (refPath, refsTree) => {
|
|
114
|
+
const direct = getValueByPath(refsTree, refPath);
|
|
115
|
+
if (typeof direct === 'string' || typeof direct === 'number') return String(direct);
|
|
116
|
+
return null;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// A leaf key of `DEFAULT` collapses to the parent prefix (Tailwind convention),
|
|
120
|
+
// so `{ radius: { DEFAULT: '4px' } }` emits `--radius: 4px;`.
|
|
121
|
+
const varNameFor = (path) => {
|
|
122
|
+
const segments = path[path.length - 1] === 'DEFAULT' ? path.slice(0, -1) : path;
|
|
123
|
+
return `--${segments.join('-')}`;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const flatten = (obj, refsTree, prefix = []) => {
|
|
127
|
+
const result = {};
|
|
128
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
129
|
+
const nextPath = [...prefix, key];
|
|
130
|
+
if (isTokenRef(value)) {
|
|
131
|
+
const resolved = resolveRef(value.__ref, refsTree);
|
|
132
|
+
result[varNameFor(nextPath)] = resolved ?? value.__ref;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
136
|
+
Object.assign(result, flatten(value, refsTree, nextPath));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
140
|
+
result[varNameFor(nextPath)] = String(value);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return result;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const compilePrimitivesVars = () => {
|
|
147
|
+
const { refsTree, varsTree } = buildTrees();
|
|
148
|
+
return flatten(varsTree, refsTree);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const compilePrimitivesCss = () => {
|
|
152
|
+
const vars = compilePrimitivesVars();
|
|
153
|
+
const body = Object.entries(vars)
|
|
154
|
+
.map(([k, v]) => ` ${k}: ${v};`)
|
|
155
|
+
.join('\n');
|
|
156
|
+
return `:root, [data-theme=light], .azion.azion-light {\n${body}\n}`;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const injectPrimitivesCss = () => {
|
|
160
|
+
const style = document.createElement('style');
|
|
161
|
+
style.setAttribute('data-azion-primitives', 'true');
|
|
162
|
+
style.textContent = compilePrimitivesCss();
|
|
163
|
+
document.head.appendChild(style);
|
|
164
|
+
return style;
|
|
165
|
+
};
|