@aziontech/theme 4.2.0 → 4.3.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.
- package/README.md +110 -335
- package/dist/v4/globals.css +249 -72
- package/dist/v4/globals.scss +249 -72
- package/package.json +4 -13
- package/src/scripts/build-tokens.mjs +57 -7
- package/src/scripts/compile-primitives.js +9 -5
- package/src/scripts/compile-theme.js +12 -6
- package/src/scripts/refs.d.ts +1 -0
- package/src/scripts/refs.d.ts.map +1 -1
- package/src/scripts/refs.js +11 -0
- package/src/scripts/zero-unit.mjs +64 -0
- package/src/tokens/README.md +44 -29
- package/src/tokens/index.d.ts +0 -1
- package/src/tokens/index.js +0 -1
- package/src/tokens/primitives/animations/animate.d.ts +6 -0
- package/src/tokens/primitives/animations/animate.d.ts.map +1 -1
- package/src/tokens/primitives/animations/animate.js +14 -3
- package/src/tokens/primitives/animations/keyframes.d.ts +13 -1
- package/src/tokens/primitives/animations/keyframes.d.ts.map +1 -1
- package/src/tokens/primitives/animations/keyframes.js +23 -4
- package/src/tokens/primitives/colors/colors.d.ts +4 -0
- package/src/tokens/primitives/colors/colors.d.ts.map +1 -1
- package/src/tokens/primitives/colors/colors.js +9 -0
- package/src/tokens/primitives/shape/container.d.ts.map +1 -1
- package/src/tokens/primitives/shape/container.js +30 -13
- package/src/tokens/primitives/typography/leading.d.ts +1 -0
- package/src/tokens/primitives/typography/leading.js +1 -0
- package/src/tokens/primitives/typography/tracking.js +12 -6
- package/src/tokens/semantic/colors.js +5 -2
- package/src/tokens/semantic/layouts.data.d.ts +65 -0
- package/src/tokens/semantic/layouts.data.d.ts.map +1 -0
- package/src/tokens/semantic/layouts.data.js +282 -0
- package/src/tokens/semantic/texts.data.d.ts +28 -10
- package/src/tokens/semantic/texts.data.d.ts.map +1 -1
- package/src/tokens/semantic/texts.data.js +50 -30
- package/src/tokens/semantic/z-indices.data.d.ts +1 -1
- package/src/tokens/semantic/z-indices.data.js +1 -1
- package/src/tokens/theme/accent.js +18 -6
- package/src/tokens/theme/background.d.ts +12 -0
- package/src/tokens/theme/background.js +11 -2
- package/src/tokens/theme/border.js +26 -4
- package/src/tokens/theme/code-sintax.d.ts +6 -0
- package/src/tokens/theme/code-sintax.js +2 -0
- package/src/tokens/theme/feedback/info.js +2 -2
- package/src/tokens/theme/surfaces.d.ts +12 -0
- package/src/tokens/theme/surfaces.d.ts.map +1 -1
- package/src/tokens/theme/surfaces.js +4 -0
- package/src/tokens/theme/text.js +2 -2
- package/src/tokens/theme.d.ts +4 -0
- package/src/scripts/css-vars.d.ts +0 -7
- package/src/scripts/css-vars.d.ts.map +0 -1
- package/src/scripts/css-vars.js +0 -46
- package/src/scripts/resolve.d.ts +0 -5
- package/src/scripts/resolve.d.ts.map +0 -1
- package/src/scripts/resolve.js +0 -107
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* typography, breakpoints, animations, …)
|
|
15
15
|
* - `tokens/semantic/{containers,spacings,texts}.data.js`
|
|
16
16
|
* responsive-shaped semantic tokens
|
|
17
|
+
* - `tokens/semantic/layouts.data.js`
|
|
18
|
+
* layout tokens (all derived from --spacing-* /
|
|
19
|
+
* --container-*) + the `@utility` blocks that
|
|
20
|
+
* build the container system on them
|
|
17
21
|
* - `tokens/primitives/animations/{animate,keyframes}.js`
|
|
18
22
|
* animation utilities (`--animate-*`) + `@keyframes`
|
|
19
23
|
* definitions + extra CSS (transform-origin) for
|
|
@@ -26,25 +30,25 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
26
30
|
import { dirname, resolve } from 'node:path';
|
|
27
31
|
import { fileURLToPath } from 'node:url';
|
|
28
32
|
|
|
29
|
-
import { animate } from '../tokens/primitives/animations/animate.js';
|
|
33
|
+
import { animate, curve, duration } from '../tokens/primitives/animations/animate.js';
|
|
30
34
|
import { animateExtras, keyframes } from '../tokens/primitives/animations/keyframes.js';
|
|
31
35
|
import { breakpoints } from '../tokens/primitives/breakpoints.js';
|
|
32
|
-
import {
|
|
36
|
+
import { compilePrimitivesVars } from './compile-primitives.js';
|
|
33
37
|
import { compileThemeCss, compileThemeVars } from './compile-theme.js';
|
|
34
38
|
import { containersData } from '../tokens/semantic/containers.data.js';
|
|
39
|
+
import { layoutsData, layoutsUtilities } from '../tokens/semantic/layouts.data.js';
|
|
35
40
|
import { spacingsData } from '../tokens/semantic/spacings.data.js';
|
|
36
41
|
import { textsData } from '../tokens/semantic/texts.data.js';
|
|
37
42
|
import { zIndicesData } from '../tokens/semantic/z-indices.data.js';
|
|
43
|
+
import { assertNoZeroWithUnit } from './zero-unit.mjs';
|
|
38
44
|
|
|
39
45
|
const BREAKPOINT_ORDER = ['sm', 'md', 'lg', 'xl', '2xl'];
|
|
40
46
|
|
|
41
47
|
const kebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
42
48
|
|
|
43
49
|
// ─── 1. Primitives ──────────────────────────────────────────────────────────
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return flatten(varsTree, refsTree);
|
|
47
|
-
};
|
|
50
|
+
// compilePrimitivesVars throws on any unresolved tokenRef (see refs.js).
|
|
51
|
+
const flattenPrimitives = () => compilePrimitivesVars();
|
|
48
52
|
|
|
49
53
|
// ─── 2. Semantic flattening ─────────────────────────────────────────────────
|
|
50
54
|
const splitResponsive = (value) => {
|
|
@@ -93,6 +97,7 @@ const buildFlatModel = () => ({
|
|
|
93
97
|
containers: flattenSingleValue(containersData, (k) => `--container-${k}`),
|
|
94
98
|
spacings: flattenSingleValue(spacingsData, (k) => `--${k}`),
|
|
95
99
|
zIndices: flattenSingleValue(zIndicesData, (k) => `--${k}`),
|
|
100
|
+
layouts: flattenSingleValue(layoutsData, (k) => `--${k}`),
|
|
96
101
|
texts: flattenBundle(textsData),
|
|
97
102
|
});
|
|
98
103
|
|
|
@@ -235,6 +240,33 @@ const emitSemanticColorUtilities = () => {
|
|
|
235
240
|
* interactive element loses the hand cursor unless a component overrides it.
|
|
236
241
|
* Disabled controls keep the default cursor.
|
|
237
242
|
*/
|
|
243
|
+
/**
|
|
244
|
+
* Emit a map of `@utility` blocks. A string value is a plain declaration; an object
|
|
245
|
+
* value is a nested rule keyed by a literal selector (`&:first-child { … }`), the way
|
|
246
|
+
* the typography utilities nest their `&:hover`.
|
|
247
|
+
*/
|
|
248
|
+
const emitUtilities = (utilities) =>
|
|
249
|
+
Object.entries(utilities)
|
|
250
|
+
.map(([name, decls]) => {
|
|
251
|
+
const body = Object.entries(decls)
|
|
252
|
+
.map(([prop, value]) =>
|
|
253
|
+
typeof value === 'object'
|
|
254
|
+
? ` ${prop} { ${formatStateDecls(value)}; }`
|
|
255
|
+
: ` ${prop}: ${value};`,
|
|
256
|
+
)
|
|
257
|
+
.join('\n');
|
|
258
|
+
return `@utility ${name} {\n${body}\n}`;
|
|
259
|
+
})
|
|
260
|
+
.join('\n\n');
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* The container system as `@utility` blocks — same reason as the typography
|
|
264
|
+
* utilities: `@layer components` classes are opaque to the variant resolver, and a
|
|
265
|
+
* layout class is exactly the kind a consumer wants behind a variant
|
|
266
|
+
* (`md:layout-column`).
|
|
267
|
+
*/
|
|
268
|
+
const emitLayoutUtilities = () => emitUtilities(layoutsUtilities);
|
|
269
|
+
|
|
238
270
|
const emitBaseLayer = () =>
|
|
239
271
|
[
|
|
240
272
|
'@layer base {',
|
|
@@ -293,11 +325,25 @@ const emitCssV4 = () => {
|
|
|
293
325
|
themeVars[`--animate-${name}`] = shorthand;
|
|
294
326
|
}
|
|
295
327
|
|
|
328
|
+
// Named `duration-*` / `ease-*` utilities. Without these the design system's
|
|
329
|
+
// own `duration-moderate-02` / `ease-productive-entrance` classes compile to
|
|
330
|
+
// NOTHING (Tailwind v4 silently drops an unknown named step), and every
|
|
331
|
+
// component that declares one falls back to Tailwind's default 150ms `ease`.
|
|
332
|
+
// The namespaces are fixed by Tailwind: `--transition-duration-*` backs
|
|
333
|
+
// `duration-*`, `--ease-*` backs `ease-*`.
|
|
334
|
+
for (const [name, value] of Object.entries(duration)) {
|
|
335
|
+
themeVars[`--transition-duration-${name}`] = value;
|
|
336
|
+
}
|
|
337
|
+
for (const [name, value] of Object.entries(curve)) {
|
|
338
|
+
themeVars[`--ease-${name}`] = value;
|
|
339
|
+
}
|
|
340
|
+
|
|
296
341
|
const semanticBase = {
|
|
297
342
|
...rootPrimitiveVars,
|
|
298
343
|
...(m.containers._ || {}),
|
|
299
344
|
...(m.spacings._ || {}),
|
|
300
345
|
...(m.zIndices._ || {}),
|
|
346
|
+
...(m.layouts._ || {}),
|
|
301
347
|
...(m.texts._ || {}),
|
|
302
348
|
};
|
|
303
349
|
|
|
@@ -307,6 +353,7 @@ const emitCssV4 = () => {
|
|
|
307
353
|
...(m.containers[bp] || {}),
|
|
308
354
|
...(m.spacings[bp] || {}),
|
|
309
355
|
...(m.zIndices[bp] || {}),
|
|
356
|
+
...(m.layouts[bp] || {}),
|
|
310
357
|
...(m.texts[bp] || {}),
|
|
311
358
|
};
|
|
312
359
|
if (Object.keys(merged).length === 0) continue;
|
|
@@ -325,7 +372,7 @@ const emitCssV4 = () => {
|
|
|
325
372
|
'@import "tailwindcss";',
|
|
326
373
|
'',
|
|
327
374
|
'/*',
|
|
328
|
-
' * `@aziontech/webkit` components use arbitrary values (`bg-
|
|
375
|
+
' * `@aziontech/webkit` components use arbitrary values (`bg-(--x)`) that',
|
|
329
376
|
' * Tailwind v4 only picks up when the source files are inside the scan.',
|
|
330
377
|
' * Auto-detect scans the CWD only, so we point `@source` at the webkit',
|
|
331
378
|
' * package via a monorepo-relative path (this file is under',
|
|
@@ -354,6 +401,8 @@ const emitCssV4 = () => {
|
|
|
354
401
|
'',
|
|
355
402
|
emitTextUtilities(),
|
|
356
403
|
'',
|
|
404
|
+
emitLayoutUtilities(),
|
|
405
|
+
'',
|
|
357
406
|
emitSemanticColorUtilities(),
|
|
358
407
|
'',
|
|
359
408
|
emitBaseLayer(),
|
|
@@ -382,6 +431,7 @@ const importIdx = rawCss.indexOf(IMPORT_LINE);
|
|
|
382
431
|
if (importIdx === -1) throw new Error('emitCssV4 output is missing the tailwind import line');
|
|
383
432
|
const afterImport = importIdx + IMPORT_LINE.length;
|
|
384
433
|
const css = `${rawCss.slice(0, afterImport)}\n\n${fontsCss}${rawCss.slice(afterImport)}`;
|
|
434
|
+
assertNoZeroWithUnit(css, 'globals.css');
|
|
385
435
|
await writeFile(resolve(dir, 'globals.css'), css, 'utf8');
|
|
386
436
|
await writeFile(resolve(dir, 'globals.scss'), css, 'utf8');
|
|
387
437
|
console.log(`✓ v4 → ${dir}`);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Imports every primitive module (colors, shape, typography, effects,
|
|
5
5
|
* breakpoints, border-widths, ring-offset), resolves `tokenRef(...)` aliases,
|
|
6
|
-
* and produces a `:root { ... }` block. Mirrors
|
|
6
|
+
* and produces a `:root { ... }` block. Mirrors `injectThemeCss` (compile-theme.js)
|
|
7
7
|
* but for primitives only — no semantic light/dark split.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -33,7 +33,7 @@ import { fontWeight } from '../tokens/primitives/typography/font-weight.js'
|
|
|
33
33
|
import { leading } from '../tokens/primitives/typography/leading.js'
|
|
34
34
|
import { lineHeight } from '../tokens/primitives/typography/line-height.js'
|
|
35
35
|
import { tracking } from '../tokens/primitives/typography/tracking.js'
|
|
36
|
-
import { isTokenRef } from './refs.js'
|
|
36
|
+
import { assertResolvedRefs, isTokenRef } from './refs.js'
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Returns two trees:
|
|
@@ -123,17 +123,18 @@ const varNameFor = (path) => {
|
|
|
123
123
|
return `--${segments.join('-')}`
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
export const flatten = (obj, refsTree, prefix = []) => {
|
|
126
|
+
export const flatten = (obj, refsTree, prefix = [], unresolved = null) => {
|
|
127
127
|
const result = {}
|
|
128
128
|
Object.entries(obj).forEach(([key, value]) => {
|
|
129
129
|
const nextPath = [...prefix, key]
|
|
130
130
|
if (isTokenRef(value)) {
|
|
131
131
|
const resolved = resolveRef(value.__ref, refsTree)
|
|
132
|
+
if (resolved == null) unresolved?.push(`${varNameFor(nextPath)} → ${value.__ref}`)
|
|
132
133
|
result[varNameFor(nextPath)] = resolved ?? value.__ref
|
|
133
134
|
return
|
|
134
135
|
}
|
|
135
136
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
136
|
-
Object.assign(result, flatten(value, refsTree, nextPath))
|
|
137
|
+
Object.assign(result, flatten(value, refsTree, nextPath, unresolved))
|
|
137
138
|
return
|
|
138
139
|
}
|
|
139
140
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
@@ -145,7 +146,10 @@ export const flatten = (obj, refsTree, prefix = []) => {
|
|
|
145
146
|
|
|
146
147
|
export const compilePrimitivesVars = () => {
|
|
147
148
|
const { refsTree, varsTree } = buildTrees()
|
|
148
|
-
|
|
149
|
+
const unresolved = []
|
|
150
|
+
const vars = flatten(varsTree, refsTree, [], unresolved)
|
|
151
|
+
assertResolvedRefs('primitives', unresolved)
|
|
152
|
+
return vars
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
export const compilePrimitivesCss = () => {
|
|
@@ -24,7 +24,7 @@ import { ring } from '../tokens/theme/ring.js'
|
|
|
24
24
|
import { secondary } from '../tokens/theme/secondary.js'
|
|
25
25
|
import { surfaces } from '../tokens/theme/surfaces.js'
|
|
26
26
|
import { text } from '../tokens/theme/text.js'
|
|
27
|
-
import { isTokenRef } from './refs.js'
|
|
27
|
+
import { assertResolvedRefs, isTokenRef } from './refs.js'
|
|
28
28
|
|
|
29
29
|
const VARIANTS = ['light', 'dark']
|
|
30
30
|
|
|
@@ -58,23 +58,25 @@ const resolveRef = (ref, surfacesResolved) => {
|
|
|
58
58
|
return null
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const resolveGroup = (group, surfacesResolved) => {
|
|
61
|
+
const resolveGroup = (group, surfacesResolved, variant, unresolved) => {
|
|
62
62
|
const result = {}
|
|
63
63
|
Object.entries(group).forEach(([key, value]) => {
|
|
64
64
|
if (!isTokenRef(value)) return
|
|
65
65
|
const resolved = resolveRef(value.__ref, surfacesResolved)
|
|
66
66
|
if (resolved != null) result[`--${key}`] = String(resolved)
|
|
67
|
+
else unresolved.push(`[${variant}] --${key} → ${value.__ref}`)
|
|
67
68
|
})
|
|
68
69
|
return result
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const compileVariant = (variant) => {
|
|
72
|
+
const compileVariant = (variant, unresolved) => {
|
|
72
73
|
// Surfaces resolved first — other groups depend on them.
|
|
73
74
|
const surfacesResolved = {}
|
|
74
75
|
Object.entries(surfaces[variant]).forEach(([key, value]) => {
|
|
75
76
|
if (!isTokenRef(value)) return
|
|
76
77
|
const v = resolveRef(value.__ref, {})
|
|
77
78
|
if (v != null) surfacesResolved[key] = String(v)
|
|
79
|
+
else unresolved.push(`[${variant}] --${key} → ${value.__ref}`)
|
|
78
80
|
})
|
|
79
81
|
|
|
80
82
|
const vars = {}
|
|
@@ -96,12 +98,16 @@ const compileVariant = (variant) => {
|
|
|
96
98
|
danger[variant],
|
|
97
99
|
info[variant]
|
|
98
100
|
]
|
|
99
|
-
groups.forEach((g) => Object.assign(vars, resolveGroup(g, surfacesResolved)))
|
|
101
|
+
groups.forEach((g) => Object.assign(vars, resolveGroup(g, surfacesResolved, variant, unresolved)))
|
|
100
102
|
return vars
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
export const compileThemeVars = () =>
|
|
104
|
-
|
|
105
|
+
export const compileThemeVars = () => {
|
|
106
|
+
const unresolved = []
|
|
107
|
+
const vars = Object.fromEntries(VARIANTS.map((v) => [v, compileVariant(v, unresolved)]))
|
|
108
|
+
assertResolvedRefs('semantic theme tokens', unresolved)
|
|
109
|
+
return vars
|
|
110
|
+
}
|
|
105
111
|
|
|
106
112
|
export const compileThemeCss = () => {
|
|
107
113
|
const { light, dark } = compileThemeVars()
|
package/src/scripts/refs.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["refs.js"],"names":[],"mappings":"AAIO;;EAA4C;AAE5C,gDAA6F"}
|
|
1
|
+
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["refs.js"],"names":[],"mappings":"AAIO;;EAA4C;AAE5C,gDAA6F;AAO7F,wEAIN"}
|
package/src/scripts/refs.js
CHANGED
|
@@ -5,3 +5,14 @@
|
|
|
5
5
|
export const tokenRef = (path) => ({ __ref: path })
|
|
6
6
|
|
|
7
7
|
export const isTokenRef = (value) => typeof value === 'object' && value !== null && '__ref' in value
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fail loudly on unresolved refs. A typo'd token must never silently vanish
|
|
11
|
+
* from the compiled CSS or ship as a raw `path.to.token` string — the compilers
|
|
12
|
+
* collect every miss and this throws once with the full list.
|
|
13
|
+
*/
|
|
14
|
+
export const assertResolvedRefs = (context, unresolved) => {
|
|
15
|
+
if (unresolved.length === 0) return
|
|
16
|
+
const lines = unresolved.map((entry) => ` ${entry}`).join('\n')
|
|
17
|
+
throw new Error(`[theme] ${unresolved.length} unresolved tokenRef(s) in ${context}:\n${lines}`)
|
|
18
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-with-unit gate for the compiled token stylesheet — the only check that sees token
|
|
3
|
+
* values (authored in JS, so no linter does), run on the final CSS before globals.css is
|
|
4
|
+
* written. A zero length is `0`; inside calc()/min()/max()/clamp() it must carry a unit, and
|
|
5
|
+
* that unit is `rem` (.claude/rules/styling.md). Mirror of the zero checks in packages/webkit
|
|
6
|
+
* token-checks.js, kept in sync — LENGTH_UNITS is the same string. Pure: node built-ins only.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Opens a math function; balances parens one level deep (a zero nested deeper falls back to
|
|
10
|
+
// the bare-zero check).
|
|
11
|
+
const MATH_FN = '(?:calc|min|max|clamp)\\((?:[^()]|\\([^()]*\\))*';
|
|
12
|
+
|
|
13
|
+
// Complete CSS length-unit set — must be complete, since a missing unit silently passes.
|
|
14
|
+
export const LENGTH_UNITS =
|
|
15
|
+
'px|rem|em|ex|rex|ch|rch|cap|rcap|ic|ric|lh|rlh|vw|vh|vi|vb|vmin|vmax|svw|svh|svi|svb|svmin|svmax|lvw|lvh|lvi|lvb|lvmin|lvmax|dvw|dvh|dvi|dvb|dvmin|dvmax|cqw|cqh|cqi|cqb|cqmin|cqmax|cm|mm|Q|in|pt|pc';
|
|
16
|
+
|
|
17
|
+
// Mutually exclusive, so a value is reported once. Non-global so `.test()` is stateless —
|
|
18
|
+
// findZeroMisuse adds `g` locally to enumerate matches.
|
|
19
|
+
export const ZERO_WITH_UNIT = new RegExp(`(?<![\\w.])(?<!${MATH_FN})0(?:${LENGTH_UNITS})(?![\\w%])`, 'i');
|
|
20
|
+
export const ZERO_UNIT_IN_MATH = new RegExp(
|
|
21
|
+
`(?<=${MATH_FN})(?<![\\w.])0(?!rem)(?:${LENGTH_UNITS})(?![\\w%])`,
|
|
22
|
+
'i',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Matches of `regex` in `cssText` as `{ line, n }`. Scans the whole string, not line-by-line,
|
|
27
|
+
* so a calc() wrapped across newlines is still seen as in-math.
|
|
28
|
+
*/
|
|
29
|
+
export const findZeroMisuse = (cssText, regex) => {
|
|
30
|
+
const scan = new RegExp(regex.source, `${regex.flags.replace('g', '')}g`);
|
|
31
|
+
const out = [];
|
|
32
|
+
for (let m; (m = scan.exec(cssText)); ) {
|
|
33
|
+
const lineStart = cssText.lastIndexOf('\n', m.index - 1) + 1;
|
|
34
|
+
const nlIdx = cssText.indexOf('\n', m.index);
|
|
35
|
+
const lineEnd = nlIdx === -1 ? cssText.length : nlIdx;
|
|
36
|
+
out.push({ line: cssText.slice(lineStart, lineEnd).trim(), n: cssText.slice(0, m.index).split('\n').length });
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// The `--custom-property` a line declares, to name the token to fix; null off a `--token:` line.
|
|
42
|
+
const tokenOf = (line) => line.match(/(--[\w-]+)\s*:/)?.[1] ?? null;
|
|
43
|
+
|
|
44
|
+
/** Throw if `cssText` misuses a zero length. `source` names the artifact in the message. */
|
|
45
|
+
export const assertNoZeroWithUnit = (cssText, source = 'globals.css') => {
|
|
46
|
+
const bare = findZeroMisuse(cssText, ZERO_WITH_UNIT);
|
|
47
|
+
const inMath = findZeroMisuse(cssText, ZERO_UNIT_IN_MATH);
|
|
48
|
+
if (bare.length === 0 && inMath.length === 0) return;
|
|
49
|
+
|
|
50
|
+
const detail = (rows) => rows.map(({ line, n }) => ` ${source}:${n} ${line}`).join('\n');
|
|
51
|
+
const tokens = [...new Set([...bare, ...inMath].map(({ line }) => tokenOf(line)).filter(Boolean))];
|
|
52
|
+
const parts = [`build:tokens — ${bare.length + inMath.length} token value(s) misuse a zero.`];
|
|
53
|
+
if (bare.length > 0) {
|
|
54
|
+
parts.push(`A zero length takes no unit: write '0', not '0px' / '0rem' / '0em'.\n${detail(bare)}`);
|
|
55
|
+
}
|
|
56
|
+
if (inMath.length > 0) {
|
|
57
|
+
parts.push(
|
|
58
|
+
`Inside calc()/min()/max()/clamp() the zero needs a unit, and that unit is rem: write '0rem'.\n${detail(inMath)}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const grepHint = tokens.length > 0 ? ` — grep the offending token name (${tokens.join(', ')})` : '';
|
|
62
|
+
parts.push(`Fix the token at its source under src/tokens/**${grepHint}, not the generated ${source}.`);
|
|
63
|
+
throw new Error(parts.join('\n'));
|
|
64
|
+
};
|
package/src/tokens/README.md
CHANGED
|
@@ -39,8 +39,6 @@ tokens/theme/** ─┤ dist/v4/
|
|
|
39
39
|
│
|
|
40
40
|
scripts/{compile-primitives, ─┘
|
|
41
41
|
compile-theme,
|
|
42
|
-
resolve,
|
|
43
|
-
css-vars,
|
|
44
42
|
refs}.js (building blocks used by build-tokens.mjs)
|
|
45
43
|
```
|
|
46
44
|
|
|
@@ -115,17 +113,11 @@ src/
|
|
|
115
113
|
│ │ └── animations.js
|
|
116
114
|
│ ├── theme.js # Tailwind theme.extend (colors + semantic mappings)
|
|
117
115
|
│ └── index.js # public re-exports
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
│ ├── compile-theme.js # legacy theme compiler (used by the harness pages)
|
|
124
|
-
│ └── build-tokens.mjs # main entrypoint: emits dist/v3 and dist/v4 bundles
|
|
125
|
-
└── tests/
|
|
126
|
-
├── primitives.html # visual harness for all primitives
|
|
127
|
-
├── theme.html # semantic tokens with light/dark toggle
|
|
128
|
-
└── tokens.html # combined view
|
|
116
|
+
└── scripts/
|
|
117
|
+
├── refs.js # tokenRef helper + isTokenRef guard + assertResolvedRefs
|
|
118
|
+
├── compile-primitives.js # flattens primitive trees into CSS vars
|
|
119
|
+
├── compile-theme.js # semantic light/dark compiler (exported as ./theme-colors)
|
|
120
|
+
└── build-tokens.mjs # main entrypoint: emits the dist/v4 bundle
|
|
129
121
|
```
|
|
130
122
|
|
|
131
123
|
---
|
|
@@ -187,7 +179,31 @@ export const spacingsData = {
|
|
|
187
179
|
}
|
|
188
180
|
```
|
|
189
181
|
|
|
190
|
-
`_` is the base value emitted in `:root`; the breakpoint keys (`sm`, `md`, `lg`, `xl`, `2xl`) become media-query overrides.
|
|
182
|
+
`_` is the base value emitted in `:root`; the breakpoint keys (`sm`, `md`, `lg`, `xl`, `2xl`) become media-query overrides. A bare string is accepted as shorthand for `{ _: value }`.
|
|
183
|
+
|
|
184
|
+
### Derived groups (`layouts.data.js`)
|
|
185
|
+
|
|
186
|
+
`semantic/layouts` is a **derived** group: every value is a `var()` reference to `--spacing-*` /
|
|
187
|
+
`--container-*`, never a literal length.
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
// tokens/semantic/layouts.data.js
|
|
191
|
+
export const layoutsData = {
|
|
192
|
+
'layout-boundary-inline': 'var(--spacing-lg)',
|
|
193
|
+
'layout-measure': 'var(--container-7xl)'
|
|
194
|
+
// …
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
That indirection is the whole point, and it is why these carry no breakpoint map of their own: the
|
|
199
|
+
spacing scale is already responsive (`--spacing-lg` is `1rem`, then `1.5rem` from `sm`), and a `var()`
|
|
200
|
+
is substituted at **use** time on the element — so a layout token follows the breakpoint override for
|
|
201
|
+
free. Giving it its own map would duplicate the spacing scale into layout and let the two drift.
|
|
202
|
+
|
|
203
|
+
The group also ships `layoutsUtilities`, the container-system classes (`.layout-column`,
|
|
204
|
+
`.layout-boundary`, …) emitted as Tailwind v4 `@utility` blocks so they support variants
|
|
205
|
+
(`md:layout-column`). Utilities are shaped exactly like `illustrationsUtilities`: a string value is a
|
|
206
|
+
declaration, an object value is a nested rule keyed by a literal selector (`'&:first-child'`).
|
|
191
207
|
|
|
192
208
|
---
|
|
193
209
|
|
|
@@ -216,18 +232,18 @@ Utilities like `text-default`, `bg-surface`, `border-default`, `text-primary`, `
|
|
|
216
232
|
### As JS objects (Node / build steps)
|
|
217
233
|
|
|
218
234
|
```js
|
|
219
|
-
import {
|
|
235
|
+
import { compileThemeVars, compileThemeCss } from '@aziontech/theme/theme-colors'
|
|
220
236
|
|
|
221
|
-
|
|
222
|
-
|
|
237
|
+
compileThemeVars() // → { light: { '--primary': '…', … }, dark: { … } }
|
|
238
|
+
compileThemeCss() // → ':root, [data-theme=light], .azion.azion-light { … }\n\n[data-theme=dark], … { … }'
|
|
223
239
|
```
|
|
224
240
|
|
|
225
241
|
### Injecting at runtime (browser)
|
|
226
242
|
|
|
227
243
|
```js
|
|
228
|
-
import {
|
|
244
|
+
import { injectThemeCss } from '@aziontech/theme/theme-colors'
|
|
229
245
|
|
|
230
|
-
|
|
246
|
+
injectThemeCss() // appends a <style data-azion-theme> element to <head>
|
|
231
247
|
```
|
|
232
248
|
|
|
233
249
|
Only useful if you can't precompile and import `globals.css` — production usage should prefer the static stylesheet.
|
|
@@ -306,18 +322,17 @@ Bundles with `fontSize: 'inherit'` are omitted from the Tailwind `fontSize` pres
|
|
|
306
322
|
|
|
307
323
|
## 🔗 Token references (`tokenRef`)
|
|
308
324
|
|
|
309
|
-
`tokenRef(path)` returns a marker object `{ __ref: path }` that the
|
|
325
|
+
`tokenRef(path)` returns a marker object `{ __ref: path }` that the compilers resolve at build time. Primitive-tree refs (`scripts/compile-primitives.js`) resolve against the full primitives tree (e.g. `shape.container.3xs`). Semantic theme refs (`scripts/compile-theme.js`) support these prefixes:
|
|
310
326
|
|
|
311
|
-
| Prefix
|
|
312
|
-
|
|
|
313
|
-
| `primitives.X.Y.Z`
|
|
314
|
-
| `
|
|
315
|
-
| `brand.
|
|
316
|
-
| `brand.
|
|
317
|
-
| `
|
|
318
|
-
| `brand.absolute.X` | `brandPrimitives.absolute.X` (`black` / `white`) |
|
|
327
|
+
| Prefix | Looks up |
|
|
328
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
329
|
+
| `primitives.X.Y.Z` | `tokens/primitives/colors/colors.js` tree (e.g., `primitives.gray.900`, `primitives.alpha.orange.100`) |
|
|
330
|
+
| `brand.primary.primary-N` | `brandPrimitives.primary[N]` (e.g., `brand.primary.primary-500` → `#FE601F`) |
|
|
331
|
+
| `brand.accent.accent-N` | `brandPrimitives.accent[N]` |
|
|
332
|
+
| `brand.absolute.X` | `brandPrimitives.absolute.X` (`black` / `white`) |
|
|
333
|
+
| `theme.surfaces.X` | the variant's pre-resolved surfaces group (`tokens/theme/surfaces.js`) |
|
|
319
334
|
|
|
320
|
-
|
|
335
|
+
A ref that does not resolve — unknown prefix or dead path — **fails the build**: the compilers collect every miss and throw one aggregated `--var → ref` list (`assertResolvedRefs` in `scripts/refs.js`).
|
|
321
336
|
|
|
322
337
|
---
|
|
323
338
|
|
package/src/tokens/index.d.ts
CHANGED
|
@@ -2,5 +2,4 @@ export * from "./primitives/colors/colors.js";
|
|
|
2
2
|
export * from "./semantic/colors.js";
|
|
3
3
|
export * from "./theme.js";
|
|
4
4
|
export { textsData } from "./semantic/texts.data.js";
|
|
5
|
-
export { createCssVars, cssVarsString, injectCssVars } from "../scripts/css-vars.js";
|
|
6
5
|
//# sourceMappingURL=index.d.ts.map
|
package/src/tokens/index.js
CHANGED
|
@@ -25,6 +25,8 @@ export const animate: {
|
|
|
25
25
|
'fade-out': string;
|
|
26
26
|
'slide-down': string;
|
|
27
27
|
'highlight-fade': string;
|
|
28
|
+
'page-enter': string;
|
|
29
|
+
'content-enter': string;
|
|
28
30
|
'popup-scale-in': string;
|
|
29
31
|
'popup-scale-out': string;
|
|
30
32
|
'slide-in-left': string;
|
|
@@ -33,8 +35,11 @@ export const animate: {
|
|
|
33
35
|
'slide-out-right': string;
|
|
34
36
|
'progress-indeterminate': string;
|
|
35
37
|
'progress-indeterminate-short': string;
|
|
38
|
+
'flow-dash': string;
|
|
36
39
|
};
|
|
37
40
|
export const useWhen: {
|
|
41
|
+
'page-enter': string;
|
|
42
|
+
'content-enter': string;
|
|
38
43
|
spin: string;
|
|
39
44
|
ping: string;
|
|
40
45
|
pulse: string;
|
|
@@ -53,6 +58,7 @@ export const useWhen: {
|
|
|
53
58
|
'slide-out-right': string;
|
|
54
59
|
'progress-indeterminate': string;
|
|
55
60
|
'progress-indeterminate-short': string;
|
|
61
|
+
'flow-dash': string;
|
|
56
62
|
};
|
|
57
63
|
declare namespace _default {
|
|
58
64
|
export { animate };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["animate.js"],"names":[],"mappings":"AAAA;;;;;EAKC;AAED;;;;;;;;;EASC;AAED
|
|
1
|
+
{"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["animate.js"],"names":[],"mappings":"AAAA;;;;;EAKC;AAED;;;;;;;;;EASC;AAED;;;;;;;;;;;;;;;;;;;;;;EAwBC;AAED;;;;;;;;;;;;;;;;;;;;;;EA4BC"}
|
|
@@ -26,7 +26,9 @@ export const animate = {
|
|
|
26
26
|
'fade-in': 'fadeIn 220ms ease-in-out',
|
|
27
27
|
'fade-out': 'fadeOut 220ms ease-in-out',
|
|
28
28
|
'slide-down': 'slideDown 220ms ease-in-out',
|
|
29
|
-
'highlight-fade':
|
|
29
|
+
'highlight-fade': `highlight ${duration['slow-03']} ease-in forwards`,
|
|
30
|
+
'page-enter': `pageEnter ${duration['moderate-02']} ${curve['productive-entrance']}`,
|
|
31
|
+
'content-enter': `contentEnter ${duration['moderate-02']} ${curve['productive-entrance']} var(--content-enter-delay, 0s) backwards`,
|
|
30
32
|
'popup-scale-in': `popupScaleIn ${duration['moderate-01']} ${curve['productive-entrance']}`,
|
|
31
33
|
'popup-scale-out': `popupScaleOut ${duration['fast-02']} ${curve['productive-exit']}`,
|
|
32
34
|
'slide-in-left': `slideInLeft ${duration['moderate-02']} ${curve['productive-entrance']}`,
|
|
@@ -34,10 +36,17 @@ export const animate = {
|
|
|
34
36
|
'slide-in-right': `slideInRight ${duration['moderate-02']} ${curve['productive-entrance']}`,
|
|
35
37
|
'slide-out-right': `slideOutRight ${duration['moderate-01']} ${curve['productive-exit']}`,
|
|
36
38
|
'progress-indeterminate': `progressIndeterminate ${duration['slow-04']} ${curve['productive-entrance']} infinite`,
|
|
37
|
-
'progress-indeterminate-short': `progressIndeterminateShort ${duration['slow-04']} ${curve['expressive-entrance']} ${duration['slow-03']} infinite
|
|
39
|
+
'progress-indeterminate-short': `progressIndeterminateShort ${duration['slow-04']} ${curve['expressive-entrance']} ${duration['slow-03']} infinite`,
|
|
40
|
+
// `linear` (not a curve token) for the same reason as spin/shimmer: an endlessly
|
|
41
|
+
// looping animation must not accelerate, or the seam between repeats is visible.
|
|
42
|
+
'flow-dash': `flowDash ${duration['slow-02']} linear infinite`
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export const useWhen = {
|
|
46
|
+
'page-enter':
|
|
47
|
+
'A page arriving on a route change — on the CONTENT ZONE only, never the shell. The chrome is the same before and after, and sliding it announces a reload that did not happen. Key it on the route path so a component serving several paths still replays it. Nothing inside the page may animate on mount at the same time: the two run in lockstep and read as one element travelling on a diagonal.',
|
|
48
|
+
'content-enter':
|
|
49
|
+
'Content settling INSIDE a page that is already on screen — a loading window resolving, a filtered list swapping, a step changing. Never on first paint of a page whose own entrance is already running (use it after that entrance, or after a wire). Stagger a follower with --content-enter-delay.',
|
|
41
50
|
spin: 'Indeterminate circular spinners (loading icons).',
|
|
42
51
|
ping: 'One-off attention ring radiating from a small element (notification dot).',
|
|
43
52
|
pulse: 'Skeleton/placeholder opacity pulse while content loads.',
|
|
@@ -58,7 +67,9 @@ export const useWhen = {
|
|
|
58
67
|
'slide-in-right': 'Right-anchored panel entering (settings/detail drawer).',
|
|
59
68
|
'slide-out-right': 'Right-anchored panel leaving (pair of slide-in-right).',
|
|
60
69
|
'progress-indeterminate': 'Indeterminate linear progress bar (primary sweep).',
|
|
61
|
-
'progress-indeterminate-short': 'Indeterminate linear progress bar (secondary short sweep).'
|
|
70
|
+
'progress-indeterminate-short': 'Indeterminate linear progress bar (secondary short sweep).',
|
|
71
|
+
'flow-dash':
|
|
72
|
+
'Flowing connection along an SVG connector stroke in a node-based / network diagram. Set a stroke-dasharray whose cycle divides 24 (e.g. 4 4) so the loop is seamless.'
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
export default { animate, curve, duration, useWhen }
|
|
@@ -16,13 +16,21 @@ export namespace keyframes {
|
|
|
16
16
|
'50%': string;
|
|
17
17
|
};
|
|
18
18
|
let highlight: {
|
|
19
|
-
'0%': string;
|
|
19
|
+
'0%, 40%': string;
|
|
20
20
|
'100%': string;
|
|
21
21
|
};
|
|
22
22
|
let shimmer: {
|
|
23
23
|
'0%': string;
|
|
24
24
|
'100%': string;
|
|
25
25
|
};
|
|
26
|
+
let pageEnter: {
|
|
27
|
+
'0%': string;
|
|
28
|
+
'100%': string;
|
|
29
|
+
};
|
|
30
|
+
let contentEnter: {
|
|
31
|
+
'0%': string;
|
|
32
|
+
'100%': string;
|
|
33
|
+
};
|
|
26
34
|
let popupScaleIn: {
|
|
27
35
|
'0%': string;
|
|
28
36
|
'100%': string;
|
|
@@ -57,6 +65,10 @@ export namespace keyframes {
|
|
|
57
65
|
'60%': string;
|
|
58
66
|
'100%': string;
|
|
59
67
|
};
|
|
68
|
+
let flowDash: {
|
|
69
|
+
'0%': string;
|
|
70
|
+
'100%': string;
|
|
71
|
+
};
|
|
60
72
|
}
|
|
61
73
|
/**
|
|
62
74
|
* Utilities that need CSS properties beyond the `animation` shorthand
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyframes.d.ts","sourceRoot":"","sources":["keyframes.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"keyframes.d.ts","sourceRoot":"","sources":["keyframes.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA;;;;GAIG;AACH;;;;;;;EAGC"}
|
|
@@ -28,21 +28,31 @@ export const keyframes = {
|
|
|
28
28
|
'0%, 100%': 'opacity: 1',
|
|
29
29
|
'50%': 'opacity: 0'
|
|
30
30
|
},
|
|
31
|
+
// Holds the highlight for the first 40% of the run, then fades it back to
|
|
32
|
+
// normal over the remaining 60% — a row-flash, not a permanent tint.
|
|
31
33
|
highlight: {
|
|
32
|
-
'0%': 'background-color: var(--surface-hover); font-weight: 500',
|
|
33
|
-
'100%': 'background-color:
|
|
34
|
+
'0%, 40%': 'background-color: var(--surface-hover); font-weight: 500',
|
|
35
|
+
'100%': 'background-color: transparent; font-weight: 400'
|
|
34
36
|
},
|
|
35
37
|
shimmer: {
|
|
36
38
|
'0%': 'background-position: 200% 0',
|
|
37
39
|
'100%': 'background-position: -200% 0'
|
|
38
40
|
},
|
|
41
|
+
pageEnter: {
|
|
42
|
+
'0%': 'opacity: 0; translate: calc(var(--page-enter-distance, var(--layout-boundary-inline)) * -1) 0',
|
|
43
|
+
'100%': 'opacity: 1; translate: 0 0'
|
|
44
|
+
},
|
|
45
|
+
contentEnter: {
|
|
46
|
+
'0%': 'opacity: 0; translate: 0 var(--spacing-xs)',
|
|
47
|
+
'100%': 'opacity: 1; translate: 0 0'
|
|
48
|
+
},
|
|
39
49
|
popupScaleIn: {
|
|
40
|
-
'0%': 'opacity: 0; transform: scale(0.
|
|
50
|
+
'0%': 'opacity: 0; transform: scale(0.9)',
|
|
41
51
|
'100%': 'opacity: 1; transform: scale(1)'
|
|
42
52
|
},
|
|
43
53
|
popupScaleOut: {
|
|
44
54
|
'0%': 'opacity: 1; transform: scale(1)',
|
|
45
|
-
'100%': 'opacity: 0; transform: scale(0.
|
|
55
|
+
'100%': 'opacity: 0; transform: scale(0.9)'
|
|
46
56
|
},
|
|
47
57
|
slideInLeft: {
|
|
48
58
|
'0%': 'transform: translateX(-100%)',
|
|
@@ -69,6 +79,15 @@ export const keyframes = {
|
|
|
69
79
|
'0%': 'inset-inline-start: -200%; inset-inline-end: 100%',
|
|
70
80
|
'60%': 'inset-inline-start: 107%; inset-inline-end: -8%',
|
|
71
81
|
'100%': 'inset-inline-start: 107%; inset-inline-end: -8%'
|
|
82
|
+
},
|
|
83
|
+
// Marching dashes along an SVG stroke — the "flowing connection" of a node-based
|
|
84
|
+
// diagram. The travel distance is 24, so the loop is seamless for any
|
|
85
|
+
// `stroke-dasharray` whose cycle DIVIDES 24 (`4 4` = 8, `2 4` = 6, `8 4` = 12): the
|
|
86
|
+
// pattern lands exactly where it started. A cycle that does not divide 24 (`5 5` = 10)
|
|
87
|
+
// visibly jumps on repeat.
|
|
88
|
+
flowDash: {
|
|
89
|
+
'0%': 'stroke-dashoffset: 24',
|
|
90
|
+
'100%': 'stroke-dashoffset: 0'
|
|
72
91
|
}
|
|
73
92
|
}
|
|
74
93
|
|