@orangesys/tokens 0.0.0-stage → 0.5.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/dist/dtcg.d.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Exporta os tokens no formato W3C Design Tokens (DTCG, versão 2025.10).
3
+ *
4
+ * Modos: o DTCG não tem modos nativos, então cada tema vira um **conjunto
5
+ * (arquivo) por modo** — `tema/dark`, `tema/light`, `tema/dark-hc`,
6
+ * `tema/light-hc` — com os tokens semânticos como aliases para as primitivas
7
+ * (`{color.orange.500}`), que ficam no conjunto `primitivas`. É o formato que a
8
+ * importação de variáveis do Figma (um arquivo JSON por modo) e o Tokens Studio
9
+ * (conjuntos + `$themes`, que viram coleções e modos no Figma) entendem; valores
10
+ * por modo em `$extensions` seriam ignorados pelos dois.
11
+ */
12
+ import { type PrimitiveName, type ThemeName } from './index.js';
13
+ export type DtcgType = 'color' | 'dimension' | 'fontFamily' | 'fontWeight' | 'duration' | 'cubicBezier' | 'number';
14
+ /** Cor no formato do DTCG 2025.10; `hex` é a reserva para ferramentas mais antigas. */
15
+ export interface DtcgColor {
16
+ colorSpace: 'srgb';
17
+ components: [number, number, number];
18
+ alpha?: number;
19
+ hex: string;
20
+ }
21
+ export interface DtcgDimension {
22
+ value: number;
23
+ unit: 'px' | 'rem';
24
+ }
25
+ export interface DtcgDuration {
26
+ value: number;
27
+ unit: 'ms' | 's';
28
+ }
29
+ export interface DtcgToken {
30
+ $value: unknown;
31
+ $type?: DtcgType;
32
+ $description?: string;
33
+ $extensions?: Record<string, unknown>;
34
+ }
35
+ export interface DtcgGroup {
36
+ $type?: DtcgType;
37
+ $description?: string;
38
+ [name: string]: unknown;
39
+ }
40
+ /** Token com o caminho completo e o `$type` (e a `$description`) já herdados dos grupos. */
41
+ export interface FlatToken {
42
+ path: string[];
43
+ type: DtcgType;
44
+ token: DtcgToken;
45
+ description?: string;
46
+ }
47
+ /** Tema do Tokens Studio (`$themes.json`): cada grupo vira uma coleção, cada tema um modo. */
48
+ export interface TokensStudioTheme {
49
+ id: string;
50
+ name: string;
51
+ group: string;
52
+ selectedTokenSets: Record<string, 'enabled' | 'source' | 'disabled'>;
53
+ }
54
+ /** Conjuntos de tokens (um arquivo cada): as primitivas e um por tema/modo. */
55
+ export declare const dtcgSets: {
56
+ readonly primitives: "primitivas";
57
+ readonly theme: (name: ThemeName) => string;
58
+ };
59
+ /** Coleções de variáveis no Figma (também os grupos de temas do Tokens Studio). */
60
+ export declare const figmaCollections: {
61
+ readonly primitives: "Orange / Primitivas";
62
+ readonly theme: "Orange / Tema";
63
+ };
64
+ /** Único modo da coleção de primitivas. Na coleção de tema, os modos são os nomes dos temas. */
65
+ export declare const primitivesMode = "valor";
66
+ /** Chave em `$extensions` com a custom property CSS de cada token (`--or-color-bg`). */
67
+ export declare const cssVarExtension = "orange.cssVar";
68
+ /** `#rrggbb` ou `rgba(r, g, b, a)` → cor DTCG. */
69
+ export declare function toDtcgColor(value: string): DtcgColor;
70
+ /** Caminho DTCG de uma primitiva: `orange-500` → `color.orange.500`. */
71
+ export declare function primitivePath(name: PrimitiveName): string;
72
+ /** `{color.orange.500}` → `['color', 'orange', '500']`; `null` se não for alias. */
73
+ export declare function parseAlias(value: unknown): string[] | null;
74
+ /** Primitivas: paletas de cor, espaço, raio, tipografia e movimento. */
75
+ export declare function buildPrimitiveTokens(): DtcgGroup;
76
+ /**
77
+ * Tokens semânticos de um tema (um modo): cada `--or-color-*` como alias para a
78
+ * primitiva de origem, ou como cor crua quando o tema usa transparência/ajuste fino.
79
+ */
80
+ export declare function buildThemeTokens(name: ThemeName): DtcgGroup;
81
+ /** Todos os conjuntos, na ordem de resolução: primitivas primeiro, depois um por tema. */
82
+ export declare function buildTokenSets(): Record<string, DtcgGroup>;
83
+ /** `$themes` do Tokens Studio: grupo "Orange / Primitivas" (1 modo) e "Orange / Tema" (4 modos). */
84
+ export declare function buildTokensStudioThemes(): TokensStudioTheme[];
85
+ /**
86
+ * Um arquivo DTCG por conjunto (`primitivas.json`, `tema/dark.json`…) mais o
87
+ * `$themes.json` e o `$metadata.json` do Tokens Studio (sincronização em pasta).
88
+ */
89
+ export declare function buildDtcgFiles(): Record<string, unknown>;
90
+ /** Arquivo único (`tokens.dtcg.json`): os conjuntos lado a lado, mais `$themes` e `$metadata`. */
91
+ export declare function buildDtcgBundle(): Record<string, unknown>;
92
+ /** Lista os tokens de um grupo com o caminho completo e o tipo (e a descrição) herdados. */
93
+ export declare function flattenTokens(root: DtcgGroup, path?: string[], inherited?: {
94
+ type?: DtcgType;
95
+ description?: string;
96
+ }): FlatToken[];
package/dist/dtcg.js ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Exporta os tokens no formato W3C Design Tokens (DTCG, versão 2025.10).
3
+ *
4
+ * Modos: o DTCG não tem modos nativos, então cada tema vira um **conjunto
5
+ * (arquivo) por modo** — `tema/dark`, `tema/light`, `tema/dark-hc`,
6
+ * `tema/light-hc` — com os tokens semânticos como aliases para as primitivas
7
+ * (`{color.orange.500}`), que ficam no conjunto `primitivas`. É o formato que a
8
+ * importação de variáveis do Figma (um arquivo JSON por modo) e o Tokens Studio
9
+ * (conjuntos + `$themes`, que viram coleções e modos no Figma) entendem; valores
10
+ * por modo em `$extensions` seriam ignorados pelos dois.
11
+ */
12
+ import { isPrimitiveName, motion, primitives, radius, space, themeDefinitions, themeNames, typography, vizDefinition, vizPalettes, } from './index.js';
13
+ /* -------------------------------------------------------------------------- */
14
+ /* Nomes */
15
+ /* -------------------------------------------------------------------------- */
16
+ /** Conjuntos de tokens (um arquivo cada): as primitivas e um por tema/modo. */
17
+ export const dtcgSets = {
18
+ primitives: 'primitivas',
19
+ theme: (name) => `tema/${name}`,
20
+ };
21
+ /** Coleções de variáveis no Figma (também os grupos de temas do Tokens Studio). */
22
+ export const figmaCollections = {
23
+ primitives: 'Orange / Primitivas',
24
+ theme: 'Orange / Tema',
25
+ };
26
+ /** Único modo da coleção de primitivas. Na coleção de tema, os modos são os nomes dos temas. */
27
+ export const primitivesMode = 'valor';
28
+ /** Chave em `$extensions` com a custom property CSS de cada token (`--or-color-bg`). */
29
+ export const cssVarExtension = 'orange.cssVar';
30
+ /* -------------------------------------------------------------------------- */
31
+ /* Conversões */
32
+ /* -------------------------------------------------------------------------- */
33
+ const round = (n) => Math.round(n * 10000) / 10000;
34
+ const toHex = (channels) => `#${channels.map((c) => Math.round(c).toString(16).padStart(2, '0')).join('')}`;
35
+ /** `#rrggbb` ou `rgba(r, g, b, a)` → cor DTCG. */
36
+ export function toDtcgColor(value) {
37
+ const hex = /^#([0-9a-f]{6})$/i.exec(value.trim());
38
+ const rgba = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i.exec(value.trim());
39
+ let channels;
40
+ let alpha = 1;
41
+ if (hex) {
42
+ channels = [0, 2, 4].map((i) => parseInt(hex[1].slice(i, i + 2), 16));
43
+ }
44
+ else if (rgba) {
45
+ channels = rgba.slice(1, 4).map(Number);
46
+ alpha = rgba[4] === undefined ? 1 : Number(rgba[4]);
47
+ }
48
+ else {
49
+ throw new Error(`Cor não suportada no DTCG: ${value}`);
50
+ }
51
+ const color = {
52
+ colorSpace: 'srgb',
53
+ components: channels.map((c) => round(c / 255)),
54
+ hex: toHex(channels),
55
+ };
56
+ if (alpha < 1)
57
+ color.alpha = alpha;
58
+ return color;
59
+ }
60
+ function toDimension(value) {
61
+ if (value === '0')
62
+ return { value: 0, unit: 'px' };
63
+ const match = /^(-?[\d.]+)(px|rem)$/.exec(value);
64
+ if (!match)
65
+ throw new Error(`Dimensão não suportada no DTCG: ${value}`);
66
+ return { value: Number(match[1]), unit: match[2] };
67
+ }
68
+ function toDuration(value) {
69
+ const match = /^([\d.]+)(ms|s)$/.exec(value);
70
+ if (!match)
71
+ throw new Error(`Duração não suportada no DTCG: ${value}`);
72
+ return { value: Number(match[1]), unit: match[2] };
73
+ }
74
+ function toCubicBezier(value) {
75
+ const match = /^cubic-bezier\(([^)]+)\)$/.exec(value);
76
+ if (!match)
77
+ throw new Error(`Curva não suportada no DTCG: ${value}`);
78
+ return match[1].split(',').map((n) => Number(n.trim()));
79
+ }
80
+ const toFontFamily = (value) => value.split(',').map((part) => part.trim().replace(/^["']|["']$/g, ''));
81
+ /** Caminho DTCG de uma primitiva: `orange-500` → `color.orange.500`. */
82
+ export function primitivePath(name) {
83
+ const at = name.lastIndexOf('-');
84
+ return `color.${name.slice(0, at)}.${name.slice(at + 1)}`;
85
+ }
86
+ /** `{color.orange.500}` → `['color', 'orange', '500']`; `null` se não for alias. */
87
+ export function parseAlias(value) {
88
+ if (typeof value !== 'string')
89
+ return null;
90
+ const match = /^\{([^{}]+)\}$/.exec(value);
91
+ return match ? match[1].split('.') : null;
92
+ }
93
+ /* -------------------------------------------------------------------------- */
94
+ /* Conjuntos */
95
+ /* -------------------------------------------------------------------------- */
96
+ const token = ($value, cssVar) => ({
97
+ $value,
98
+ $extensions: { [cssVarExtension]: cssVar },
99
+ });
100
+ function group(type, values, convert, cssVar, description) {
101
+ return {
102
+ $type: type,
103
+ ...(description ? { $description: description } : {}),
104
+ ...Object.fromEntries(Object.entries(values).map(([key, value]) => [key, token(convert(value), cssVar(key))])),
105
+ };
106
+ }
107
+ /** Primitivas: paletas de cor, espaço, raio, tipografia e movimento. */
108
+ export function buildPrimitiveTokens() {
109
+ const palettes = primitives;
110
+ return {
111
+ color: {
112
+ $type: 'color',
113
+ ...Object.fromEntries(Object.entries(palettes).map(([family, scale]) => [
114
+ family,
115
+ Object.fromEntries(Object.entries(scale).map(([step, hex]) => [
116
+ step,
117
+ token(toDtcgColor(hex), `--or-palette-${family}-${step}`),
118
+ ])),
119
+ ])),
120
+ },
121
+ space: group('dimension', space, toDimension, (k) => `--or-space-${k}`),
122
+ radius: group('dimension', radius, toDimension, (k) => `--or-radius-${k}`),
123
+ font: {
124
+ family: group('fontFamily', typography.fontFamily, toFontFamily, (k) => `--or-font-${k}`),
125
+ size: group('dimension', typography.fontSize, toDimension, (k) => `--or-text-${k}`),
126
+ weight: group('fontWeight', typography.fontWeight, Number, (k) => `--or-weight-${k}`),
127
+ 'line-height': group('number', typography.lineHeight, Number, (k) => `--or-leading-${k}`, 'Multiplicador do tamanho da fonte (sem unidade).'),
128
+ 'letter-spacing': group('number', typography.letterSpacing, (v) => parseFloat(v), (k) => `--or-tracking-${k}`, 'Em em (fração do tamanho da fonte): o DTCG só aceita px e rem em dimension.'),
129
+ },
130
+ motion: {
131
+ duration: group('duration', motion.duration, toDuration, (k) => `--or-duration-${k}`),
132
+ easing: group('cubicBezier', motion.easing, toCubicBezier, (k) => `--or-ease-${k}`),
133
+ distance: group('dimension', motion.distance, toDimension, (k) => `--or-distance-${k}`),
134
+ stagger: { $type: 'duration', ...token(toDuration(motion.stagger), '--or-stagger') },
135
+ },
136
+ };
137
+ }
138
+ const colorValue = (value) => isPrimitiveName(value) ? `{${primitivePath(value)}}` : toDtcgColor(value);
139
+ /**
140
+ * Tokens semânticos de um tema (um modo): cada `--or-color-*` como alias para a
141
+ * primitiva de origem, ou como cor crua quando o tema usa transparência/ajuste fino.
142
+ */
143
+ export function buildThemeTokens(name) {
144
+ const entries = [
145
+ ...Object.entries(themeDefinitions[name]),
146
+ ...Object.entries(vizDefinition(vizPalettes[name])),
147
+ ];
148
+ return {
149
+ color: {
150
+ $type: 'color',
151
+ ...Object.fromEntries(entries.map(([key, value]) => [key, token(colorValue(value), `--or-color-${key}`)])),
152
+ },
153
+ };
154
+ }
155
+ /** Todos os conjuntos, na ordem de resolução: primitivas primeiro, depois um por tema. */
156
+ export function buildTokenSets() {
157
+ return {
158
+ [dtcgSets.primitives]: buildPrimitiveTokens(),
159
+ ...Object.fromEntries(themeNames.map((name) => [dtcgSets.theme(name), buildThemeTokens(name)])),
160
+ };
161
+ }
162
+ /** `$themes` do Tokens Studio: grupo "Orange / Primitivas" (1 modo) e "Orange / Tema" (4 modos). */
163
+ export function buildTokensStudioThemes() {
164
+ return [
165
+ {
166
+ id: 'orange-primitivas',
167
+ name: primitivesMode,
168
+ group: figmaCollections.primitives,
169
+ selectedTokenSets: { [dtcgSets.primitives]: 'enabled' },
170
+ },
171
+ ...themeNames.map((name) => ({
172
+ id: `orange-tema-${name}`,
173
+ name,
174
+ group: figmaCollections.theme,
175
+ selectedTokenSets: { [dtcgSets.primitives]: 'source', [dtcgSets.theme(name)]: 'enabled' },
176
+ })),
177
+ ];
178
+ }
179
+ const metadata = () => ({ tokenSetOrder: Object.keys(buildTokenSets()) });
180
+ /**
181
+ * Um arquivo DTCG por conjunto (`primitivas.json`, `tema/dark.json`…) mais o
182
+ * `$themes.json` e o `$metadata.json` do Tokens Studio (sincronização em pasta).
183
+ */
184
+ export function buildDtcgFiles() {
185
+ return {
186
+ ...Object.fromEntries(Object.entries(buildTokenSets()).map(([set, tokens]) => [`${set}.json`, tokens])),
187
+ '$themes.json': buildTokensStudioThemes(),
188
+ '$metadata.json': metadata(),
189
+ };
190
+ }
191
+ /** Arquivo único (`tokens.dtcg.json`): os conjuntos lado a lado, mais `$themes` e `$metadata`. */
192
+ export function buildDtcgBundle() {
193
+ return { ...buildTokenSets(), $themes: buildTokensStudioThemes(), $metadata: metadata() };
194
+ }
195
+ /* -------------------------------------------------------------------------- */
196
+ /* Leitura */
197
+ /* -------------------------------------------------------------------------- */
198
+ const isToken = (node) => typeof node === 'object' && node !== null && '$value' in node;
199
+ /** Lista os tokens de um grupo com o caminho completo e o tipo (e a descrição) herdados. */
200
+ export function flattenTokens(root, path = [], inherited = {}) {
201
+ const context = {
202
+ type: root.$type ?? inherited.type,
203
+ description: root.$description ?? inherited.description,
204
+ };
205
+ return Object.entries(root).flatMap(([key, node]) => {
206
+ if (key.startsWith('$') || typeof node !== 'object' || node === null)
207
+ return [];
208
+ if (isToken(node)) {
209
+ const type = node.$type ?? context.type;
210
+ if (!type)
211
+ throw new Error(`Token sem $type: ${[...path, key].join('.')}`);
212
+ const description = node.$description ?? context.description;
213
+ return [{ path: [...path, key], type, token: node, ...(description ? { description } : {}) }];
214
+ }
215
+ return flattenTokens(node, [...path, key], context);
216
+ });
217
+ }