@massimo-cassandro/minimo 0.2.0 → 0.2.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/package.json +2 -2
- package/src/components/spinner/readme.md +1 -0
- package/src/design-tokens/TODO.md +2 -0
- package/src/design-tokens-utilities/build-tokens.mjs +501 -0
- package/src/design-tokens-utilities/check-unresolved-custom-props.mjs +131 -0
- package/src/design-tokens-utilities/config-templates/tokens-config.mjs +34 -0
- package/src/design-tokens-utilities/readme.md +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massimo-cassandro/minimo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Light css framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/massimo-cassandro/minimo#readme",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"main": "./src/index.js",
|
|
27
27
|
"files": [
|
|
28
|
-
"src/**/*.{js,mjs,css,svg,tokens.json,tokens.jsonc}"
|
|
28
|
+
"src/**/*.{js,mjs,css,svg,tokens.json,tokens.jsonc,md}"
|
|
29
29
|
],
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@massimo-cassandro/dev-updater": "^2.1.0",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Gli spinner hanno tutti la stessa classe, presumendo che se ne usi solo una per volta...
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// StyleDictionary v.5
|
|
2
|
+
import StyleDictionary from 'style-dictionary';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import { styleText } from 'node:util';
|
|
6
|
+
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
|
|
9
|
+
import stylelint from 'stylelint';
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Risoluzione del config file tramite flag --config
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const configFlagIndex = args.indexOf('--config');
|
|
16
|
+
|
|
17
|
+
if (configFlagIndex === -1 || !args[configFlagIndex + 1]) {
|
|
18
|
+
console.error('Errore: devi specificare il file di configurazione con --config ./path/to/config/file');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let customPropsCount;
|
|
23
|
+
|
|
24
|
+
// Il percorso passato è relativo alla CWD (root del progetto)
|
|
25
|
+
const configArgPath = args[configFlagIndex + 1];
|
|
26
|
+
const configAbsPath = path.resolve(process.cwd(), configArgPath);
|
|
27
|
+
|
|
28
|
+
// La directory del config file: usata come base per risolvere i percorsi interni
|
|
29
|
+
const configDir = path.dirname(configAbsPath);
|
|
30
|
+
|
|
31
|
+
// Importa il config dinamicamente (usa fileURL per compatibilità ESM su Windows)
|
|
32
|
+
const buildConfig = (await import(pathToFileURL(configAbsPath))).default;
|
|
33
|
+
|
|
34
|
+
// Helper: risolve un percorso relativo rispetto alla dir del config file.
|
|
35
|
+
// Se il percorso è già assoluto lo lascia intatto.
|
|
36
|
+
const resolveFromConfig = (p) => path.resolve(configDir, p);
|
|
37
|
+
|
|
38
|
+
// Risolve tutti i percorsi dichiarati nel config
|
|
39
|
+
const stylelintConfigPath = resolveFromConfig(buildConfig.stylelintConfigPath);
|
|
40
|
+
const buildPath = resolveFromConfig(buildConfig.buildPath);
|
|
41
|
+
const destFile = buildConfig.destFile; // nome file, non un percorso da risolvere
|
|
42
|
+
|
|
43
|
+
// source può contenere glob/percorsi: li risolviamo tutti rispetto alla configDir.
|
|
44
|
+
// path.resolve non gestisce i glob, quindi usiamo path.posix per mantenere le slash
|
|
45
|
+
// forward anche su Windows (fast-glob le richiede).
|
|
46
|
+
const source = buildConfig.source.map((s) => {
|
|
47
|
+
if (path.isAbsolute(s)) return s; // già assoluto, niente da fare
|
|
48
|
+
|
|
49
|
+
// Unisce configDir + s e normalizza, poi ri-converte in forward-slash per fast-glob
|
|
50
|
+
const joined = path.join(configDir, s);
|
|
51
|
+
// Su Windows path.join usa '\', fast-glob vuole '/': normalizziamo sempre
|
|
52
|
+
return joined.split(path.sep).join('/');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const stylelintConfig = await import(pathToFileURL(stylelintConfigPath)).then(m => m.default);
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// 1. Shadow Transform
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
StyleDictionary.registerTransform({
|
|
61
|
+
name: 'shadow/css',
|
|
62
|
+
type: 'value',
|
|
63
|
+
transitive: true,
|
|
64
|
+
filter: (token) => (token.$type === 'shadow' || token.type === 'shadow'),
|
|
65
|
+
transform: (token) => {
|
|
66
|
+
const original = token.original?.$value ?? token.original?.value;
|
|
67
|
+
|
|
68
|
+
const value = (typeof original === 'string' && original.startsWith('{'))
|
|
69
|
+
? (token.$value ?? token.value)
|
|
70
|
+
: (original ?? token.$value ?? token.value);
|
|
71
|
+
|
|
72
|
+
const shadows = Array.isArray(value) ? value : [value];
|
|
73
|
+
|
|
74
|
+
if (shadows.length === 1 && typeof shadows[0] === 'string') {
|
|
75
|
+
return shadows[0];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const BASE_FONT_SIZE = 16;
|
|
79
|
+
const toRem = (val) => {
|
|
80
|
+
if (val === undefined || val === null) return '0';
|
|
81
|
+
const str = String(val).trim();
|
|
82
|
+
const num = parseFloat(str);
|
|
83
|
+
if (isNaN(num)) return str;
|
|
84
|
+
if (num === 0) return '0';
|
|
85
|
+
if (str.endsWith('px')) return `${num / BASE_FONT_SIZE}rem`;
|
|
86
|
+
return str;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return shadows
|
|
90
|
+
.map((s) => {
|
|
91
|
+
const offsetX = toRem(s.offsetX ?? s.x);
|
|
92
|
+
const offsetY = toRem(s.offsetY ?? s.y);
|
|
93
|
+
const blur = toRem(s.blur);
|
|
94
|
+
const spread = toRem(s.spread);
|
|
95
|
+
const color = s.color ?? 'transparent';
|
|
96
|
+
const inset = s.inset ? 'inset ' : '';
|
|
97
|
+
return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
|
|
98
|
+
})
|
|
99
|
+
.join(', ');
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// 2. PX to REM Transform
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
StyleDictionary.registerTransform({
|
|
107
|
+
name: 'size/pxToRem-smart',
|
|
108
|
+
type: 'value',
|
|
109
|
+
filter: (token) => {
|
|
110
|
+
const value = token.$value || token.value;
|
|
111
|
+
return (token.$type === 'dimension' || token.type === 'dimension')
|
|
112
|
+
&& typeof value === 'string'
|
|
113
|
+
&& !value.endsWith('rem');
|
|
114
|
+
},
|
|
115
|
+
transform: (token) => {
|
|
116
|
+
const baseFontSize = 16;
|
|
117
|
+
const value = token.$value || token.value;
|
|
118
|
+
const floatValue = parseFloat(value);
|
|
119
|
+
|
|
120
|
+
if (isNaN(floatValue)) return value;
|
|
121
|
+
|
|
122
|
+
return `${floatValue / baseFontSize}rem`;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// 3. Color passthrough
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
StyleDictionary.registerTransform({
|
|
130
|
+
name: 'color/css-modern',
|
|
131
|
+
type: 'value',
|
|
132
|
+
transitive: true,
|
|
133
|
+
filter: (token) => token.$type === 'color' || token.type === 'color',
|
|
134
|
+
transform: (token) => {
|
|
135
|
+
const value = token.$value ?? token.value;
|
|
136
|
+
if (typeof value === 'string') return value;
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// 4. Gradient Transform
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
StyleDictionary.registerTransform({
|
|
145
|
+
name: 'gradient/css',
|
|
146
|
+
type: 'value',
|
|
147
|
+
transitive: true,
|
|
148
|
+
filter: (token) => token.$type === 'gradient' || token.type === 'gradient',
|
|
149
|
+
transform: (token) => {
|
|
150
|
+
const original = token.original?.$value ?? token.original?.value;
|
|
151
|
+
|
|
152
|
+
const value = (typeof original === 'string' && original.startsWith('{'))
|
|
153
|
+
? (token.$value ?? token.value)
|
|
154
|
+
: (original ?? token.$value ?? token.value);
|
|
155
|
+
|
|
156
|
+
if (typeof value === 'string') return value;
|
|
157
|
+
|
|
158
|
+
const { type = 'linear', angle = 90, stops = [] } = value;
|
|
159
|
+
|
|
160
|
+
const stopsList = stops
|
|
161
|
+
.map((s) => {
|
|
162
|
+
let color = s.color ?? 'transparent';
|
|
163
|
+
if (s.alpha !== undefined) {
|
|
164
|
+
color = `color-mix(in srgb, ${color} ${s.alpha * 100}%, transparent)`;
|
|
165
|
+
}
|
|
166
|
+
const position = s.position !== undefined ? ` ${s.position * 100}%` : '';
|
|
167
|
+
return `${color}${position}`;
|
|
168
|
+
})
|
|
169
|
+
.join(', ');
|
|
170
|
+
|
|
171
|
+
if (type === 'linear') return `linear-gradient(${angle}deg, ${stopsList})`;
|
|
172
|
+
if (type === 'radial') return `radial-gradient(circle, ${stopsList})`;
|
|
173
|
+
if (type === 'conic') return `conic-gradient(from ${angle}deg, ${stopsList})`;
|
|
174
|
+
|
|
175
|
+
return `linear-gradient(${angle}${typeof angle === 'number'? 'deg' : ''}, ${stopsList})`;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// 5. Composite Transform (border, outline, transition, animation)
|
|
181
|
+
// Tutti i tipi che hanno un shorthand CSS diretto.
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
StyleDictionary.registerTransform({
|
|
184
|
+
name: 'composite/css',
|
|
185
|
+
type: 'value',
|
|
186
|
+
transitive: true,
|
|
187
|
+
filter: (token) => {
|
|
188
|
+
const type = token.$type ?? token.type;
|
|
189
|
+
const value = token.$value ?? token.value;
|
|
190
|
+
return (
|
|
191
|
+
typeof value === 'object' &&
|
|
192
|
+
value !== null &&
|
|
193
|
+
!Array.isArray(value) &&
|
|
194
|
+
['border', 'outline', 'transition', 'animation'].includes(type)
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
transform: (token) => {
|
|
198
|
+
const type = token.$type ?? token.type;
|
|
199
|
+
const v = token.$value ?? token.value;
|
|
200
|
+
|
|
201
|
+
if (type === 'border') {
|
|
202
|
+
const parts = [v.width ?? '1px'];
|
|
203
|
+
if (v.style !== undefined && v.style !== null && v.style !== '') parts.push(v.style);
|
|
204
|
+
parts.push(v.color ?? 'transparent');
|
|
205
|
+
return parts.join(' ');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (type === 'outline') {
|
|
209
|
+
return `${v.width ?? '1px'} ${v.style ?? 'solid'} ${v.color ?? 'transparent'}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (type === 'transition') {
|
|
213
|
+
// Ordine: duration | timing-function | delay | property
|
|
214
|
+
return [
|
|
215
|
+
v.duration ?? '0s',
|
|
216
|
+
v.timingFunction ?? 'ease',
|
|
217
|
+
v.delay ?? '0s',
|
|
218
|
+
v.property ?? 'all',
|
|
219
|
+
].join(' ');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (type === 'animation') {
|
|
223
|
+
// Ordine: duration | timing-function | delay | iteration-count |
|
|
224
|
+
// direction | fill-mode | play-state | name
|
|
225
|
+
return [
|
|
226
|
+
v.duration ?? '0s',
|
|
227
|
+
v.timingFunction ?? 'ease',
|
|
228
|
+
v.delay ?? '0s',
|
|
229
|
+
v.iterationCount ?? '1',
|
|
230
|
+
v.direction ?? 'normal',
|
|
231
|
+
v.fillMode ?? 'none',
|
|
232
|
+
v.playState ?? 'running',
|
|
233
|
+
v.name ?? 'none',
|
|
234
|
+
].join(' ');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// 6. Typography Transform
|
|
241
|
+
// Restituisce l'oggetto così com'è: il format lo esploderà in più props.
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
StyleDictionary.registerTransform({
|
|
244
|
+
name: 'typography/css',
|
|
245
|
+
type: 'value',
|
|
246
|
+
transitive: true,
|
|
247
|
+
filter: (token) => token.$type === 'typography' || token.type === 'typography',
|
|
248
|
+
transform: (token) => token.$value ?? token.value,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// Format: css/variables-sorted
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
StyleDictionary.registerFormat({
|
|
255
|
+
name: 'css/variables-sorted',
|
|
256
|
+
format: ({ dictionary, options }) => {
|
|
257
|
+
const selector = options.selector ?? ':root';
|
|
258
|
+
|
|
259
|
+
const tokenByPath = {};
|
|
260
|
+
for (const token of dictionary.allTokens) {
|
|
261
|
+
tokenByPath[token.path.join('.')] = token;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const resolveRefs = (str) =>
|
|
265
|
+
String(str).replace(/\{([^}]+)\}/g, (_, p) => {
|
|
266
|
+
const ref = tokenByPath[p];
|
|
267
|
+
return ref ? `var(--${ref.name})` : `{${p}}`;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const BASE_FONT_SIZE = 16;
|
|
271
|
+
const toRem = (val) => {
|
|
272
|
+
if (val === undefined || val === null) return '0';
|
|
273
|
+
const str = String(val).trim();
|
|
274
|
+
const num = parseFloat(str);
|
|
275
|
+
if (isNaN(num) || num === 0) return num === 0 ? '0' : str;
|
|
276
|
+
if (str.endsWith('px')) return `${num / BASE_FONT_SIZE}rem`;
|
|
277
|
+
return str;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const buildShadow = (original) => {
|
|
281
|
+
const shadows = Array.isArray(original) ? original : [original];
|
|
282
|
+
return shadows.map((s) => {
|
|
283
|
+
const offsetX = toRem(s.offsetX ?? s.x);
|
|
284
|
+
const offsetY = toRem(s.offsetY ?? s.y);
|
|
285
|
+
const blur = toRem(s.blur);
|
|
286
|
+
const spread = toRem(s.spread);
|
|
287
|
+
const color = resolveRefs(s.color ?? 'transparent');
|
|
288
|
+
const inset = s.inset ? 'inset ' : '';
|
|
289
|
+
return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
|
|
290
|
+
}).join(', ');
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const buildGradient = (original) => {
|
|
294
|
+
const { type = 'linear', angle = 90, stops = [] } = original;
|
|
295
|
+
const stopsList = stops.map((s) => {
|
|
296
|
+
let color = resolveRefs(s.color ?? 'transparent');
|
|
297
|
+
if (s.alpha !== undefined) {
|
|
298
|
+
color = `color-mix(in srgb, ${color} ${s.alpha * 100}%, transparent)`;
|
|
299
|
+
}
|
|
300
|
+
const position = s.position !== undefined ? ` ${s.position * 100}%` : '';
|
|
301
|
+
return `${color}${position}`;
|
|
302
|
+
}).join(', ');
|
|
303
|
+
|
|
304
|
+
if (type === 'radial') return `radial-gradient(circle, ${stopsList})`;
|
|
305
|
+
if (type === 'conic') return `conic-gradient(from ${angle}deg, ${stopsList})`;
|
|
306
|
+
return `linear-gradient(${angle}deg, ${stopsList})`;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// border e outline: shorthand con resolveRefs su tutti i campi; style è opzionale
|
|
310
|
+
const buildBorderLike = (original) => {
|
|
311
|
+
const parts = [resolveRefs(String(original.width ?? '1px'))];
|
|
312
|
+
if (original.style !== undefined && original.style !== null && original.style !== '') {
|
|
313
|
+
parts.push(resolveRefs(String(original.style)));
|
|
314
|
+
}
|
|
315
|
+
parts.push(resolveRefs(String(original.color ?? 'transparent')));
|
|
316
|
+
return parts.join(' ');
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// transition: resolveRefs su tutti i campi
|
|
320
|
+
const buildTransition = (original) => [
|
|
321
|
+
resolveRefs(String(original.duration ?? '0s')),
|
|
322
|
+
resolveRefs(String(original.timingFunction ?? 'ease')),
|
|
323
|
+
resolveRefs(String(original.delay ?? '0s')),
|
|
324
|
+
resolveRefs(String(original.property ?? 'all')),
|
|
325
|
+
].join(' ');
|
|
326
|
+
|
|
327
|
+
// animation: resolveRefs su tutti i campi
|
|
328
|
+
const buildAnimation = (original) => [
|
|
329
|
+
resolveRefs(String(original.duration ?? '0s')),
|
|
330
|
+
resolveRefs(String(original.timingFunction ?? 'ease')),
|
|
331
|
+
resolveRefs(String(original.delay ?? '0s')),
|
|
332
|
+
resolveRefs(String(original.iterationCount ?? '1')),
|
|
333
|
+
resolveRefs(String(original.direction ?? 'normal')),
|
|
334
|
+
resolveRefs(String(original.fillMode ?? 'none')),
|
|
335
|
+
resolveRefs(String(original.playState ?? 'running')),
|
|
336
|
+
resolveRefs(String(original.name ?? 'none')),
|
|
337
|
+
].join(' ');
|
|
338
|
+
|
|
339
|
+
const buildTypography = (tokenName, original) => {
|
|
340
|
+
const {
|
|
341
|
+
fontStyle,
|
|
342
|
+
fontVariant,
|
|
343
|
+
fontWeight,
|
|
344
|
+
fontStretch,
|
|
345
|
+
fontSize,
|
|
346
|
+
lineHeight,
|
|
347
|
+
fontFamily,
|
|
348
|
+
// queste non entrano nel shorthand font
|
|
349
|
+
letterSpacing,
|
|
350
|
+
textTransform,
|
|
351
|
+
textDecoration,
|
|
352
|
+
} = original;
|
|
353
|
+
|
|
354
|
+
const lines = [];
|
|
355
|
+
|
|
356
|
+
// shorthand font se ci sono size e family (requisiti minimi CSS)
|
|
357
|
+
if (fontSize && fontFamily) {
|
|
358
|
+
const parts = [];
|
|
359
|
+
if (fontStyle) parts.push(resolveRefs(String(fontStyle)));
|
|
360
|
+
if (fontVariant) parts.push(resolveRefs(String(fontVariant)));
|
|
361
|
+
if (fontWeight) parts.push(resolveRefs(String(fontWeight)));
|
|
362
|
+
if (fontStretch) parts.push(resolveRefs(String(fontStretch)));
|
|
363
|
+
|
|
364
|
+
const sizeSlash = lineHeight
|
|
365
|
+
? `${resolveRefs(String(fontSize))}/${resolveRefs(String(lineHeight))}`
|
|
366
|
+
: resolveRefs(String(fontSize));
|
|
367
|
+
|
|
368
|
+
parts.push(sizeSlash);
|
|
369
|
+
parts.push(resolveRefs(String(fontFamily)));
|
|
370
|
+
|
|
371
|
+
lines.push(` --${tokenName}-font: ${parts.join(' ')};`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// proprietà che non entrano nel shorthand font
|
|
375
|
+
const EXTRA_PROPS = {
|
|
376
|
+
letterSpacing: ['letter-spacing', letterSpacing],
|
|
377
|
+
textTransform: ['text-transform', textTransform],
|
|
378
|
+
textDecoration: ['text-decoration', textDecoration],
|
|
379
|
+
// fallback: se mancano size o family, le esponiamo comunque separatamente
|
|
380
|
+
...(!fontSize ? { fontSize: ['font-size', original.fontSize] } : {}),
|
|
381
|
+
...(!fontFamily ? { fontFamily: ['font-family', original.fontFamily] } : {}),
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
for (const [, [cssProp, val]] of Object.entries(EXTRA_PROPS)) {
|
|
385
|
+
if (val !== undefined) {
|
|
386
|
+
lines.push(` --${tokenName}-${cssProp}: ${resolveRefs(String(val))};`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return lines.join('\n');
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const lines = dictionary.allTokens
|
|
394
|
+
.slice()
|
|
395
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
396
|
+
.flatMap((token) => {
|
|
397
|
+
const type = token.$type ?? token.type;
|
|
398
|
+
const orig = token.original?.$value ?? token.original?.value;
|
|
399
|
+
|
|
400
|
+
// typography: genera più righe, una per proprietà
|
|
401
|
+
if (type === 'typography') {
|
|
402
|
+
return [buildTypography(token.name, orig ?? {})];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let value;
|
|
406
|
+
|
|
407
|
+
if (type === 'shadow' && options.outputReferences) {
|
|
408
|
+
value = (typeof orig === 'string' && orig.startsWith('{'))
|
|
409
|
+
? resolveRefs(orig)
|
|
410
|
+
: buildShadow(orig);
|
|
411
|
+
|
|
412
|
+
} else if (type === 'gradient' && options.outputReferences) {
|
|
413
|
+
value = (typeof orig === 'string' && orig.startsWith('{'))
|
|
414
|
+
? resolveRefs(orig)
|
|
415
|
+
: buildGradient(orig);
|
|
416
|
+
|
|
417
|
+
} else if ((type === 'border' || type === 'outline') && options.outputReferences) {
|
|
418
|
+
value = (typeof orig === 'string' && orig.startsWith('{'))
|
|
419
|
+
? resolveRefs(orig)
|
|
420
|
+
: buildBorderLike(orig);
|
|
421
|
+
|
|
422
|
+
} else if (type === 'transition' && options.outputReferences) {
|
|
423
|
+
value = (typeof orig === 'string' && orig.startsWith('{'))
|
|
424
|
+
? resolveRefs(orig)
|
|
425
|
+
: buildTransition(orig);
|
|
426
|
+
|
|
427
|
+
} else if (type === 'animation' && options.outputReferences) {
|
|
428
|
+
value = (typeof orig === 'string' && orig.startsWith('{'))
|
|
429
|
+
? resolveRefs(orig)
|
|
430
|
+
: buildAnimation(orig);
|
|
431
|
+
|
|
432
|
+
} else if (options.outputReferences && typeof orig === 'string' && orig.includes('{')) {
|
|
433
|
+
value = resolveRefs(orig);
|
|
434
|
+
|
|
435
|
+
} else {
|
|
436
|
+
value = String(token.$value ?? token.value);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return [` --${token.name}: ${value};`];
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
customPropsCount = lines.length;
|
|
443
|
+
return `${selector} {\n${lines.join('\n')}\n}\n`;
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
// Style Dictionary config
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
450
|
+
const config = {
|
|
451
|
+
source,
|
|
452
|
+
|
|
453
|
+
log: {
|
|
454
|
+
verbosity: 'verbose'
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
platforms: {
|
|
458
|
+
css: {
|
|
459
|
+
buildPath: buildPath + '/',
|
|
460
|
+
transforms: [
|
|
461
|
+
'attribute/cti',
|
|
462
|
+
'name/kebab',
|
|
463
|
+
'time/seconds',
|
|
464
|
+
'asset/url',
|
|
465
|
+
'size/pxToRem-smart',
|
|
466
|
+
'color/css-modern',
|
|
467
|
+
'shadow/css',
|
|
468
|
+
'gradient/css',
|
|
469
|
+
'composite/css', // border, outline, transition, animation
|
|
470
|
+
'typography/css', // typography (esploso nel format)
|
|
471
|
+
],
|
|
472
|
+
files: [
|
|
473
|
+
{
|
|
474
|
+
destination: destFile,
|
|
475
|
+
format: 'css/variables-sorted',
|
|
476
|
+
options: {
|
|
477
|
+
outputReferences: true,
|
|
478
|
+
showFileHeader: true,
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const sd = new StyleDictionary(config);
|
|
487
|
+
await sd.buildAllPlatforms();
|
|
488
|
+
|
|
489
|
+
await stylelint.lint({
|
|
490
|
+
config: stylelintConfig,
|
|
491
|
+
files: [ path.join(buildPath, destFile) ],
|
|
492
|
+
fix: true
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
console.log(styleText(['yellow'], `[build-tokens] config file : ${configAbsPath.replace(homedir(), '~')}` ));
|
|
496
|
+
console.log(styleText(['yellow'], `[build-tokens] source : ${source.map(i => i.replace(homedir(), '~'))}` ));
|
|
497
|
+
console.log(styleText(['yellow'], `[build-tokens] dest file : ${path.join(buildPath, destFile).replace(homedir(), '~')}` ));
|
|
498
|
+
|
|
499
|
+
console.log(styleText(['green'], `[build-tokens] ${customPropsCount} custom props elaborate`));
|
|
500
|
+
|
|
501
|
+
console.log(styleText(['green'], '**** FINE ****'));
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console */
|
|
4
|
+
|
|
5
|
+
// rivela eventuale custom props css nei file della dir data non presenti nel file css di definizione
|
|
6
|
+
// uso: node check-unresolved-custom-props.mjs --config ./path/to-config.mjs
|
|
7
|
+
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
11
|
+
import { dirname } from 'path';
|
|
12
|
+
import { glob } from 'node:fs/promises';
|
|
13
|
+
import { styleText, parseArgs } from 'node:util';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// 1. Configurazione e parsing dei flag
|
|
19
|
+
const options = {
|
|
20
|
+
config: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
short: 'c',
|
|
23
|
+
},
|
|
24
|
+
// Qui in futuro potrai aggiungere altri flag, es:
|
|
25
|
+
// verbose: { type: 'boolean', short: 'v' }
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const { values } = parseArgs({ options });
|
|
29
|
+
|
|
30
|
+
if (!values.config) {
|
|
31
|
+
console.error(styleText(['red'], 'Errore: Devi fornire il percorso del config con il flag --config o -c'));
|
|
32
|
+
console.log('Esempio: node check-unresolved-custom-props.mjs --config ./config.mjs');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const absoluteConfigPath = path.resolve(process.cwd(), values.config);
|
|
37
|
+
const configDir = path.dirname(absoluteConfigPath);
|
|
38
|
+
|
|
39
|
+
async function run() {
|
|
40
|
+
try {
|
|
41
|
+
|
|
42
|
+
const { default: config } = await import(pathToFileURL(absoluteConfigPath).href);
|
|
43
|
+
console.log(config);
|
|
44
|
+
const custom_prop_file_path = path.resolve(configDir, path.join(config.buildPath, config.destFile));
|
|
45
|
+
const custom_prop_filename = path.basename(custom_prop_file_path);
|
|
46
|
+
const checkdir = path.resolve(configDir, config.dir_to_check);
|
|
47
|
+
const exclude = config.exclude_pattern;
|
|
48
|
+
|
|
49
|
+
const files = [];
|
|
50
|
+
for await (const entry of glob(path.join(checkdir, '/**/*.css'))) {
|
|
51
|
+
if (!entry.endsWith(custom_prop_filename)) {
|
|
52
|
+
files.push(entry);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!fs.existsSync(custom_prop_file_path)) {
|
|
57
|
+
throw new Error(`File delle proprietà non trovato: ${custom_prop_file_path}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// lista custom props definiti
|
|
61
|
+
const cssContent = fs.readFileSync(custom_prop_file_path, 'utf-8');
|
|
62
|
+
const regex = /--([a-z0-9-]+)(?=\s*:)/g;
|
|
63
|
+
const propertyNamesList = [...cssContent.matchAll(regex)].map(match => `--${match[1]}`);
|
|
64
|
+
// extra_custom_props_files
|
|
65
|
+
|
|
66
|
+
(config.extra_custom_props_files??[]).forEach(extraCustomePropsFile => {
|
|
67
|
+
|
|
68
|
+
const extraCustomPropsContent = fs.readFileSync(
|
|
69
|
+
path.resolve(configDir, extraCustomePropsFile),
|
|
70
|
+
'utf-8');
|
|
71
|
+
|
|
72
|
+
const extraMatches = [...extraCustomPropsContent.matchAll(regex)].map(match => `--${match[1]}`);
|
|
73
|
+
propertyNamesList.push(...extraMatches);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// rilevamento custom props nei vari file css
|
|
79
|
+
const regex2 = /var\(\s*(--[a-z0-9-]+)\s*(,.*?)?\)/g;
|
|
80
|
+
const unresolved_props = [];
|
|
81
|
+
const check = []; // per controllo aggiuntivo esecuzione
|
|
82
|
+
|
|
83
|
+
files.forEach(file => {
|
|
84
|
+
const fileContent = fs.readFileSync(file, 'utf-8');
|
|
85
|
+
// const filename = path.relative(checkdir, file);
|
|
86
|
+
const filename = path.relative(configDir, file);
|
|
87
|
+
const matches = [...fileContent.matchAll(regex2)];
|
|
88
|
+
|
|
89
|
+
matches.forEach(match => {
|
|
90
|
+
const cprop = match[1];
|
|
91
|
+
const isExcluded = exclude.some(exRegex => exRegex.test(cprop));
|
|
92
|
+
const isDefined = propertyNamesList.includes(cprop);
|
|
93
|
+
|
|
94
|
+
const lineNumber = fileContent.substring(0, match.index).split('\n').length;
|
|
95
|
+
if (!isExcluded && !isDefined) {
|
|
96
|
+
// unresolved_props.push(`${filename}:${lineNumber} -> var(${cprop})`);
|
|
97
|
+
unresolved_props.push({file: filename, line: lineNumber, prop: cprop});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
check.push(`${filename}:${lineNumber} - ${cprop} - unresolved: ${(!isExcluded && !isDefined) ? 'Y' : 'N'}`);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
unresolved_props.sort((a,b) => a.file.localeCompare(b.file));
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
const result_file = path.resolve(configDir, 'unresolved-props.md');
|
|
108
|
+
|
|
109
|
+
console.log(styleText(['green'],
|
|
110
|
+
`${unresolved_props.length} custom properties non impostate ` +
|
|
111
|
+
`-> ${path.relative(process.cwd(), result_file)}`)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Output
|
|
115
|
+
fs.writeFileSync(result_file, unresolved_props
|
|
116
|
+
.map(item => `* [${path.basename(item.file)} :: ${item.line}](${item.file}#L${item.line}) -> var(${item.prop})`)
|
|
117
|
+
.join('\n'), 'utf-8');
|
|
118
|
+
|
|
119
|
+
// fs.writeFileSync(path.resolve(configDir, 'full-result.txt'), check.join('\n'), 'utf-8');
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
console.log(styleText(['green'], '**** FINE ****'));
|
|
123
|
+
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.log(styleText(['redBright'], err.stack));
|
|
126
|
+
// console.error(styleText(['red'], `Errore: ${err.message}`));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
run();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
// TODO spostare percorsi minimo su node modules
|
|
6
|
+
const minimo_path = path.resolve(homedir(), 'Sites/minimo/src');
|
|
7
|
+
|
|
8
|
+
// path relativi a questo file
|
|
9
|
+
|
|
10
|
+
const config = {
|
|
11
|
+
// build tokens
|
|
12
|
+
stylelintConfigPath: '../../stylelint.config.mjs',
|
|
13
|
+
buildPath: '../src/css', // dir in cui salvare il file generato da style dictionary
|
|
14
|
+
destFile: 'custom-properties.css', // nome del file generato da style dictionary
|
|
15
|
+
// tokens files
|
|
16
|
+
source: [
|
|
17
|
+
// '../../node_modules/open-props/open-props.style-dictionary-tokens.json',
|
|
18
|
+
path.join(minimo_path, 'design-tokens/*.jsonc'),
|
|
19
|
+
path.join(minimo_path, 'components/**/*.tokens.jsonc'),
|
|
20
|
+
path.join(minimo_path, 'web-components/**/*.tokens.jsonc'),
|
|
21
|
+
'./figma-tokens-OK/*.jsonc',
|
|
22
|
+
],
|
|
23
|
+
|
|
24
|
+
// eventuali file aggiuntivi con custim props utilizzate nel progetto
|
|
25
|
+
extra_custom_props_files: [
|
|
26
|
+
// '../../node_modules/open-props/open-props.min.css'
|
|
27
|
+
],
|
|
28
|
+
dir_to_check: '../../src', // percorso della dir che include i css da analizzare
|
|
29
|
+
exclude_pattern: [ // patterns per l'esclusione dal controllo di alcune custom props
|
|
30
|
+
/^--_/,
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default config;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
```json
|
|
2
|
+
{
|
|
3
|
+
"build tokens": "node ~/Sites/minimo/utilities/build-tokens.mjs --config ./path/to/tokens-config.mjs",
|
|
4
|
+
"check unresolved props": "node ~/Sites/minimo/utilities/check-unresolved-custom-props.mjs --config ./path/to/tokens-config.mjs"
|
|
5
|
+
}
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
ln -s ~/Sites/minimo/src dest/_minimo-alias
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## TODO
|
|
13
|
+
|
|
14
|
+
* <https://github.com/georgeadamson/postcss-merge-selectors>
|
|
15
|
+
* <https://medium.com/@ashusingh584/chokidar-11290855e2cb>
|
|
16
|
+
* <https://github.com/xtivia/style-dictionary-watcher>
|