@aziontech/theme 1.13.0 → 2.0.1

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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/package.json +19 -21
  3. package/src/scripts/build-tokens.mjs +391 -0
  4. package/src/scripts/compile-primitives.js +165 -0
  5. package/src/scripts/compile-theme.js +117 -0
  6. package/src/{tokens/build → scripts}/css-vars.js +10 -9
  7. package/src/{tokens/build → scripts}/resolve.js +2 -0
  8. package/src/tests/primitives.html +293 -0
  9. package/src/tests/theme.html +310 -0
  10. package/src/tests/tokens.html +84 -0
  11. package/src/tokens/README.md +306 -272
  12. package/src/tokens/index.js +4 -12
  13. package/src/tokens/primitives/animations/animate.js +26 -0
  14. package/src/tokens/primitives/animations/ease.js +7 -0
  15. package/src/tokens/primitives/border-widths.js +9 -0
  16. package/src/tokens/primitives/breakpoints.js +9 -0
  17. package/src/tokens/primitives/colors/alpha.js +104 -0
  18. package/src/tokens/primitives/colors/brand.js +40 -0
  19. package/src/tokens/primitives/colors/colors.js +142 -0
  20. package/src/tokens/primitives/effects/blur.js +11 -0
  21. package/src/tokens/primitives/effects/drop-shadow.js +10 -0
  22. package/src/tokens/primitives/effects/inset-shadow.js +7 -0
  23. package/src/tokens/primitives/effects/opacity.js +8 -0
  24. package/src/tokens/primitives/effects/perspective.js +9 -0
  25. package/src/tokens/primitives/effects/shadow.js +11 -0
  26. package/src/tokens/primitives/ring-offset.js +5 -0
  27. package/src/tokens/primitives/shape/aspect-video.js +3 -0
  28. package/src/tokens/primitives/shape/container.js +17 -0
  29. package/src/tokens/primitives/shape/height.js +32 -0
  30. package/src/tokens/primitives/shape/radius.js +13 -0
  31. package/src/tokens/primitives/shape/shape.js +12 -0
  32. package/src/tokens/primitives/shape/size.js +32 -0
  33. package/src/tokens/primitives/shape/spacing.js +33 -0
  34. package/src/tokens/primitives/shape/width.js +19 -0
  35. package/src/tokens/primitives/typography/font-family.js +7 -0
  36. package/src/tokens/primitives/typography/font-size.js +30 -0
  37. package/src/tokens/primitives/typography/font-weight.js +13 -0
  38. package/src/tokens/primitives/typography/leading.js +9 -0
  39. package/src/tokens/primitives/typography/line-height.js +13 -0
  40. package/src/tokens/primitives/typography/tracking.js +10 -0
  41. package/src/{tailwind/semantic-animations-plugin.js → tokens/semantic/animations.js} +19 -21
  42. package/src/tokens/semantic/colors.js +138 -0
  43. package/src/tokens/semantic/containers.data.js +18 -0
  44. package/src/tokens/semantic/containers.js +70 -0
  45. package/src/tokens/semantic/spacings.data.js +18 -0
  46. package/src/tokens/semantic/spacings.js +163 -0
  47. package/src/tokens/semantic/texts.data.js +103 -0
  48. package/src/{tailwind/semantic-texts-plugin.js → tokens/semantic/texts.js} +30 -9
  49. package/src/tokens/theme/accent.js +18 -0
  50. package/src/tokens/theme/background.js +32 -0
  51. package/src/tokens/theme/border.js +18 -0
  52. package/src/tokens/theme/feedback/danger.js +16 -0
  53. package/src/tokens/theme/feedback/info.js +16 -0
  54. package/src/tokens/theme/feedback/success.js +16 -0
  55. package/src/tokens/theme/feedback/warning.js +16 -0
  56. package/src/tokens/theme/primary.js +18 -0
  57. package/src/tokens/theme/ring.js +12 -0
  58. package/src/tokens/theme/secondary.js +18 -0
  59. package/src/tokens/theme/surfaces.js +23 -0
  60. package/src/tokens/theme/text.js +18 -0
  61. package/src/tokens/theme.js +32 -0
  62. package/colors-brand.js +0 -24
  63. package/src/tailwind/semantic-colors-plugin.js +0 -91
  64. package/src/tailwind/semantic-spacing-plugin.js +0 -151
  65. package/src/tailwind/tailwind-theme.js +0 -54
  66. package/src/tokens/build/preset.js +0 -62
  67. package/src/tokens/build/tailwind-flat-colors.js +0 -75
  68. package/src/tokens/build/tailwind-helper.js +0 -12
  69. package/src/tokens/build/tailwind-plugin.js +0 -175
  70. package/src/tokens/colors-brand.js +0 -24
  71. package/src/tokens/primitives/brand.js +0 -69
  72. package/src/tokens/primitives/colors.js +0 -144
  73. package/src/tokens/semantic/backgrounds.js +0 -46
  74. package/src/tokens/semantic/borders.js +0 -44
  75. package/src/tokens/semantic/text.js +0 -48
  76. /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**: Primitive and semantic color tokens generated from Figma;
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": "1.13.0",
4
+ "version": "2.0.1",
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
- "./widget": "./widget.js",
53
- "./tokens": "./tokens.js",
54
- "./tokens/primitive": "./tokens.js",
55
- "./tokens/brand": "./tokens.js",
56
- "./tokens/semantic/text": "./tokens.js",
57
- "./tokens/semantic/backgrounds": "./tokens.js",
58
- "./tokens/semantic/borders": "./tokens.js",
59
- "./tokens/build/preset": "./src/tokens/build/preset.js",
60
- "./tokens/build/tailwind-helper": "./src/tokens/build/tailwind-helper.js",
61
- "./tokens/build/tailwind-plugin": "./src/tokens/build/tailwind-plugin",
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
+ };