@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.
Files changed (37) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +18 -0
  3. package/dist/generate-astro-AcegHzWG.js +476 -0
  4. package/dist/generate-astro-B225JTMY.js +534 -0
  5. package/dist/generate-astro-Btr2jiyr.js +389 -0
  6. package/dist/generate-astro-CpNCOn_3.js +475 -0
  7. package/dist/generate-astro-CqXZcTfi.js +820 -0
  8. package/dist/generate-astro-D-FcwrAx.js +819 -0
  9. package/dist/generate-astro-DfaWTEF_.js +539 -0
  10. package/dist/generate-astro-DwBkft4p.js +820 -0
  11. package/dist/generate-razor--hJCWG13.js +454 -0
  12. package/dist/generate-razor-B0Pid10L.js +441 -0
  13. package/dist/generate-razor-B0n9FUR8.js +441 -0
  14. package/dist/generate-razor-B3a_-uDf.js +441 -0
  15. package/dist/generate-razor-BLNML3CC.js +441 -0
  16. package/dist/generate-razor-CABSDQok.js +454 -0
  17. package/dist/generate-razor-CGhqwpRL.js +413 -0
  18. package/dist/generate-razor-DcAkNKya.js +9 -0
  19. package/dist/generate-razor-DwXDvSTP.js +332 -0
  20. package/dist/generate-razor-IGs8o9fx.js +441 -0
  21. package/dist/generate-razor-nGTa0EVW.js +335 -0
  22. package/dist/generate-tailwind-16Mayr2z.js +1050 -0
  23. package/dist/generate-tailwind-BR1-fpNI.js +1056 -0
  24. package/dist/generate-tailwind-BVpI-hUx.js +1057 -0
  25. package/dist/generate-tailwind-Bd3ltCQR.js +1084 -0
  26. package/dist/generate-tailwind-BljcUYMa.js +1051 -0
  27. package/dist/generate-tailwind-BqZCEvj5.js +1049 -0
  28. package/dist/generate-tailwind-CGwQarzK.js +1059 -0
  29. package/dist/generate-tailwind-Cpb8YNNz.js +1050 -0
  30. package/dist/generate-tailwind-CtUsHnBd.js +1060 -0
  31. package/dist/generate-tailwind-DL0GEqR6.js +1050 -0
  32. package/dist/generate-tailwind-Dr3KLBMD.js +1045 -0
  33. package/dist/generate-tailwind-KzRHwyMG.js +1049 -0
  34. package/dist/generate-tailwind-VnkcDTZP.js +1049 -0
  35. package/dist/index.esm.js +58 -0
  36. package/dist/inputs-CQVgm9Do.js +439 -0
  37. 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('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
812
+ }
813
+ function a(s = '') {
814
+ return String(s).replaceAll('"', '&quot;');
815
+ }
816
+ function rel(p) {
817
+ return path.relative(process.cwd(), p);
818
+ }
819
+
820
+ export { generateAstro as default };