@vettvangur/design-system 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -0
- package/README.md +18 -0
- package/dist/generate-astro-AcegHzWG.js +476 -0
- package/dist/generate-astro-B225JTMY.js +534 -0
- package/dist/generate-astro-Btr2jiyr.js +389 -0
- package/dist/generate-astro-CpNCOn_3.js +475 -0
- package/dist/generate-astro-CqXZcTfi.js +820 -0
- package/dist/generate-astro-D-FcwrAx.js +819 -0
- package/dist/generate-astro-DfaWTEF_.js +539 -0
- package/dist/generate-astro-DwBkft4p.js +820 -0
- package/dist/generate-razor--hJCWG13.js +454 -0
- package/dist/generate-razor-B0Pid10L.js +441 -0
- package/dist/generate-razor-B0n9FUR8.js +441 -0
- package/dist/generate-razor-B3a_-uDf.js +441 -0
- package/dist/generate-razor-BLNML3CC.js +441 -0
- package/dist/generate-razor-CABSDQok.js +454 -0
- package/dist/generate-razor-CGhqwpRL.js +413 -0
- package/dist/generate-razor-DcAkNKya.js +9 -0
- package/dist/generate-razor-DwXDvSTP.js +332 -0
- package/dist/generate-razor-IGs8o9fx.js +441 -0
- package/dist/generate-razor-nGTa0EVW.js +335 -0
- package/dist/generate-tailwind-16Mayr2z.js +1050 -0
- package/dist/generate-tailwind-BR1-fpNI.js +1056 -0
- package/dist/generate-tailwind-BVpI-hUx.js +1057 -0
- package/dist/generate-tailwind-Bd3ltCQR.js +1084 -0
- package/dist/generate-tailwind-BljcUYMa.js +1051 -0
- package/dist/generate-tailwind-BqZCEvj5.js +1049 -0
- package/dist/generate-tailwind-CGwQarzK.js +1059 -0
- package/dist/generate-tailwind-Cpb8YNNz.js +1050 -0
- package/dist/generate-tailwind-CtUsHnBd.js +1060 -0
- package/dist/generate-tailwind-DL0GEqR6.js +1050 -0
- package/dist/generate-tailwind-Dr3KLBMD.js +1045 -0
- package/dist/generate-tailwind-KzRHwyMG.js +1049 -0
- package/dist/generate-tailwind-VnkcDTZP.js +1049 -0
- package/dist/index.esm.js +58 -0
- package/dist/inputs-CQVgm9Do.js +439 -0
- package/package.json +35 -0
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { promises } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import url from 'node:url';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
// core/typography.mjs
|
|
8
|
+
path.dirname(url.fileURLToPath(import.meta.url));
|
|
9
|
+
const DEFAULT_TYPO_PATH = path.resolve(process.cwd(), 'src/styles/config/typography.css');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse CSS custom properties for a given family (e.g. "body" or "headline").
|
|
13
|
+
* Supports:
|
|
14
|
+
* --text-body1: 20px;
|
|
15
|
+
* --leading-body1: 30px;
|
|
16
|
+
* --text-headline1-mobile: 40px;
|
|
17
|
+
* --leading-headline1-mobile: 50px;
|
|
18
|
+
*/
|
|
19
|
+
function parseFamily(css, family) {
|
|
20
|
+
// desktop
|
|
21
|
+
const textRe = new RegExp(`--text-${family}(\\d+)\\s*:\\s*([^;]+);`, 'g');
|
|
22
|
+
const leadRe = new RegExp(`--leading-${family}(\\d+)\\s*:\\s*([^;]+);`, 'g');
|
|
23
|
+
// mobile overrides
|
|
24
|
+
const textMobRe = new RegExp(`--text-${family}(\\d+)-mobile\\s*:\\s*([^;]+);`, 'g');
|
|
25
|
+
const leadMobRe = new RegExp(`--leading-${family}(\\d+)-mobile\\s*:\\s*([^;]+);`, 'g');
|
|
26
|
+
|
|
27
|
+
/** @type {Map<string, {text?:string, leading?:string, textMobile?:string, leadingMobile?:string}>} */
|
|
28
|
+
const map = new Map();
|
|
29
|
+
for (const m of css.matchAll(textRe)) {
|
|
30
|
+
const n = m[1];
|
|
31
|
+
const v = m[2].trim();
|
|
32
|
+
const e = map.get(n) ?? {};
|
|
33
|
+
e.text = v;
|
|
34
|
+
map.set(n, e);
|
|
35
|
+
}
|
|
36
|
+
for (const m of css.matchAll(leadRe)) {
|
|
37
|
+
const n = m[1];
|
|
38
|
+
const v = m[2].trim();
|
|
39
|
+
const e = map.get(n) ?? {};
|
|
40
|
+
e.leading = v;
|
|
41
|
+
map.set(n, e);
|
|
42
|
+
}
|
|
43
|
+
for (const m of css.matchAll(textMobRe)) {
|
|
44
|
+
const n = m[1];
|
|
45
|
+
const v = m[2].trim();
|
|
46
|
+
const e = map.get(n) ?? {};
|
|
47
|
+
e.textMobile = v;
|
|
48
|
+
map.set(n, e);
|
|
49
|
+
}
|
|
50
|
+
for (const m of css.matchAll(leadMobRe)) {
|
|
51
|
+
const n = m[1];
|
|
52
|
+
const v = m[2].trim();
|
|
53
|
+
const e = map.get(n) ?? {};
|
|
54
|
+
e.leadingMobile = v;
|
|
55
|
+
map.set(n, e);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Build output objects
|
|
59
|
+
const items = [...map.keys()].map(Number).sort((a, b) => a - b).map(n => {
|
|
60
|
+
const k = String(n);
|
|
61
|
+
const e = map.get(k) ?? {};
|
|
62
|
+
const desktop = e.text || e.leading ? {
|
|
63
|
+
size: e.text ?? null,
|
|
64
|
+
lineHeight: e.leading ?? null
|
|
65
|
+
} : null;
|
|
66
|
+
const mobile = e.textMobile || e.leadingMobile ? {
|
|
67
|
+
size: e.textMobile ?? null,
|
|
68
|
+
lineHeight: e.leadingMobile ?? null
|
|
69
|
+
} : null;
|
|
70
|
+
const titleBase = family === 'body' ? 'Body' : 'Headline';
|
|
71
|
+
return {
|
|
72
|
+
title: `${titleBase} ${n}`,
|
|
73
|
+
size: e.text ?? null,
|
|
74
|
+
lineheight: e.leading ?? null,
|
|
75
|
+
class: `${family}-${n}`,
|
|
76
|
+
config: {
|
|
77
|
+
mobile,
|
|
78
|
+
desktop
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
return items;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Read the CSS file and return { bodies, headlines } arrays in your required shape. */
|
|
86
|
+
async function parseTypography(typographyCssPath = DEFAULT_TYPO_PATH) {
|
|
87
|
+
const css = await promises.readFile(typographyCssPath, 'utf8');
|
|
88
|
+
const s = {
|
|
89
|
+
bodies: parseFamily(css, 'body'),
|
|
90
|
+
headlines: parseFamily(css, 'headline')
|
|
91
|
+
};
|
|
92
|
+
return s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** CLI: node core/typography.mjs [path/to/typography.css] */
|
|
96
|
+
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
97
|
+
const file = process.argv[2] ? path.resolve(process.argv[2]) : DEFAULT_TYPO_PATH;
|
|
98
|
+
parseTypography(file).then(res => {
|
|
99
|
+
// pretty-print to stdout so other generators can pipe/consume it
|
|
100
|
+
console.log(JSON.stringify(res, null, 2));
|
|
101
|
+
}).catch(err => {
|
|
102
|
+
console.error('Failed to parse typography CSS:', err);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// core/colors.mjs
|
|
108
|
+
path.dirname(url.fileURLToPath(import.meta.url));
|
|
109
|
+
const DEFAULT_COLOR_PATH = path.resolve(process.cwd(), 'src/styles/config/color.css');
|
|
110
|
+
|
|
111
|
+
// Turn "primary-dark-blue" -> "Primary Dark Blue"
|
|
112
|
+
function titleCaseFromKebab(kebab) {
|
|
113
|
+
return kebab.split('-').map(w => w ? w[0].toUpperCase() + w.slice(1) : w).join(' ');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Parse @theme color vars like:
|
|
118
|
+
* --color-primary: #123456;
|
|
119
|
+
* --color-secondary-blue: #4566c4;
|
|
120
|
+
* --color-transparent: transparent;
|
|
121
|
+
* and return:
|
|
122
|
+
* [{ title, color, class }]
|
|
123
|
+
*/
|
|
124
|
+
async function parseColors(filePath = DEFAULT_COLOR_PATH) {
|
|
125
|
+
const css = await promises.readFile(filePath, 'utf8');
|
|
126
|
+
|
|
127
|
+
// Matches "--color-<name>: <value>;"
|
|
128
|
+
const re = /--color-([a-z0-9-]+)\s*:\s*([^;]+);/gi;
|
|
129
|
+
/** @type {Array<{title:string,color:string,class:string}>} */
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const m of css.matchAll(re)) {
|
|
132
|
+
const name = m[1].trim(); // e.g. "primary-dark-blue"
|
|
133
|
+
const value = m[2].trim(); // e.g. "#292a4b" or "rgba(...)" or "orange"
|
|
134
|
+
out.push({
|
|
135
|
+
title: titleCaseFromKebab(name),
|
|
136
|
+
// "Primary Dark Blue"
|
|
137
|
+
color: value,
|
|
138
|
+
// "#292a4b"
|
|
139
|
+
class: `x-${name}` // "x-primary-dark-blue"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//console.log('out', out)
|
|
144
|
+
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** CLI usage: node core/colors.mjs [path/to/color.css] */
|
|
149
|
+
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
150
|
+
const file = process.argv[2] ? path.resolve(process.argv[2]) : DEFAULT_COLOR_PATH;
|
|
151
|
+
parseColors(file).then(arr => console.log(JSON.stringify(arr, null, 2))).catch(err => {
|
|
152
|
+
console.error('Failed to parse colors:', err);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// core/buttons.mjs
|
|
158
|
+
path.dirname(url.fileURLToPath(import.meta.url));
|
|
159
|
+
const DEFAULT_BUTTON_CSS = path.resolve(process.cwd(), 'src/styles/core/button.css');
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parse @utility blocks from the CSS file.
|
|
163
|
+
* Returns Map<utilityName, blockContent>
|
|
164
|
+
*/
|
|
165
|
+
function parseUtilityBlocks(css) {
|
|
166
|
+
const re = /@utility\s+([a-z0-9-]+)\s*\{([\s\S]*?)\}/gi;
|
|
167
|
+
const map = new Map();
|
|
168
|
+
for (const m of css.matchAll(re)) {
|
|
169
|
+
const name = m[1].trim(); // e.g. "button-primary"
|
|
170
|
+
const body = m[2].trim(); // inner CSS of the utility
|
|
171
|
+
map.set(name, body);
|
|
172
|
+
}
|
|
173
|
+
return map;
|
|
174
|
+
}
|
|
175
|
+
function kebabToTitle(kebab) {
|
|
176
|
+
return kebab.split('-').filter(Boolean).map(w => w[0].toUpperCase() + w.slice(1)).join(' ');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// A tiny, stable "random" picker for lorem words — keeps output deterministic per name.
|
|
180
|
+
const LOREM = ['Lorem', 'Ipsum', 'Dolor', 'Sit', 'Amet', 'Quasar', 'Nimbus', 'Orbit', 'Vector', 'Nova', 'Pulse'];
|
|
181
|
+
function pickWord(seedStr) {
|
|
182
|
+
let h = 0;
|
|
183
|
+
for (let i = 0; i < seedStr.length; i++) {
|
|
184
|
+
h = h * 131 + seedStr.charCodeAt(i) >>> 0;
|
|
185
|
+
}
|
|
186
|
+
return LOREM[h % LOREM.length];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Parse the button utilities and return:
|
|
191
|
+
* [
|
|
192
|
+
* { title: 'Button Primary', class: 'button-primary', text: 'Lorem' },
|
|
193
|
+
* { title: 'Button Square', class: 'button-primary button-square', text: 'Ipsum' },
|
|
194
|
+
* ...
|
|
195
|
+
* ]
|
|
196
|
+
*/
|
|
197
|
+
async function parseButtons(filePath = DEFAULT_BUTTON_CSS) {
|
|
198
|
+
const css = await promises.readFile(filePath, 'utf8');
|
|
199
|
+
const utilities = parseUtilityBlocks(css);
|
|
200
|
+
|
|
201
|
+
// Identify names
|
|
202
|
+
const allButtonUtils = [...utilities.keys()].filter(n => n === 'button' || n.startsWith('button-'));
|
|
203
|
+
|
|
204
|
+
// Variants are utilities that explicitly apply the base "button" utility
|
|
205
|
+
const variants = allButtonUtils.filter(n => {
|
|
206
|
+
if (n === 'button') {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const body = utilities.get(n) || '';
|
|
210
|
+
return /@apply\s+button\b/.test(body);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Modifiers are other button-* utilities (not "button" and not variants)
|
|
214
|
+
const modifiers = allButtonUtils.filter(n => n !== 'button' && !variants.includes(n));
|
|
215
|
+
|
|
216
|
+
/** @type {Array<{title:string,class:string,text:string}>} */
|
|
217
|
+
const out = [];
|
|
218
|
+
|
|
219
|
+
// Base variants
|
|
220
|
+
for (const v of variants) {
|
|
221
|
+
const label = kebabToTitle(v.replace(/^button-/, '')) || 'Base';
|
|
222
|
+
out.push({
|
|
223
|
+
title: `Button ${label}`,
|
|
224
|
+
class: v,
|
|
225
|
+
text: pickWord(v)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Variant + modifier combos
|
|
230
|
+
for (const m of modifiers) {
|
|
231
|
+
const mLabel = kebabToTitle(m.replace(/^button-/, ''));
|
|
232
|
+
for (const v of variants) {
|
|
233
|
+
out.push({
|
|
234
|
+
title: `Button ${mLabel}`,
|
|
235
|
+
class: `${v} ${m}`,
|
|
236
|
+
text: pickWord(`${v}|${m}`)
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** CLI: node core/buttons.mjs [path/to/button.css] */
|
|
244
|
+
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
245
|
+
const file = process.argv[2] ? path.resolve(process.argv[2]) : DEFAULT_BUTTON_CSS;
|
|
246
|
+
parseButtons(file).then(arr => console.log(JSON.stringify(arr, null, 2))).catch(err => {
|
|
247
|
+
console.error('Failed to parse buttons:', err);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
var ButtonType;
|
|
253
|
+
(function (ButtonType) {
|
|
254
|
+
ButtonType["Button"] = "button";
|
|
255
|
+
ButtonType["Submit"] = "submit";
|
|
256
|
+
ButtonType["Reset"] = "reset";
|
|
257
|
+
})(ButtonType || (ButtonType = {}));
|
|
258
|
+
var InputType;
|
|
259
|
+
(function (InputType) {
|
|
260
|
+
InputType["Text"] = "text";
|
|
261
|
+
InputType["Password"] = "password";
|
|
262
|
+
InputType["Email"] = "email";
|
|
263
|
+
InputType["Number"] = "number";
|
|
264
|
+
InputType["Tel"] = "tel";
|
|
265
|
+
InputType["Url"] = "url";
|
|
266
|
+
InputType["Search"] = "search";
|
|
267
|
+
InputType["Date"] = "date";
|
|
268
|
+
InputType["Time"] = "time";
|
|
269
|
+
InputType["DateTime"] = "datetime-local";
|
|
270
|
+
InputType["Month"] = "month";
|
|
271
|
+
InputType["Week"] = "week";
|
|
272
|
+
InputType["Color"] = "color";
|
|
273
|
+
InputType["File"] = "file";
|
|
274
|
+
InputType["Hidden"] = "hidden";
|
|
275
|
+
InputType["Range"] = "range";
|
|
276
|
+
InputType["Checkbox"] = "checkbox";
|
|
277
|
+
InputType["Radio"] = "radio";
|
|
278
|
+
})(InputType || (InputType = {}));
|
|
279
|
+
var CropFormat;
|
|
280
|
+
(function (CropFormat) {
|
|
281
|
+
CropFormat["Jpeg"] = "jpeg";
|
|
282
|
+
CropFormat["Png"] = "png";
|
|
283
|
+
CropFormat["Webp"] = "webp";
|
|
284
|
+
CropFormat["Gif"] = "gif";
|
|
285
|
+
})(CropFormat || (CropFormat = {}));
|
|
286
|
+
var CropMode;
|
|
287
|
+
(function (CropMode) {
|
|
288
|
+
CropMode["Crop"] = "crop";
|
|
289
|
+
CropMode["BoxPad"] = "boxpad";
|
|
290
|
+
CropMode["Min"] = "min";
|
|
291
|
+
CropMode["Max"] = "max";
|
|
292
|
+
CropMode["Stretch"] = "stretch";
|
|
293
|
+
CropMode["Pad"] = "pad";
|
|
294
|
+
})(CropMode || (CropMode = {}));
|
|
295
|
+
|
|
296
|
+
const inputs = [{
|
|
297
|
+
id: '1',
|
|
298
|
+
label: 'Input',
|
|
299
|
+
type: InputType.Text,
|
|
300
|
+
isDisabled: false,
|
|
301
|
+
isReadOnly: false,
|
|
302
|
+
isError: false
|
|
303
|
+
}, {
|
|
304
|
+
id: '2',
|
|
305
|
+
label: 'Input Disabled',
|
|
306
|
+
type: InputType.Text,
|
|
307
|
+
isDisabled: true,
|
|
308
|
+
isReadOnly: false,
|
|
309
|
+
isError: false
|
|
310
|
+
}, {
|
|
311
|
+
id: '3',
|
|
312
|
+
label: 'Input Read Only',
|
|
313
|
+
type: InputType.Text,
|
|
314
|
+
isDisabled: false,
|
|
315
|
+
isReadOnly: true,
|
|
316
|
+
isError: false
|
|
317
|
+
}, {
|
|
318
|
+
id: '4',
|
|
319
|
+
label: 'Input Error',
|
|
320
|
+
type: InputType.Text,
|
|
321
|
+
isDisabled: false,
|
|
322
|
+
isReadOnly: false,
|
|
323
|
+
isError: true
|
|
324
|
+
}];
|
|
325
|
+
const selects = [{
|
|
326
|
+
id: '5',
|
|
327
|
+
label: 'Select',
|
|
328
|
+
isDisabled: false,
|
|
329
|
+
isReadOnly: false,
|
|
330
|
+
isError: true
|
|
331
|
+
}, {
|
|
332
|
+
id: '6',
|
|
333
|
+
label: 'Select Disabled',
|
|
334
|
+
isDisabled: true,
|
|
335
|
+
isReadOnly: false,
|
|
336
|
+
isError: true
|
|
337
|
+
}, {
|
|
338
|
+
id: '7',
|
|
339
|
+
label: 'Select Error',
|
|
340
|
+
isDisabled: false,
|
|
341
|
+
isReadOnly: false,
|
|
342
|
+
isError: true
|
|
343
|
+
}];
|
|
344
|
+
const checkboxes = [{
|
|
345
|
+
id: '8',
|
|
346
|
+
label: 'Checbox',
|
|
347
|
+
name: '',
|
|
348
|
+
type: InputType.Checkbox,
|
|
349
|
+
isDisabled: false,
|
|
350
|
+
isError: false
|
|
351
|
+
}, {
|
|
352
|
+
id: '9',
|
|
353
|
+
label: 'Checbox',
|
|
354
|
+
name: '',
|
|
355
|
+
type: InputType.Checkbox,
|
|
356
|
+
isDisabled: false,
|
|
357
|
+
isError: false
|
|
358
|
+
}, {
|
|
359
|
+
id: '10',
|
|
360
|
+
label: 'Checbox Disabled',
|
|
361
|
+
name: '',
|
|
362
|
+
type: InputType.Checkbox,
|
|
363
|
+
isDisabled: true,
|
|
364
|
+
isError: false
|
|
365
|
+
}, {
|
|
366
|
+
id: '11',
|
|
367
|
+
Label: 'Checbox Error',
|
|
368
|
+
name: '',
|
|
369
|
+
type: InputType.Checkbox,
|
|
370
|
+
isDisabled: false,
|
|
371
|
+
isError: true
|
|
372
|
+
}];
|
|
373
|
+
const radios = [{
|
|
374
|
+
id: '12',
|
|
375
|
+
Label: 'Radio',
|
|
376
|
+
Name: 'prettyradio',
|
|
377
|
+
type: InputType.Radio,
|
|
378
|
+
isDisabled: false,
|
|
379
|
+
isError: false
|
|
380
|
+
}, {
|
|
381
|
+
id: '13',
|
|
382
|
+
Label: 'Radio',
|
|
383
|
+
name: 'prettyradio',
|
|
384
|
+
type: InputType.Radio,
|
|
385
|
+
isDisabled: false,
|
|
386
|
+
isError: false
|
|
387
|
+
}, {
|
|
388
|
+
id: '14',
|
|
389
|
+
Label: 'Radio Error',
|
|
390
|
+
name: 'prettyradio',
|
|
391
|
+
type: InputType.Radio,
|
|
392
|
+
isDisabled: false,
|
|
393
|
+
isError: true
|
|
394
|
+
}, {
|
|
395
|
+
id: '15',
|
|
396
|
+
Label: 'Radio Disabled',
|
|
397
|
+
name: 'prettyradio',
|
|
398
|
+
type: InputType.Radio,
|
|
399
|
+
isDisabled: true,
|
|
400
|
+
isError: false
|
|
401
|
+
}];
|
|
402
|
+
const textareas = [{
|
|
403
|
+
id: '16',
|
|
404
|
+
Label: 'Textarea',
|
|
405
|
+
type: '',
|
|
406
|
+
isDisabled: false,
|
|
407
|
+
isReadOnly: false,
|
|
408
|
+
isError: false
|
|
409
|
+
}, {
|
|
410
|
+
id: '17',
|
|
411
|
+
Label: 'Textarea Disabled',
|
|
412
|
+
type: '',
|
|
413
|
+
isDisabled: true,
|
|
414
|
+
isReadOnly: false,
|
|
415
|
+
isError: false
|
|
416
|
+
}, {
|
|
417
|
+
id: '18',
|
|
418
|
+
Label: 'Textarea Read Only',
|
|
419
|
+
type: '',
|
|
420
|
+
isDisabled: false,
|
|
421
|
+
isReadOnly: true,
|
|
422
|
+
isError: false
|
|
423
|
+
}, {
|
|
424
|
+
id: '19',
|
|
425
|
+
Label: 'Textarea Error',
|
|
426
|
+
type: '',
|
|
427
|
+
isDisabled: false,
|
|
428
|
+
isReadOnly: false,
|
|
429
|
+
isError: true
|
|
430
|
+
}];
|
|
431
|
+
var inputsSpec = {
|
|
432
|
+
inputs,
|
|
433
|
+
selects,
|
|
434
|
+
checkboxes,
|
|
435
|
+
radios,
|
|
436
|
+
textareas
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// core/astro/generate-astro.mjs
|
|
440
|
+
const CWD = process.cwd();
|
|
441
|
+
|
|
442
|
+
// Generated components
|
|
443
|
+
const OUT_DIR = path.resolve(CWD, 'src/astrobook/components');
|
|
444
|
+
const OUT_PREVIEW = path.join(OUT_DIR, 'Preview.astro'); // <-- new
|
|
445
|
+
const OUT_COLORS = path.join(OUT_DIR, 'Colors.astro');
|
|
446
|
+
const OUT_BODIES = path.join(OUT_DIR, 'Bodies.astro');
|
|
447
|
+
const OUT_HEADLINES = path.join(OUT_DIR, 'Headlines.astro');
|
|
448
|
+
const OUT_BUTTONS = path.join(OUT_DIR, 'Buttons.astro');
|
|
449
|
+
const OUT_INPUTS = path.join(OUT_DIR, 'Inputs.astro');
|
|
450
|
+
|
|
451
|
+
// Story folders
|
|
452
|
+
const ASTROBOOK_DIR = path.resolve(CWD, 'src/astrobook');
|
|
453
|
+
const OUT_DIR_TYPOGRAPHY = path.join(ASTROBOOK_DIR, 'Typography');
|
|
454
|
+
const OUT_DIR_UI = path.join(ASTROBOOK_DIR, 'UI');
|
|
455
|
+
const OUT_DIR_COLORS = path.join(ASTROBOOK_DIR, 'Colors');
|
|
456
|
+
async function generateAstro() {
|
|
457
|
+
// ensure dirs
|
|
458
|
+
await promises.mkdir(OUT_DIR, {
|
|
459
|
+
recursive: true
|
|
460
|
+
});
|
|
461
|
+
await promises.mkdir(ASTROBOOK_DIR, {
|
|
462
|
+
recursive: true
|
|
463
|
+
});
|
|
464
|
+
await promises.mkdir(OUT_DIR_TYPOGRAPHY, {
|
|
465
|
+
recursive: true
|
|
466
|
+
});
|
|
467
|
+
await promises.mkdir(OUT_DIR_UI, {
|
|
468
|
+
recursive: true
|
|
469
|
+
});
|
|
470
|
+
await promises.mkdir(OUT_DIR_COLORS, {
|
|
471
|
+
recursive: true
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// write Preview.astro (to a FILE, not the folder)
|
|
475
|
+
await promises.writeFile(OUT_PREVIEW, generatePreview(), 'utf8');
|
|
476
|
+
console.log(`✓ Wrote ${rel(OUT_PREVIEW)} (Preview)`);
|
|
477
|
+
|
|
478
|
+
// write story files
|
|
479
|
+
await renderStoriesAstro();
|
|
480
|
+
const [colors, typography, buttons] = await Promise.all([parseColors(),
|
|
481
|
+
// -> [{ title, color, class }]
|
|
482
|
+
parseTypography(),
|
|
483
|
+
// -> { bodies: [...], headlines: [...] }
|
|
484
|
+
parseButtons() // -> [{ title, class, text }]
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
// Colors -> Colors.astro
|
|
488
|
+
if (colors?.length) {
|
|
489
|
+
await promises.writeFile(OUT_COLORS, renderColorsAstro(colors), 'utf8');
|
|
490
|
+
console.log(`✓ Wrote ${rel(OUT_COLORS)} (${colors.length} swatches)`);
|
|
491
|
+
} else {
|
|
492
|
+
console.warn('• No colors parsed — Colors.astro not written');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Bodies -> Bodies.astro
|
|
496
|
+
if (typography?.bodies?.length) {
|
|
497
|
+
await promises.writeFile(OUT_BODIES, renderBodiesAstro(typography.bodies), 'utf8');
|
|
498
|
+
console.log(`✓ Wrote ${rel(OUT_BODIES)} (${typography.bodies.length} bodies)`);
|
|
499
|
+
} else {
|
|
500
|
+
console.warn('• No bodies parsed — Bodies.astro not written');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Headlines -> Headlines.astro
|
|
504
|
+
if (typography?.headlines?.length) {
|
|
505
|
+
await promises.writeFile(OUT_HEADLINES, renderHeadlinesAstro(typography.headlines), 'utf8');
|
|
506
|
+
console.log(`✓ Wrote ${rel(OUT_HEADLINES)} (${typography.headlines.length} headlines)`);
|
|
507
|
+
} else {
|
|
508
|
+
console.warn('• No headlines parsed — Headlines.astro not written');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Buttons -> Buttons.astro
|
|
512
|
+
if (buttons?.length) {
|
|
513
|
+
await promises.writeFile(OUT_BUTTONS, renderButtonsAstro(buttons), 'utf8');
|
|
514
|
+
console.log(`✓ Wrote ${rel(OUT_BUTTONS)} (${buttons.length} buttons)`);
|
|
515
|
+
} else {
|
|
516
|
+
console.warn('• No buttons parsed — Buttons.astro not written');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Inputs -> Inputs.astro
|
|
520
|
+
const hasAnyInputs = Object.values(inputsSpec).some(arr => Array.isArray(arr) && arr.length);
|
|
521
|
+
if (hasAnyInputs) {
|
|
522
|
+
await promises.writeFile(OUT_INPUTS, renderInputsAstro(inputsSpec), 'utf8');
|
|
523
|
+
console.log(`✓ Wrote ${rel(OUT_INPUTS)} (inputs:${inputsSpec.inputs?.length ?? 0} selects:${inputsSpec.selects?.length ?? 0} ` + `checkboxes:${inputsSpec.checkboxes?.length ?? 0} radios:${inputsSpec.radios?.length ?? 0} textareas:${inputsSpec.textareas?.length ?? 0})`);
|
|
524
|
+
} else {
|
|
525
|
+
console.warn('• No input spec items — Inputs.astro not written');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* ---------- story writers ---------- */
|
|
530
|
+
|
|
531
|
+
async function renderStoriesAstro() {
|
|
532
|
+
const storyBody = generateStoryBody();
|
|
533
|
+
const storyColors = generateStoryColors();
|
|
534
|
+
const storyHeadline = generateStoryHeadline();
|
|
535
|
+
const storyButton = generateStoryButtons();
|
|
536
|
+
const storyInputs = generateStoryInputs();
|
|
537
|
+
const FILE_BODY = path.join(OUT_DIR_TYPOGRAPHY, 'Body.stories.ts');
|
|
538
|
+
const FILE_HEADLINES = path.join(OUT_DIR_TYPOGRAPHY, 'Headlines.stories.ts');
|
|
539
|
+
const FILE_BUTTONS = path.join(OUT_DIR_UI, 'Buttons.stories.ts');
|
|
540
|
+
const FILE_INPUTS = path.join(OUT_DIR_UI, 'Inputs.stories.ts');
|
|
541
|
+
const FILE_COLORS = path.join(OUT_DIR_COLORS, 'Colors.stories.ts');
|
|
542
|
+
await promises.writeFile(FILE_BODY, storyBody, 'utf8');
|
|
543
|
+
await promises.writeFile(FILE_HEADLINES, storyHeadline, 'utf8');
|
|
544
|
+
await promises.writeFile(FILE_BUTTONS, storyButton, 'utf8');
|
|
545
|
+
await promises.writeFile(FILE_INPUTS, storyInputs, 'utf8');
|
|
546
|
+
await promises.writeFile(FILE_COLORS, storyColors, 'utf8');
|
|
547
|
+
console.log('✓ Wrote story files:');
|
|
548
|
+
console.log(` - ${rel(FILE_BODY)}`);
|
|
549
|
+
console.log(` - ${rel(FILE_HEADLINES)}`);
|
|
550
|
+
console.log(` - ${rel(FILE_BUTTONS)}`);
|
|
551
|
+
console.log(` - ${rel(FILE_INPUTS)}`);
|
|
552
|
+
console.log(` - ${rel(FILE_COLORS)}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/* ---------- code gens ---------- */
|
|
556
|
+
|
|
557
|
+
function generatePreview() {
|
|
558
|
+
return `
|
|
559
|
+
---
|
|
560
|
+
/** Auto-generated - do not edit by hand */
|
|
561
|
+
import '@/styles/main.css'
|
|
562
|
+
import Fonts from '@/components/core/utility/Fonts.astro'
|
|
563
|
+
---
|
|
564
|
+
<Fonts />
|
|
565
|
+
<div class="astrobook-preview bg-background">
|
|
566
|
+
<slot />
|
|
567
|
+
</div>
|
|
568
|
+
`.trimStart();
|
|
569
|
+
}
|
|
570
|
+
function generateStoryBody() {
|
|
571
|
+
return `
|
|
572
|
+
// Auto-generated — do not edit by hand
|
|
573
|
+
import Preview from '@/astrobook/components/Preview.astro'
|
|
574
|
+
import Bodies from '@/astrobook/components/Bodies.astro'
|
|
575
|
+
|
|
576
|
+
export default {
|
|
577
|
+
title: 'Typography/Body',
|
|
578
|
+
component: Bodies,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export const Overview = {
|
|
582
|
+
render: () => Preview({ slots: { default: Bodies({}) } }),
|
|
583
|
+
}
|
|
584
|
+
`.trimStart();
|
|
585
|
+
}
|
|
586
|
+
function generateStoryColors() {
|
|
587
|
+
return `
|
|
588
|
+
// Auto-generated — do not edit by hand
|
|
589
|
+
import Preview from '@/astrobook/components/Preview.astro'
|
|
590
|
+
import Colors from '@/astrobook/components/Colors.astro'
|
|
591
|
+
|
|
592
|
+
export default {
|
|
593
|
+
title: 'Colors',
|
|
594
|
+
component: Colors,
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export const Overview = {
|
|
598
|
+
render: () => Preview({ slots: { default: Colors({}) } }),
|
|
599
|
+
}
|
|
600
|
+
`.trimStart();
|
|
601
|
+
}
|
|
602
|
+
function generateStoryHeadline() {
|
|
603
|
+
return `
|
|
604
|
+
// Auto-generated — do not edit by hand
|
|
605
|
+
import Preview from '@/astrobook/components/Preview.astro'
|
|
606
|
+
import Headlines from '@/astrobook/components/Headlines.astro'
|
|
607
|
+
|
|
608
|
+
export default {
|
|
609
|
+
title: 'Typography/Headlines',
|
|
610
|
+
component: Headlines,
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export const Overview = {
|
|
614
|
+
render: () => Preview({ slots: { default: Headlines({}) } }),
|
|
615
|
+
}
|
|
616
|
+
`.trimStart();
|
|
617
|
+
}
|
|
618
|
+
function generateStoryButtons() {
|
|
619
|
+
return `
|
|
620
|
+
// Auto-generated — do not edit by hand
|
|
621
|
+
import Preview from '@/astrobook/components/Preview.astro'
|
|
622
|
+
import Buttons from '@/astrobook/components/Buttons.astro'
|
|
623
|
+
|
|
624
|
+
export default {
|
|
625
|
+
title: 'UI/Buttons',
|
|
626
|
+
component: Buttons,
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export const Overview = {
|
|
630
|
+
render: () => Preview({ slots: { default: Buttons({}) } }),
|
|
631
|
+
}
|
|
632
|
+
`.trimStart();
|
|
633
|
+
}
|
|
634
|
+
function generateStoryInputs() {
|
|
635
|
+
return `
|
|
636
|
+
// Auto-generated — do not edit by hand
|
|
637
|
+
import Preview from '@/astrobook/components/Preview.astro'
|
|
638
|
+
import Inputs from '@/astrobook/components/Inputs.astro'
|
|
639
|
+
|
|
640
|
+
export default {
|
|
641
|
+
title: 'UI/Inputs',
|
|
642
|
+
component: Inputs,
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export const Overview = {
|
|
646
|
+
render: () => Preview({ slots: { default: Inputs({}) } }),
|
|
647
|
+
}
|
|
648
|
+
`.trimStart();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* ---------- renderers ---------- */
|
|
652
|
+
|
|
653
|
+
function renderColorsAstro(colors) {
|
|
654
|
+
const items = colors.map(({
|
|
655
|
+
title,
|
|
656
|
+
color,
|
|
657
|
+
class: klass
|
|
658
|
+
}) => {
|
|
659
|
+
const style = `background:${color};`;
|
|
660
|
+
return `
|
|
661
|
+
<div class="flex items-center gap-4 p-3 rounded border border-black/10 bg-white/5">
|
|
662
|
+
<div class="size-10 rounded shadow-inner ring-1 ring-black/10" style="${style}"></div>
|
|
663
|
+
<div class="flex flex-col text-sm">
|
|
664
|
+
<span class="font-medium">${e(title)}</span>
|
|
665
|
+
<span class="opacity-70">token: <code>--color-${e(klass.replace(/^x-/, ''))}</code></span>
|
|
666
|
+
<span class="opacity-70">class: <code>${e(klass)}</code></span>
|
|
667
|
+
<span class="opacity-70">value: <code>${e(color)}</code></span>
|
|
668
|
+
</div>
|
|
669
|
+
</div>`.trim();
|
|
670
|
+
});
|
|
671
|
+
return `---
|
|
672
|
+
/* Auto-generated — do not edit by hand */
|
|
673
|
+
---
|
|
674
|
+
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
675
|
+
${items.join('\n')}
|
|
676
|
+
</div>
|
|
677
|
+
`;
|
|
678
|
+
}
|
|
679
|
+
function renderBodiesAstro(bodies) {
|
|
680
|
+
const items = bodies.map(b => {
|
|
681
|
+
const meta = metaText(b.size, b.lineheight, b.config);
|
|
682
|
+
return ` <p class="${a(b.class)}">${e(b.title)} ${meta}</p>`;
|
|
683
|
+
});
|
|
684
|
+
return `---
|
|
685
|
+
/* Auto-generated — do not edit by hand */
|
|
686
|
+
---
|
|
687
|
+
<div class="space-y-4">
|
|
688
|
+
${items.join('\n')}
|
|
689
|
+
</div>
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
function renderHeadlinesAstro(headlines) {
|
|
693
|
+
const lines = headlines.map(h => {
|
|
694
|
+
const meta = metaText(h.size, h.lineheight, h.config);
|
|
695
|
+
return ` <div class="flex items-baseline gap-2">
|
|
696
|
+
<Headline size="2" text="${a(h.title)}" class="${a(h.class)}" />
|
|
697
|
+
${meta}
|
|
698
|
+
</div>`;
|
|
699
|
+
});
|
|
700
|
+
return `---
|
|
701
|
+
/* Auto-generated — do not edit by hand */
|
|
702
|
+
import Headline from '@/components/core/Headline.astro'
|
|
703
|
+
---
|
|
704
|
+
<div class="space-y-4">
|
|
705
|
+
${lines.join('\n')}
|
|
706
|
+
</div>
|
|
707
|
+
`;
|
|
708
|
+
}
|
|
709
|
+
function renderButtonsAstro(buttons) {
|
|
710
|
+
const lines = buttons.map(b => {
|
|
711
|
+
const klass = b.class.trim(); // variants already @apply button; don't prepend "button"
|
|
712
|
+
return ` <div class="flex items-center gap-3">
|
|
713
|
+
<Button text="${a(b.text)}" modifier="${a(klass)}" />
|
|
714
|
+
<span class="text-sm opacity-70">${e(b.title)} — <code>${e(klass)}</code></span>
|
|
715
|
+
</div>`;
|
|
716
|
+
});
|
|
717
|
+
return `---
|
|
718
|
+
/* Auto-generated — do not edit by hand */
|
|
719
|
+
import Button from '@/components/core/button/Button.astro' /* adjust if path differs */
|
|
720
|
+
---
|
|
721
|
+
<div class="space-y-3">
|
|
722
|
+
${lines.join('\n')}
|
|
723
|
+
</div>
|
|
724
|
+
`;
|
|
725
|
+
}
|
|
726
|
+
function renderInputsAstro(spec) {
|
|
727
|
+
const {
|
|
728
|
+
inputs = [],
|
|
729
|
+
selects = [],
|
|
730
|
+
checkboxes = [],
|
|
731
|
+
radios = [],
|
|
732
|
+
textareas = []
|
|
733
|
+
} = spec;
|
|
734
|
+
const renderGroup = (title, componentTag, items) => {
|
|
735
|
+
if (!items?.length) {
|
|
736
|
+
return '';
|
|
737
|
+
}
|
|
738
|
+
const rows = items.map(obj => ` <${componentTag} ${attrs(obj)} />`).join('\n');
|
|
739
|
+
return `
|
|
740
|
+
<section>
|
|
741
|
+
<h3 class="text-sm opacity-70 mb-2">${e(title)}</h3>
|
|
742
|
+
${rows}
|
|
743
|
+
</section>`;
|
|
744
|
+
};
|
|
745
|
+
return `---
|
|
746
|
+
/* Auto-generated — do not edit by hand */
|
|
747
|
+
import Input from '@/components/core/inputs/Input.astro'
|
|
748
|
+
import Select from '@/components/core/inputs/Select.astro'
|
|
749
|
+
import Selection from '@/components/core/inputs/Selection.astro'
|
|
750
|
+
---
|
|
751
|
+
<div class="space-y-8">
|
|
752
|
+
${renderGroup('Inputs', 'Input', inputs)}
|
|
753
|
+
${renderGroup('Selects', 'Select', selects)}
|
|
754
|
+
${renderGroup('Checkboxes', 'Selection', checkboxes)}
|
|
755
|
+
${renderGroup('Radios', 'Selection', radios)}
|
|
756
|
+
${renderGroup('Textareas', 'Select', textareas)}
|
|
757
|
+
</div>
|
|
758
|
+
`;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* ---------- helpers ---------- */
|
|
762
|
+
|
|
763
|
+
function attrs(obj = {}) {
|
|
764
|
+
const parts = [];
|
|
765
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
766
|
+
if (val === '' || val === null || val === undefined || val === false) {
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (typeof val === 'string') {
|
|
770
|
+
parts.push(`${key}="${a(val)}"`);
|
|
771
|
+
} else if (typeof val === 'number') {
|
|
772
|
+
parts.push(`${key}={${val}}`);
|
|
773
|
+
} else if (typeof val === 'boolean') {
|
|
774
|
+
parts.push(`${key}={true}`);
|
|
775
|
+
} else {
|
|
776
|
+
parts.push(`${key}={${JSON.stringify(val)}}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return parts.join(' ');
|
|
780
|
+
}
|
|
781
|
+
function metaText(size, lineheight, config) {
|
|
782
|
+
const basePair = pair({
|
|
783
|
+
size,
|
|
784
|
+
lineHeight: lineheight
|
|
785
|
+
});
|
|
786
|
+
const parts = [];
|
|
787
|
+
if (config?.desktop && (config.desktop.size || config.desktop.lineHeight)) {
|
|
788
|
+
parts.push(`D: ${pair(config.desktop)}`);
|
|
789
|
+
}
|
|
790
|
+
if (config?.mobile && (config.mobile.size || config.mobile.lineHeight)) {
|
|
791
|
+
parts.push(`M: ${pair(config.mobile)}`);
|
|
792
|
+
}
|
|
793
|
+
const label = parts.length ? parts.join(' • ') : basePair;
|
|
794
|
+
if (!label) {
|
|
795
|
+
return '';
|
|
796
|
+
}
|
|
797
|
+
return `<span class="opacity-60 text-sm">${e(label)}</span>`;
|
|
798
|
+
}
|
|
799
|
+
function pair({
|
|
800
|
+
size,
|
|
801
|
+
lineHeight
|
|
802
|
+
} = {}) {
|
|
803
|
+
const s = size ?? null;
|
|
804
|
+
const l = lineHeight ?? null;
|
|
805
|
+
if (!s && !l) {
|
|
806
|
+
return '';
|
|
807
|
+
}
|
|
808
|
+
return `${s ?? '-'} / ${l ?? '-'}`;
|
|
809
|
+
}
|
|
810
|
+
function e(s = '') {
|
|
811
|
+
return String(s).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
812
|
+
}
|
|
813
|
+
function a(s = '') {
|
|
814
|
+
return String(s).replaceAll('"', '"');
|
|
815
|
+
}
|
|
816
|
+
function rel(p) {
|
|
817
|
+
return path.relative(process.cwd(), p);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export { generateAstro as default };
|