swatchkit 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.js +82 -90
- package/package.json +2 -2
- package/src/blueprints/colors.json +11 -0
- package/src/blueprints/compositions/cluster.css +20 -0
- package/src/blueprints/compositions/flow.css +10 -0
- package/src/blueprints/compositions/grid.css +36 -0
- package/src/blueprints/compositions/index.css +12 -0
- package/src/blueprints/compositions/prose.css +89 -0
- package/src/blueprints/compositions/repel.css +23 -0
- package/src/blueprints/compositions/sidebar.css +44 -0
- package/src/blueprints/compositions/switcher.css +32 -0
- package/src/blueprints/compositions/wrapper.css +14 -0
- package/src/blueprints/fonts.json +24 -0
- package/src/blueprints/global/elements.css +609 -0
- package/src/blueprints/global/index.css +13 -0
- package/src/blueprints/global/reset.css +91 -0
- package/src/blueprints/global/variables.css +58 -0
- package/src/blueprints/main.css +27 -0
- package/src/blueprints/spacing.json +19 -0
- package/src/blueprints/swatches/hello.css +11 -0
- package/src/blueprints/swatches/index.css +7 -0
- package/src/blueprints/swatchkit-ui.css +79 -0
- package/src/blueprints/text-leading.json +13 -0
- package/src/blueprints/text-sizes.json +21 -0
- package/src/blueprints/text-weights.json +11 -0
- package/src/blueprints/utilities/index.css +9 -0
- package/src/blueprints/utilities/region.css +12 -0
- package/src/blueprints/utilities/visually-hidden.css +18 -0
- package/src/blueprints/viewports.json +10 -0
- package/src/generators/index.js +437 -0
- package/src/layout.html +0 -1
- package/src/preview-layout.html +13 -0
- package/src/templates/compositions/cluster/description.html +7 -0
- package/src/templates/compositions/cluster/index.html +7 -0
- package/src/templates/compositions/flow/description.html +8 -0
- package/src/templates/compositions/flow/index.html +6 -0
- package/src/templates/compositions/grid/description.html +12 -0
- package/src/templates/compositions/grid/index.html +7 -0
- package/src/templates/compositions/prose/description.html +8 -0
- package/src/templates/compositions/prose/index.html +6 -0
- package/src/templates/compositions/repel/description.html +9 -0
- package/src/templates/compositions/repel/index.html +4 -0
- package/src/templates/compositions/sidebar/description.html +13 -0
- package/src/templates/compositions/sidebar/index.html +4 -0
- package/src/templates/compositions/switcher/description.html +8 -0
- package/src/templates/compositions/switcher/index.html +29 -0
- package/src/templates/compositions/wrapper/description.html +8 -0
- package/src/templates/compositions/wrapper/index.html +5 -0
- package/src/templates/hello/README.md +83 -0
- package/src/templates/hello/index.html +1 -0
- package/src/templates/prose.html +366 -0
- package/src/templates/script.js +41 -0
- package/src/templates/utilities/region/description.html +8 -0
- package/src/templates/utilities/region/index.html +5 -0
- package/src/templates/utilities/visually-hidden/description.html +7 -0
- package/src/templates/utilities/visually-hidden/index.html +7 -0
- package/src/tokens.js +428 -0
- package/src/utils/clamp-generator.js +38 -0
package/src/tokens.js
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const clampGenerator = require('./utils/clamp-generator');
|
|
4
|
+
|
|
5
|
+
function slugify(text) {
|
|
6
|
+
return text
|
|
7
|
+
.toString()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.trim()
|
|
10
|
+
.replace(/\s+/g, '-'); // Replace spaces with -
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function processTokens(tokensDir, cssDir) {
|
|
14
|
+
const outputFile = path.join(cssDir, 'tokens.css');
|
|
15
|
+
let cssContent = '/* AUTO-GENERATED by SwatchKit — do not edit manually. */\n';
|
|
16
|
+
cssContent += '/* Edit the JSON files in your tokens directory and rebuild. */\n\n';
|
|
17
|
+
cssContent += ':root {\n';
|
|
18
|
+
let hasTokens = false;
|
|
19
|
+
|
|
20
|
+
// Store structured data for utility generation
|
|
21
|
+
const tokensContext = {
|
|
22
|
+
colors: [],
|
|
23
|
+
textWeights: [],
|
|
24
|
+
textLeading: [],
|
|
25
|
+
textSizes: [],
|
|
26
|
+
spacing: [],
|
|
27
|
+
fonts: [],
|
|
28
|
+
viewports: {}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Helper to process a generic token file
|
|
32
|
+
function processFile(filename, contextKey) {
|
|
33
|
+
const filePath = path.join(tokensDir, filename);
|
|
34
|
+
if (!fs.existsSync(filePath)) return;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
38
|
+
const data = JSON.parse(fileContent);
|
|
39
|
+
|
|
40
|
+
if (data.items && Array.isArray(data.items)) {
|
|
41
|
+
hasTokens = true;
|
|
42
|
+
cssContent += ` /* ${data.title || filename} */\n`;
|
|
43
|
+
data.items.forEach(item => {
|
|
44
|
+
if (item.name && item.value) {
|
|
45
|
+
const slug = slugify(item.name);
|
|
46
|
+
cssContent += ` --${slug}: ${item.value};\n`;
|
|
47
|
+
|
|
48
|
+
// Store for utilities if a key is provided
|
|
49
|
+
if (contextKey && tokensContext[contextKey]) {
|
|
50
|
+
tokensContext[contextKey].push({ name: slug, value: item.value });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
cssContent += '\n';
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`[SwatchKit] Error processing ${filename}:`, error.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 1. Process Viewports (First, so they are available for fluid calculations)
|
|
62
|
+
const viewportsFile = path.join(tokensDir, 'viewports.json');
|
|
63
|
+
if (fs.existsSync(viewportsFile)) {
|
|
64
|
+
try {
|
|
65
|
+
const fileContent = fs.readFileSync(viewportsFile, 'utf-8');
|
|
66
|
+
const data = JSON.parse(fileContent);
|
|
67
|
+
|
|
68
|
+
if (data.items && Array.isArray(data.items)) {
|
|
69
|
+
// Build a lookup object for clamp-generator (keyed by name)
|
|
70
|
+
const viewportsLookup = {};
|
|
71
|
+
data.items.forEach(item => {
|
|
72
|
+
viewportsLookup[item.name] = item.value;
|
|
73
|
+
});
|
|
74
|
+
tokensContext.viewports = viewportsLookup;
|
|
75
|
+
|
|
76
|
+
hasTokens = true;
|
|
77
|
+
cssContent += ` /* ${data.title || 'Viewports'} */\n`;
|
|
78
|
+
|
|
79
|
+
data.items.forEach(item => {
|
|
80
|
+
if (item.name && item.value !== undefined) {
|
|
81
|
+
const slug = slugify(item.name);
|
|
82
|
+
// Append 'px' if it's a number
|
|
83
|
+
const cssValue = typeof item.value === 'number' ? `${item.value}px` : item.value;
|
|
84
|
+
cssContent += ` --${slug}: ${cssValue};\n`;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
cssContent += '\n';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`[SwatchKit] Error processing viewports.json:`, error.message);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Process Colors
|
|
96
|
+
processFile('colors.json', 'colors');
|
|
97
|
+
|
|
98
|
+
// 3. Process Text Weights
|
|
99
|
+
processFile('text-weights.json', 'textWeights');
|
|
100
|
+
|
|
101
|
+
// 4. Process Text Leading
|
|
102
|
+
const leadingFile = path.join(tokensDir, 'text-leading.json');
|
|
103
|
+
if (fs.existsSync(leadingFile)) {
|
|
104
|
+
try {
|
|
105
|
+
const fileContent = fs.readFileSync(leadingFile, 'utf-8');
|
|
106
|
+
const data = JSON.parse(fileContent);
|
|
107
|
+
|
|
108
|
+
if (data.base && data.ratio && data.items) {
|
|
109
|
+
hasTokens = true;
|
|
110
|
+
cssContent += ` /* ${data.title || 'Text Leading'} */\n`;
|
|
111
|
+
cssContent += ` --leading-scale-base: ${data.base};\n`;
|
|
112
|
+
cssContent += ` --leading-scale-ratio: ${data.ratio};\n`;
|
|
113
|
+
|
|
114
|
+
data.items.forEach(item => {
|
|
115
|
+
const slug = slugify(item.name);
|
|
116
|
+
let value;
|
|
117
|
+
|
|
118
|
+
if (item.value !== undefined) {
|
|
119
|
+
// Manual value (e.g. 1.5)
|
|
120
|
+
value = item.value;
|
|
121
|
+
cssContent += ` --${slug}: ${value};\n`;
|
|
122
|
+
} else if (item.step !== undefined) {
|
|
123
|
+
// Modular scale step (e.g. 1)
|
|
124
|
+
// We can't resolve calc() here for context easily, but utilities use the var anyway
|
|
125
|
+
value = `calc(var(--leading-scale-base) * pow(var(--leading-scale-ratio), ${item.step}))`;
|
|
126
|
+
cssContent += ` --${slug}: ${value};\n`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Store for utilities
|
|
130
|
+
if (tokensContext.textLeading) {
|
|
131
|
+
tokensContext.textLeading.push({ name: slug, value });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
cssContent += '\n';
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`[SwatchKit] Error processing text-leading.json:`, error.message);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 5. Process Text Sizes
|
|
142
|
+
const textSizesFile = path.join(tokensDir, 'text-sizes.json');
|
|
143
|
+
if (fs.existsSync(textSizesFile)) {
|
|
144
|
+
try {
|
|
145
|
+
const fileContent = fs.readFileSync(textSizesFile, 'utf-8');
|
|
146
|
+
const data = JSON.parse(fileContent);
|
|
147
|
+
|
|
148
|
+
if (data.items && Array.isArray(data.items)) {
|
|
149
|
+
hasTokens = true;
|
|
150
|
+
cssContent += ` /* ${data.title || 'Text Sizes'} */\n`;
|
|
151
|
+
|
|
152
|
+
// Check if we have viewports for fluid generation
|
|
153
|
+
const hasFluidData = tokensContext.viewports &&
|
|
154
|
+
tokensContext.viewports['viewport-min'] &&
|
|
155
|
+
tokensContext.viewports['viewport-max'];
|
|
156
|
+
|
|
157
|
+
const fileRatio = data.fluidRatio || 1.125;
|
|
158
|
+
const fluidItems = [];
|
|
159
|
+
const staticItems = [];
|
|
160
|
+
|
|
161
|
+
data.items.forEach(item => {
|
|
162
|
+
// If "value" is present, it's static (user explicit override)
|
|
163
|
+
if (item.value !== undefined) {
|
|
164
|
+
staticItems.push(item);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// If min or max is present, it's fluid (calculate missing side)
|
|
169
|
+
if (item.min !== undefined || item.max !== undefined) {
|
|
170
|
+
const ratio = item.fluidRatio || fileRatio;
|
|
171
|
+
let min = item.min;
|
|
172
|
+
let max = item.max;
|
|
173
|
+
|
|
174
|
+
if (min === undefined) min = parseFloat((max / ratio).toFixed(2));
|
|
175
|
+
if (max === undefined) max = parseFloat((min * ratio).toFixed(2));
|
|
176
|
+
|
|
177
|
+
fluidItems.push({ ...item, min, max });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Process Fluid Items
|
|
182
|
+
if (hasFluidData && fluidItems.length > 0) {
|
|
183
|
+
const fluidTokens = clampGenerator(fluidItems, tokensContext.viewports);
|
|
184
|
+
fluidTokens.forEach(item => {
|
|
185
|
+
const slug = slugify(item.name);
|
|
186
|
+
cssContent += ` --${slug}: ${item.value};\n`;
|
|
187
|
+
tokensContext.textSizes.push({ name: slug, value: item.value });
|
|
188
|
+
});
|
|
189
|
+
} else if (fluidItems.length > 0) {
|
|
190
|
+
console.warn('[SwatchKit] Fluid text sizes detected but viewports missing. Skipping fluid generation.');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Process Static Items (fallback or simple values)
|
|
194
|
+
staticItems.forEach(item => {
|
|
195
|
+
if (item.name && item.value) {
|
|
196
|
+
const slug = slugify(item.name);
|
|
197
|
+
cssContent += ` --${slug}: ${item.value};\n`;
|
|
198
|
+
tokensContext.textSizes.push({ name: slug, value: item.value });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
cssContent += '\n';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`[SwatchKit] Error processing text-sizes.json:`, error.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 6. Process Spacing
|
|
211
|
+
const spacingFile = path.join(tokensDir, 'spacing.json');
|
|
212
|
+
if (fs.existsSync(spacingFile)) {
|
|
213
|
+
try {
|
|
214
|
+
const fileContent = fs.readFileSync(spacingFile, 'utf-8');
|
|
215
|
+
const data = JSON.parse(fileContent);
|
|
216
|
+
|
|
217
|
+
if (data.items && Array.isArray(data.items)) {
|
|
218
|
+
hasTokens = true;
|
|
219
|
+
cssContent += ` /* ${data.title || 'Spacing'} */\n`;
|
|
220
|
+
|
|
221
|
+
// Check if we have viewports for fluid generation
|
|
222
|
+
const hasFluidData = tokensContext.viewports &&
|
|
223
|
+
tokensContext.viewports['viewport-min'] &&
|
|
224
|
+
tokensContext.viewports['viewport-max'];
|
|
225
|
+
|
|
226
|
+
const fileRatio = data.fluidRatio || 1.125;
|
|
227
|
+
const fluidItems = [];
|
|
228
|
+
const staticItems = [];
|
|
229
|
+
|
|
230
|
+
data.items.forEach(item => {
|
|
231
|
+
// If "value" is present, it's static (user explicit override)
|
|
232
|
+
if (item.value !== undefined) {
|
|
233
|
+
staticItems.push(item);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If min or max is present, it's fluid (calculate missing side)
|
|
238
|
+
if (item.min !== undefined || item.max !== undefined) {
|
|
239
|
+
const ratio = item.fluidRatio || fileRatio;
|
|
240
|
+
let min = item.min;
|
|
241
|
+
let max = item.max;
|
|
242
|
+
|
|
243
|
+
if (min === undefined) min = parseFloat((max / ratio).toFixed(2));
|
|
244
|
+
if (max === undefined) max = parseFloat((min * ratio).toFixed(2));
|
|
245
|
+
|
|
246
|
+
fluidItems.push({ ...item, min, max });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Process Fluid Items
|
|
251
|
+
if (hasFluidData && fluidItems.length > 0) {
|
|
252
|
+
const fluidTokens = clampGenerator(fluidItems, tokensContext.viewports);
|
|
253
|
+
fluidTokens.forEach(item => {
|
|
254
|
+
const slug = slugify(item.name);
|
|
255
|
+
cssContent += ` --${slug}: ${item.value};\n`;
|
|
256
|
+
tokensContext.spacing.push({ name: slug, value: item.value });
|
|
257
|
+
});
|
|
258
|
+
} else if (fluidItems.length > 0) {
|
|
259
|
+
console.warn('[SwatchKit] Fluid spacing detected but viewports missing. Skipping fluid generation.');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Process Static Items (fallback or simple values)
|
|
263
|
+
staticItems.forEach(item => {
|
|
264
|
+
if (item.name && item.value) {
|
|
265
|
+
const slug = slugify(item.name);
|
|
266
|
+
cssContent += ` --${slug}: ${item.value};\n`;
|
|
267
|
+
tokensContext.spacing.push({ name: slug, value: item.value });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
cssContent += '\n';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error(`[SwatchKit] Error processing spacing.json:`, error.message);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 7. Process Fonts
|
|
280
|
+
const fontsFile = path.join(tokensDir, 'fonts.json');
|
|
281
|
+
if (fs.existsSync(fontsFile)) {
|
|
282
|
+
try {
|
|
283
|
+
const fileContent = fs.readFileSync(fontsFile, 'utf-8');
|
|
284
|
+
const data = JSON.parse(fileContent);
|
|
285
|
+
|
|
286
|
+
if (data.items && Array.isArray(data.items)) {
|
|
287
|
+
hasTokens = true;
|
|
288
|
+
cssContent += ` /* ${data.title || 'Fonts'} */\n`;
|
|
289
|
+
data.items.forEach(item => {
|
|
290
|
+
if (item.name && item.value && Array.isArray(item.value)) {
|
|
291
|
+
const slug = slugify(item.name);
|
|
292
|
+
// Join font names with commas, quoting if necessary (though mostly optional in modern CSS if no special chars)
|
|
293
|
+
// But let's just join them as requested.
|
|
294
|
+
const fontStack = item.value.join(', ');
|
|
295
|
+
cssContent += ` --${slug}: ${fontStack};\n`;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
cssContent += '\n';
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error(`[SwatchKit] Error processing fonts.json:`, error.message);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
cssContent += '}\n';
|
|
306
|
+
|
|
307
|
+
// If no tokens found, exit early
|
|
308
|
+
if (!hasTokens) {
|
|
309
|
+
return tokensContext;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Ensure cssDir exists
|
|
313
|
+
if (!fs.existsSync(cssDir)) {
|
|
314
|
+
fs.mkdirSync(cssDir, { recursive: true });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const existing = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, 'utf-8') : null;
|
|
318
|
+
if (existing !== cssContent) {
|
|
319
|
+
fs.writeFileSync(outputFile, cssContent);
|
|
320
|
+
console.log(`+ Generated CSS: ${outputFile} (Do not edit manually)`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return tokensContext;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function generateTokenUtilities(tokensContext, cssDir) {
|
|
327
|
+
const outputFile = path.join(cssDir, 'tokens.css');
|
|
328
|
+
let cssContent = '/* AUTO-GENERATED Token Utilities */\n';
|
|
329
|
+
cssContent += '/* Classes that map directly to your tokens */\n\n';
|
|
330
|
+
|
|
331
|
+
// 1. Colors (.color\:name, .background-color\:name)
|
|
332
|
+
if (tokensContext.colors && tokensContext.colors.length > 0) {
|
|
333
|
+
cssContent += '/* Colors */\n';
|
|
334
|
+
tokensContext.colors.forEach(item => {
|
|
335
|
+
// Escape the colon for CSS selector but use normal name for variable
|
|
336
|
+
// Using !important to ensure utility classes always win specificity wars (CUBE/Every Layout methodology)
|
|
337
|
+
cssContent += `.color\\:${item.name} { color: var(--${item.name}) !important; }\n`;
|
|
338
|
+
cssContent += `.background-color\\:${item.name} { background-color: var(--${item.name}) !important; }\n`;
|
|
339
|
+
});
|
|
340
|
+
cssContent += '\n';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 2. Text Sizes (.font-size\:name)
|
|
344
|
+
if (tokensContext.textSizes && tokensContext.textSizes.length > 0) {
|
|
345
|
+
cssContent += '/* Text Sizes */\n';
|
|
346
|
+
tokensContext.textSizes.forEach(item => {
|
|
347
|
+
cssContent += `.font-size\\:${item.name} { font-size: var(--${item.name}) !important; }\n`;
|
|
348
|
+
});
|
|
349
|
+
cssContent += '\n';
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 3. Text Weights (.font-weight\:name)
|
|
353
|
+
if (tokensContext.textWeights && tokensContext.textWeights.length > 0) {
|
|
354
|
+
cssContent += '/* Text Weights */\n';
|
|
355
|
+
tokensContext.textWeights.forEach(item => {
|
|
356
|
+
cssContent += `.font-weight\\:${item.name} { font-weight: var(--${item.name}) !important; }\n`;
|
|
357
|
+
});
|
|
358
|
+
cssContent += '\n';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 4. Leading (.line-height\:name)
|
|
362
|
+
if (tokensContext.textLeading && tokensContext.textLeading.length > 0) {
|
|
363
|
+
cssContent += '/* Line Heights */\n';
|
|
364
|
+
tokensContext.textLeading.forEach(item => {
|
|
365
|
+
cssContent += `.line-height\\:${item.name} { line-height: var(--${item.name}) !important; }\n`;
|
|
366
|
+
});
|
|
367
|
+
cssContent += '\n';
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 5. Spacing logical properties (12 per token)
|
|
371
|
+
if (tokensContext.spacing && tokensContext.spacing.length > 0) {
|
|
372
|
+
const spacingProperties = [
|
|
373
|
+
'margin-block',
|
|
374
|
+
'margin-block-start',
|
|
375
|
+
'margin-block-end',
|
|
376
|
+
'margin-inline',
|
|
377
|
+
'margin-inline-start',
|
|
378
|
+
'margin-inline-end',
|
|
379
|
+
'padding-block',
|
|
380
|
+
'padding-block-start',
|
|
381
|
+
'padding-block-end',
|
|
382
|
+
'padding-inline',
|
|
383
|
+
'padding-inline-start',
|
|
384
|
+
'padding-inline-end',
|
|
385
|
+
];
|
|
386
|
+
cssContent += '/* Spacing */\n';
|
|
387
|
+
tokensContext.spacing.forEach(item => {
|
|
388
|
+
spacingProperties.forEach(prop => {
|
|
389
|
+
cssContent += `.${prop}\\:${item.name} { ${prop}: var(--${item.name}) !important; }\n`;
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
cssContent += '\n';
|
|
393
|
+
|
|
394
|
+
// 6. Region space (sets --region-space custom property)
|
|
395
|
+
cssContent += '/* Region Space */\n';
|
|
396
|
+
tokensContext.spacing.forEach(item => {
|
|
397
|
+
cssContent += `.region-space\\:${item.name} { --region-space: var(--${item.name}); }\n`;
|
|
398
|
+
});
|
|
399
|
+
cssContent += '\n';
|
|
400
|
+
|
|
401
|
+
// 7. Flow space (sets --flow-space custom property)
|
|
402
|
+
cssContent += '/* Flow Space */\n';
|
|
403
|
+
tokensContext.spacing.forEach(item => {
|
|
404
|
+
cssContent += `.flow-space\\:${item.name} { --flow-space: var(--${item.name}); }\n`;
|
|
405
|
+
});
|
|
406
|
+
cssContent += '\n';
|
|
407
|
+
|
|
408
|
+
// 8. Gutter (sets --gutter custom property)
|
|
409
|
+
cssContent += '/* Gutter */\n';
|
|
410
|
+
tokensContext.spacing.forEach(item => {
|
|
411
|
+
cssContent += `.gutter\\:${item.name} { --gutter: var(--${item.name}); }\n`;
|
|
412
|
+
});
|
|
413
|
+
cssContent += '\n';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Ensure cssDir exists
|
|
417
|
+
if (!fs.existsSync(cssDir)) {
|
|
418
|
+
fs.mkdirSync(cssDir, { recursive: true });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const existing = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, 'utf-8') : null;
|
|
422
|
+
if (existing !== cssContent) {
|
|
423
|
+
fs.writeFileSync(outputFile, cssContent);
|
|
424
|
+
console.log(`+ Generated Utilities: ${outputFile} (Do not edit manually)`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
module.exports = { processTokens, generateTokenUtilities };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes an array of tokens and sends back and array of name
|
|
3
|
+
* and clamp pairs for CSS fluid values.
|
|
4
|
+
*
|
|
5
|
+
* @param {array} tokens array of {name: string, min: number, max: number}
|
|
6
|
+
* @param {object} viewports {min: number, max: number}
|
|
7
|
+
* @returns {array} {name: string, value: string}
|
|
8
|
+
*/
|
|
9
|
+
const clampGenerator = (tokens, viewports) => {
|
|
10
|
+
const rootSize = 16;
|
|
11
|
+
|
|
12
|
+
return tokens.map(({ name, min, max }) => {
|
|
13
|
+
if (min === max) {
|
|
14
|
+
return `${min / rootSize}rem`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Convert the min and max sizes to rems
|
|
18
|
+
const minSize = min / rootSize;
|
|
19
|
+
const maxSize = max / rootSize;
|
|
20
|
+
|
|
21
|
+
// Convert the pixel viewport sizes into rems
|
|
22
|
+
const minViewport = viewports['viewport-min'] / rootSize;
|
|
23
|
+
const maxViewport = viewports['viewport-max'] / rootSize;
|
|
24
|
+
|
|
25
|
+
// Slope and intersection allow us to have a fluid value but also keep that sensible
|
|
26
|
+
const slope = (maxSize - minSize) / (maxViewport - minViewport);
|
|
27
|
+
const intersection = -1 * minViewport * slope + minSize;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
name,
|
|
31
|
+
value: `clamp(${minSize}rem, ${intersection.toFixed(2)}rem + ${(
|
|
32
|
+
slope * 100
|
|
33
|
+
).toFixed(2)}vw, ${maxSize}rem)`,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
module.exports = clampGenerator;
|