@fragments-sdk/cli 0.15.3 → 0.15.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +1 -1
- package/dist/{create-JVAU3YKN.js → create-AC2PMGBF.js} +394 -145
- package/dist/create-AC2PMGBF.js.map +1 -0
- package/package.json +18 -18
- package/src/bin.ts +1 -1
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +1 -1
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +1 -1
- package/src/commands/__tests__/create.test.ts +10 -3
- package/src/commands/create.ts +378 -83
- package/src/theme/seed-presets.ts +104 -0
- package/dist/create-JVAU3YKN.js.map +0 -1
|
@@ -298,72 +298,6 @@ function generateDarkTokens(config, format) {
|
|
|
298
298
|
}
|
|
299
299
|
return tokens;
|
|
300
300
|
}
|
|
301
|
-
function generateScssTokens(config) {
|
|
302
|
-
const lines = [];
|
|
303
|
-
lines.push("// Auto-generated by @fragments-sdk/cli");
|
|
304
|
-
lines.push(`// Theme: ${config.name}`);
|
|
305
|
-
if (config.version) {
|
|
306
|
-
lines.push(`// Version: ${config.version}`);
|
|
307
|
-
}
|
|
308
|
-
lines.push("");
|
|
309
|
-
const colorTokens = generateCategoryTokens(config, "colors", "scss");
|
|
310
|
-
if (colorTokens.length > 0) {
|
|
311
|
-
lines.push("// Colors");
|
|
312
|
-
lines.push(...colorTokens);
|
|
313
|
-
lines.push("");
|
|
314
|
-
}
|
|
315
|
-
const surfaceTokens = generateCategoryTokens(config, "surfaces", "scss");
|
|
316
|
-
if (surfaceTokens.length > 0) {
|
|
317
|
-
lines.push("// Surfaces");
|
|
318
|
-
lines.push(...surfaceTokens);
|
|
319
|
-
lines.push("");
|
|
320
|
-
}
|
|
321
|
-
const textTokens = generateCategoryTokens(config, "text", "scss");
|
|
322
|
-
if (textTokens.length > 0) {
|
|
323
|
-
lines.push("// Text");
|
|
324
|
-
lines.push(...textTokens);
|
|
325
|
-
lines.push("");
|
|
326
|
-
}
|
|
327
|
-
const borderTokens = generateCategoryTokens(config, "borders", "scss");
|
|
328
|
-
if (borderTokens.length > 0) {
|
|
329
|
-
lines.push("// Borders");
|
|
330
|
-
lines.push(...borderTokens);
|
|
331
|
-
if (config.borders?.default) {
|
|
332
|
-
lines.push(`$fui-border-default: ${config.borders.default} !default;`);
|
|
333
|
-
}
|
|
334
|
-
lines.push("");
|
|
335
|
-
}
|
|
336
|
-
const typographyTokens = generateCategoryTokens(config, "typography", "scss");
|
|
337
|
-
if (typographyTokens.length > 0) {
|
|
338
|
-
lines.push("// Typography");
|
|
339
|
-
lines.push(...typographyTokens);
|
|
340
|
-
lines.push("");
|
|
341
|
-
}
|
|
342
|
-
const radiusTokens = generateCategoryTokens(config, "radius", "scss");
|
|
343
|
-
if (radiusTokens.length > 0) {
|
|
344
|
-
lines.push("// Border Radius");
|
|
345
|
-
lines.push(...radiusTokens);
|
|
346
|
-
lines.push("");
|
|
347
|
-
}
|
|
348
|
-
const shadowTokens = generateCategoryTokens(config, "shadows", "scss");
|
|
349
|
-
if (shadowTokens.length > 0) {
|
|
350
|
-
lines.push("// Shadows");
|
|
351
|
-
lines.push(...shadowTokens);
|
|
352
|
-
lines.push("");
|
|
353
|
-
}
|
|
354
|
-
if (config.density) {
|
|
355
|
-
lines.push("// Density");
|
|
356
|
-
lines.push(`$fui-density: "${config.density}" !default;`);
|
|
357
|
-
lines.push("");
|
|
358
|
-
}
|
|
359
|
-
const darkTokens = generateDarkTokens(config, "scss");
|
|
360
|
-
if (darkTokens.length > 0) {
|
|
361
|
-
lines.push("// Dark Mode");
|
|
362
|
-
lines.push(...darkTokens);
|
|
363
|
-
lines.push("");
|
|
364
|
-
}
|
|
365
|
-
return lines.join("\n");
|
|
366
|
-
}
|
|
367
301
|
function generateCssTokens(config) {
|
|
368
302
|
const lines = [];
|
|
369
303
|
lines.push("/* Auto-generated by @fragments-sdk/cli */");
|
|
@@ -404,6 +338,66 @@ function generateCssTokens(config) {
|
|
|
404
338
|
return lines.join("\n");
|
|
405
339
|
}
|
|
406
340
|
|
|
341
|
+
// src/theme/seed-presets.ts
|
|
342
|
+
var SEED_PRESETS = [
|
|
343
|
+
{
|
|
344
|
+
name: "Stone",
|
|
345
|
+
description: "warm gray",
|
|
346
|
+
seeds: { brand: "#52525b", neutral: "stone", density: "default", radiusStyle: "default" }
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "Sand",
|
|
350
|
+
description: "warm beige",
|
|
351
|
+
seeds: { brand: "#8b5e34", neutral: "sand", density: "default", radiusStyle: "default" }
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: "Ice",
|
|
355
|
+
description: "cool blue",
|
|
356
|
+
seeds: { brand: "#0284c7", neutral: "ice", density: "default", radiusStyle: "default" }
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: "Earth",
|
|
360
|
+
description: "olive green",
|
|
361
|
+
seeds: { brand: "#517035", neutral: "earth", density: "default", radiusStyle: "default" }
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "Fire",
|
|
365
|
+
description: "warm orange",
|
|
366
|
+
seeds: { brand: "#ea580c", neutral: "fire", density: "default", radiusStyle: "default" }
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
var SEED_DEFAULTS = {
|
|
370
|
+
brand: "#18181b",
|
|
371
|
+
neutral: "stone",
|
|
372
|
+
density: "default",
|
|
373
|
+
radiusStyle: "default"
|
|
374
|
+
};
|
|
375
|
+
function generateSeedScss(seeds) {
|
|
376
|
+
const overrides = [];
|
|
377
|
+
if (seeds.brand !== SEED_DEFAULTS.brand) {
|
|
378
|
+
overrides.push(` $fui-brand: ${seeds.brand}`);
|
|
379
|
+
}
|
|
380
|
+
if (seeds.neutral !== SEED_DEFAULTS.neutral) {
|
|
381
|
+
overrides.push(` $fui-neutral: "${seeds.neutral}"`);
|
|
382
|
+
}
|
|
383
|
+
if (seeds.density !== SEED_DEFAULTS.density) {
|
|
384
|
+
overrides.push(` $fui-density: "${seeds.density}"`);
|
|
385
|
+
}
|
|
386
|
+
if (seeds.radiusStyle !== SEED_DEFAULTS.radiusStyle) {
|
|
387
|
+
overrides.push(` $fui-radius-style: "${seeds.radiusStyle}"`);
|
|
388
|
+
}
|
|
389
|
+
if (overrides.length === 0) {
|
|
390
|
+
return `// Fragments UI \u2014 tokens, resets, dark mode
|
|
391
|
+
@use '@fragments-sdk/ui/styles';
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
return `// Fragments UI \u2014 tokens, resets, dark mode
|
|
395
|
+
@use '@fragments-sdk/ui/styles' with (
|
|
396
|
+
${overrides.join(",\n")}
|
|
397
|
+
);
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
400
|
+
|
|
407
401
|
// src/commands/create.ts
|
|
408
402
|
function extractFontFamily(cssFontStack) {
|
|
409
403
|
const match = cssFontStack.match(/^["']?([^"',]+)["']?/);
|
|
@@ -481,7 +475,7 @@ async function resolveTheme(options) {
|
|
|
481
475
|
}
|
|
482
476
|
};
|
|
483
477
|
}
|
|
484
|
-
function generateNextjsLayout(themePath, theme) {
|
|
478
|
+
function generateNextjsLayout(themePath, theme, useSeedPath = false) {
|
|
485
479
|
const fontName = theme?.typography?.fontSans ? extractFontFamily(theme.typography.fontSans) : null;
|
|
486
480
|
const fontUrl = fontName ? googleFontsUrl(fontName) : null;
|
|
487
481
|
const htmlOpen = fontUrl ? ` <html lang="en" suppressHydrationWarning>
|
|
@@ -492,9 +486,10 @@ function generateNextjsLayout(themePath, theme) {
|
|
|
492
486
|
</head>
|
|
493
487
|
<body>` : ` <html lang="en" suppressHydrationWarning>
|
|
494
488
|
<body>`;
|
|
489
|
+
const stylesImport = useSeedPath ? `import '${themePath}';` : `import '@fragments-sdk/ui/styles';
|
|
490
|
+
import '${themePath}';`;
|
|
495
491
|
return `import type { Metadata } from 'next';
|
|
496
|
-
|
|
497
|
-
import '${themePath}';
|
|
492
|
+
${stylesImport}
|
|
498
493
|
import { Providers } from './providers';
|
|
499
494
|
|
|
500
495
|
export const metadata: Metadata = {
|
|
@@ -536,37 +531,251 @@ export function Providers({ children }: { children: ReactNode }) {
|
|
|
536
531
|
function generateNextjsPage() {
|
|
537
532
|
return `'use client';
|
|
538
533
|
|
|
539
|
-
import {
|
|
534
|
+
import { useState } from 'react';
|
|
535
|
+
import {
|
|
536
|
+
Avatar,
|
|
537
|
+
Badge,
|
|
538
|
+
Button,
|
|
539
|
+
Card,
|
|
540
|
+
Checkbox,
|
|
541
|
+
Input,
|
|
542
|
+
Progress,
|
|
543
|
+
Separator,
|
|
544
|
+
Stack,
|
|
545
|
+
Switch,
|
|
546
|
+
Tabs,
|
|
547
|
+
Text,
|
|
548
|
+
Tooltip,
|
|
549
|
+
} from '@fragments-sdk/ui';
|
|
550
|
+
import styles from './page.module.css';
|
|
551
|
+
|
|
552
|
+
function StatCard({ label, value, change }: { label: string; value: string; change: string }) {
|
|
553
|
+
const isPositive = change.startsWith('+');
|
|
554
|
+
return (
|
|
555
|
+
<Card>
|
|
556
|
+
<Card.Body>
|
|
557
|
+
<Stack gap="xs">
|
|
558
|
+
<Text size="sm" color="secondary">{label}</Text>
|
|
559
|
+
<Text size="2xl" weight="semibold">{value}</Text>
|
|
560
|
+
<Badge variant={isPositive ? 'success' : 'error'} size="sm">{change}</Badge>
|
|
561
|
+
</Stack>
|
|
562
|
+
</Card.Body>
|
|
563
|
+
</Card>
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function TaskItem({ title, done }: { title: string; done?: boolean }) {
|
|
568
|
+
const [checked, setChecked] = useState(done ?? false);
|
|
569
|
+
return (
|
|
570
|
+
<Stack direction="row" align="center" gap="sm">
|
|
571
|
+
<Checkbox checked={checked} onChange={() => setChecked(!checked)} />
|
|
572
|
+
<Text size="sm" className={checked ? styles.done : undefined}>{title}</Text>
|
|
573
|
+
</Stack>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
540
576
|
|
|
541
577
|
export default function Home() {
|
|
578
|
+
const [notifications, setNotifications] = useState(true);
|
|
579
|
+
|
|
542
580
|
return (
|
|
543
|
-
<main
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
<
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
581
|
+
<main className={styles.page}>
|
|
582
|
+
{/* Hero */}
|
|
583
|
+
<Stack align="center" gap="md" className={styles.hero}>
|
|
584
|
+
<Badge variant="info">Powered by Fragments UI</Badge>
|
|
585
|
+
<Text as="h1" size="2xl" weight="bold">Your app is ready</Text>
|
|
586
|
+
<Text size="lg" color="secondary" className={styles.heroDescription}>
|
|
587
|
+
This page showcases your custom theme and components.
|
|
588
|
+
Edit <code>src/app/page.tsx</code> to start building.
|
|
589
|
+
</Text>
|
|
590
|
+
<Stack direction="row" gap="sm">
|
|
591
|
+
<Button size="lg">Start building</Button>
|
|
592
|
+
<Button size="lg" variant="secondary">Read the docs</Button>
|
|
593
|
+
</Stack>
|
|
594
|
+
</Stack>
|
|
595
|
+
|
|
596
|
+
<Separator />
|
|
597
|
+
|
|
598
|
+
{/* Stats */}
|
|
599
|
+
<div className={styles.statsGrid}>
|
|
600
|
+
<StatCard label="Components" value="66" change="+12 this month" />
|
|
601
|
+
<StatCard label="Design tokens" value="80" change="+5 new" />
|
|
602
|
+
<StatCard label="Accessibility" value="98%" change="+2.4%" />
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
{/* Content */}
|
|
606
|
+
<div className={styles.contentGrid}>
|
|
607
|
+
<Card>
|
|
608
|
+
<Card.Header>
|
|
609
|
+
<Card.Title>Getting started</Card.Title>
|
|
610
|
+
<Card.Description>Pick up where you left off</Card.Description>
|
|
611
|
+
</Card.Header>
|
|
612
|
+
<Card.Body>
|
|
613
|
+
<Tabs defaultValue="tasks">
|
|
614
|
+
<Tabs.List>
|
|
615
|
+
<Tabs.Tab value="tasks">Tasks</Tabs.Tab>
|
|
616
|
+
<Tabs.Tab value="progress">Progress</Tabs.Tab>
|
|
617
|
+
</Tabs.List>
|
|
618
|
+
<Tabs.Panel value="tasks">
|
|
619
|
+
<Stack gap="sm" className={styles.tabContent}>
|
|
620
|
+
<TaskItem title="Install Fragments UI" done />
|
|
621
|
+
<TaskItem title="Configure your theme" done />
|
|
622
|
+
<TaskItem title="Build your first page" />
|
|
623
|
+
<TaskItem title="Set up MCP for AI tooling" />
|
|
624
|
+
<TaskItem title="Deploy to production" />
|
|
625
|
+
</Stack>
|
|
626
|
+
</Tabs.Panel>
|
|
627
|
+
<Tabs.Panel value="progress">
|
|
628
|
+
<Stack gap="md" className={styles.tabContent}>
|
|
629
|
+
<Stack gap="xs">
|
|
630
|
+
<Stack direction="row" justify="between">
|
|
631
|
+
<Text size="sm">Setup</Text>
|
|
632
|
+
<Text size="sm" color="secondary">2 of 5</Text>
|
|
633
|
+
</Stack>
|
|
634
|
+
<Progress value={40} />
|
|
635
|
+
</Stack>
|
|
636
|
+
<Text size="sm" color="secondary">
|
|
637
|
+
Complete the remaining tasks to finish setting up your project.
|
|
638
|
+
</Text>
|
|
639
|
+
</Stack>
|
|
640
|
+
</Tabs.Panel>
|
|
641
|
+
</Tabs>
|
|
642
|
+
</Card.Body>
|
|
643
|
+
</Card>
|
|
644
|
+
|
|
645
|
+
<Card>
|
|
646
|
+
<Card.Header>
|
|
647
|
+
<Card.Title>Settings</Card.Title>
|
|
648
|
+
<Card.Description>Manage your preferences</Card.Description>
|
|
649
|
+
</Card.Header>
|
|
650
|
+
<Card.Body>
|
|
651
|
+
<Stack gap="md">
|
|
652
|
+
<Input placeholder="Search settings..." />
|
|
653
|
+
<Stack direction="row" align="center" justify="between">
|
|
654
|
+
<Stack gap="xs">
|
|
655
|
+
<Text size="sm" weight="medium">Notifications</Text>
|
|
656
|
+
<Text size="xs" color="secondary">Receive alerts for updates</Text>
|
|
657
|
+
</Stack>
|
|
658
|
+
<Switch checked={notifications} onChange={() => setNotifications(!notifications)} />
|
|
659
|
+
</Stack>
|
|
660
|
+
<Separator />
|
|
661
|
+
<Stack direction="row" align="center" justify="between">
|
|
662
|
+
<Stack gap="xs">
|
|
663
|
+
<Text size="sm" weight="medium">Theme</Text>
|
|
664
|
+
<Text size="xs" color="secondary">System preference detected</Text>
|
|
665
|
+
</Stack>
|
|
666
|
+
<Badge variant="default" size="sm">Auto</Badge>
|
|
667
|
+
</Stack>
|
|
668
|
+
<Separator />
|
|
669
|
+
<Stack direction="row" gap="sm" align="center">
|
|
670
|
+
<Avatar name="You" size="sm" />
|
|
671
|
+
<Stack gap="xs" className={styles.flex1}>
|
|
672
|
+
<Text size="sm" weight="medium">Your account</Text>
|
|
673
|
+
<Text size="xs" color="secondary">Manage profile and billing</Text>
|
|
674
|
+
</Stack>
|
|
675
|
+
<Tooltip content="Go to account settings">
|
|
676
|
+
<Button variant="ghost" size="sm">Manage</Button>
|
|
677
|
+
</Tooltip>
|
|
678
|
+
</Stack>
|
|
555
679
|
</Stack>
|
|
556
|
-
</
|
|
557
|
-
</Card
|
|
558
|
-
</
|
|
680
|
+
</Card.Body>
|
|
681
|
+
</Card>
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
{/* Footer */}
|
|
685
|
+
<Text size="sm" color="secondary" className={styles.footer}>
|
|
686
|
+
Built with{' '}
|
|
687
|
+
<a href="https://usefragments.com" className={styles.link}>Fragments UI</a>
|
|
688
|
+
{' '}— 66 components, 80 design tokens, accessible by default.
|
|
689
|
+
</Text>
|
|
559
690
|
</main>
|
|
560
691
|
);
|
|
561
692
|
}
|
|
562
693
|
`;
|
|
563
694
|
}
|
|
564
|
-
function
|
|
695
|
+
function generatePageCssModule() {
|
|
696
|
+
return `.page {
|
|
697
|
+
min-height: 100vh;
|
|
698
|
+
display: flex;
|
|
699
|
+
flex-direction: column;
|
|
700
|
+
align-items: center;
|
|
701
|
+
padding: var(--fui-space-6, 3rem) var(--fui-space-3, 1.5rem);
|
|
702
|
+
gap: var(--fui-space-4, 2rem);
|
|
703
|
+
max-width: 960px;
|
|
704
|
+
margin: 0 auto;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.hero {
|
|
708
|
+
text-align: center;
|
|
709
|
+
padding-top: var(--fui-space-4, 2rem);
|
|
710
|
+
padding-bottom: var(--fui-space-2, 1rem);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.heroDescription {
|
|
714
|
+
max-width: 480px;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.heroDescription code {
|
|
718
|
+
font-size: 0.875em;
|
|
719
|
+
font-family: var(--fui-font-mono, monospace);
|
|
720
|
+
background: var(--fui-bg-secondary);
|
|
721
|
+
padding: 0.125em 0.375em;
|
|
722
|
+
border-radius: var(--fui-radius-sm, 0.25rem);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.statsGrid {
|
|
726
|
+
display: grid;
|
|
727
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
728
|
+
gap: var(--fui-space-2, 1rem);
|
|
729
|
+
width: 100%;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.contentGrid {
|
|
733
|
+
display: grid;
|
|
734
|
+
grid-template-columns: 1fr 1fr;
|
|
735
|
+
gap: var(--fui-space-3, 1.5rem);
|
|
736
|
+
width: 100%;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
@media (max-width: 768px) {
|
|
740
|
+
.contentGrid {
|
|
741
|
+
grid-template-columns: 1fr;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.tabContent {
|
|
746
|
+
padding-top: var(--fui-space-1, 0.75rem);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.done {
|
|
750
|
+
text-decoration: line-through;
|
|
751
|
+
opacity: 0.5;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.flex1 {
|
|
755
|
+
flex: 1;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.footer {
|
|
759
|
+
padding-top: var(--fui-space-2, 1rem);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.link {
|
|
763
|
+
color: inherit;
|
|
764
|
+
text-decoration: underline;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.link:hover {
|
|
768
|
+
color: var(--fui-color-accent);
|
|
769
|
+
}
|
|
770
|
+
`;
|
|
771
|
+
}
|
|
772
|
+
function generateViteMain(themePath, useSeedPath = false) {
|
|
773
|
+
const stylesImport = useSeedPath ? `import '${themePath}';` : `import '@fragments-sdk/ui/styles';
|
|
774
|
+
import '${themePath}';`;
|
|
565
775
|
return `import { StrictMode } from 'react';
|
|
566
776
|
import { createRoot } from 'react-dom/client';
|
|
567
777
|
import { ThemeProvider, TooltipProvider, ToastProvider } from '@fragments-sdk/ui';
|
|
568
|
-
|
|
569
|
-
import '${themePath}';
|
|
778
|
+
${stylesImport}
|
|
570
779
|
import App from './App';
|
|
571
780
|
|
|
572
781
|
createRoot(document.getElementById('root')!).render(
|
|
@@ -583,32 +792,7 @@ createRoot(document.getElementById('root')!).render(
|
|
|
583
792
|
`;
|
|
584
793
|
}
|
|
585
794
|
function generateViteApp() {
|
|
586
|
-
return
|
|
587
|
-
|
|
588
|
-
function App() {
|
|
589
|
-
return (
|
|
590
|
-
<main style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
591
|
-
<Card style={{ maxWidth: 480, width: '100%' }}>
|
|
592
|
-
<Card.Header>
|
|
593
|
-
<Card.Title>Welcome to Fragments</Card.Title>
|
|
594
|
-
<Card.Description>Your app is ready. Start building something great.</Card.Description>
|
|
595
|
-
</Card.Header>
|
|
596
|
-
<Card.Body>
|
|
597
|
-
<Stack gap={3}>
|
|
598
|
-
<Input placeholder="Enter something..." />
|
|
599
|
-
<Stack direction="row" gap={2}>
|
|
600
|
-
<Button>Get Started</Button>
|
|
601
|
-
<Button variant="secondary">Learn More</Button>
|
|
602
|
-
</Stack>
|
|
603
|
-
</Stack>
|
|
604
|
-
</Card.Body>
|
|
605
|
-
</Card>
|
|
606
|
-
</main>
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
export default App;
|
|
611
|
-
`;
|
|
795
|
+
return generateNextjsPage().replace("'use client';\n\n", "").replace("import styles from './page.module.css';", "import styles from './App.module.css';").replace("src/app/page.tsx", "src/App.tsx").replace("export default function Home()", "function App()") + "\nexport default App;\n";
|
|
612
796
|
}
|
|
613
797
|
function injectFontIntoViteHtml(projectDir, theme) {
|
|
614
798
|
const fontName = theme.typography?.fontSans ? extractFontFamily(theme.typography.fontSans) : null;
|
|
@@ -660,6 +844,51 @@ async function promptIfMissing(options) {
|
|
|
660
844
|
}
|
|
661
845
|
resolved.template = resolved.template || "nextjs";
|
|
662
846
|
resolved.packageManager = resolved.packageManager || detectPackageManager();
|
|
847
|
+
if (!resolved.preset && !resolved.theme && !resolved.brand && !resolved.yes) {
|
|
848
|
+
try {
|
|
849
|
+
const { select, input } = await import("@inquirer/prompts");
|
|
850
|
+
const choices = SEED_PRESETS.map((p) => ({
|
|
851
|
+
name: `${p.name} (${p.description})`,
|
|
852
|
+
value: p.name.toLowerCase()
|
|
853
|
+
}));
|
|
854
|
+
choices.push({ name: "Custom (pick color + palette)", value: "custom" });
|
|
855
|
+
const selected = await select({
|
|
856
|
+
message: "Theme:",
|
|
857
|
+
choices,
|
|
858
|
+
default: "stone"
|
|
859
|
+
});
|
|
860
|
+
if (selected === "custom") {
|
|
861
|
+
const brandColor = await input({
|
|
862
|
+
message: "Brand color (hex):",
|
|
863
|
+
default: "#6366f1",
|
|
864
|
+
validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) || "Enter a valid hex color (e.g., #6366f1)"
|
|
865
|
+
});
|
|
866
|
+
const neutral = await select({
|
|
867
|
+
message: "Neutral palette:",
|
|
868
|
+
choices: [
|
|
869
|
+
{ name: "Stone (warm gray)", value: "stone" },
|
|
870
|
+
{ name: "Sand (warm beige)", value: "sand" },
|
|
871
|
+
{ name: "Ice (cool blue)", value: "ice" },
|
|
872
|
+
{ name: "Earth (olive green)", value: "earth" },
|
|
873
|
+
{ name: "Fire (warm orange)", value: "fire" }
|
|
874
|
+
],
|
|
875
|
+
default: "stone"
|
|
876
|
+
});
|
|
877
|
+
resolved.seeds = {
|
|
878
|
+
brand: brandColor,
|
|
879
|
+
neutral,
|
|
880
|
+
density: "default",
|
|
881
|
+
radiusStyle: "default"
|
|
882
|
+
};
|
|
883
|
+
} else {
|
|
884
|
+
const preset = SEED_PRESETS.find((p) => p.name.toLowerCase() === selected);
|
|
885
|
+
if (preset) {
|
|
886
|
+
resolved.seeds = { ...preset.seeds };
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
} catch {
|
|
890
|
+
}
|
|
891
|
+
}
|
|
663
892
|
return resolved;
|
|
664
893
|
}
|
|
665
894
|
function scaffoldFramework(name, template, pm) {
|
|
@@ -677,26 +906,33 @@ Scaffolding ${template === "nextjs" ? "Next.js" : "Vite + React"} project...
|
|
|
677
906
|
}
|
|
678
907
|
}
|
|
679
908
|
}
|
|
680
|
-
function installDeps(projectDir, pm
|
|
909
|
+
function installDeps(projectDir, pm) {
|
|
681
910
|
console.log(pc.cyan("\nInstalling Fragments UI...\n"));
|
|
682
911
|
const install = getInstallCommand(pm);
|
|
912
|
+
const devInstall = getDevInstallCommand(pm);
|
|
683
913
|
execSync(`${install} @fragments-sdk/ui`, { cwd: projectDir, stdio: "inherit" });
|
|
684
|
-
|
|
685
|
-
const devInstall = getDevInstallCommand(pm);
|
|
686
|
-
execSync(`${devInstall} sass`, { cwd: projectDir, stdio: "inherit" });
|
|
687
|
-
}
|
|
914
|
+
execSync(`${devInstall} sass`, { cwd: projectDir, stdio: "inherit" });
|
|
688
915
|
}
|
|
689
|
-
function writeThemeFile(projectDir,
|
|
916
|
+
function writeThemeFile(projectDir, template, source) {
|
|
690
917
|
const stylesDir = join2(projectDir, "src", "styles");
|
|
691
918
|
mkdirSync(stylesDir, { recursive: true });
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
919
|
+
let content;
|
|
920
|
+
if (source.kind === "seeds") {
|
|
921
|
+
content = generateSeedScss(source.seeds);
|
|
922
|
+
} else {
|
|
923
|
+
const cssContent = generateCssTokens(source.theme);
|
|
924
|
+
content = `// Fragments UI \u2014 tokens, resets, dark mode
|
|
925
|
+
@use '@fragments-sdk/ui/styles';
|
|
926
|
+
|
|
927
|
+
// Theme overrides (generated from your preset)
|
|
928
|
+
${cssContent}`;
|
|
929
|
+
}
|
|
930
|
+
const filePath = join2(stylesDir, "theme.scss");
|
|
695
931
|
writeFileSync(filePath, content, "utf-8");
|
|
696
932
|
if (template === "nextjs") {
|
|
697
|
-
return
|
|
933
|
+
return "../styles/theme.scss";
|
|
698
934
|
}
|
|
699
|
-
return
|
|
935
|
+
return "./styles/theme.scss";
|
|
700
936
|
}
|
|
701
937
|
function addNextTranspilePackages(projectDir) {
|
|
702
938
|
const configCandidates = ["next.config.ts", "next.config.mjs", "next.config.js"];
|
|
@@ -739,19 +975,16 @@ function addNextTranspilePackages(projectDir) {
|
|
|
739
975
|
return;
|
|
740
976
|
}
|
|
741
977
|
}
|
|
742
|
-
function rewriteAppFiles(projectDir, template, themePath, theme) {
|
|
978
|
+
function rewriteAppFiles(projectDir, template, themePath, theme, useSeedPath) {
|
|
743
979
|
if (template === "nextjs") {
|
|
744
980
|
const layoutPath = join2(projectDir, "src", "app", "layout.tsx");
|
|
745
|
-
writeFileSync(layoutPath, generateNextjsLayout(themePath, theme), "utf-8");
|
|
981
|
+
writeFileSync(layoutPath, generateNextjsLayout(themePath, theme ?? void 0, useSeedPath), "utf-8");
|
|
746
982
|
const providersPath = join2(projectDir, "src", "app", "providers.tsx");
|
|
747
983
|
writeFileSync(providersPath, generateNextjsProviders(), "utf-8");
|
|
748
984
|
const pagePath = join2(projectDir, "src", "app", "page.tsx");
|
|
749
985
|
writeFileSync(pagePath, generateNextjsPage(), "utf-8");
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
unlinkSync(moduleCssPath);
|
|
753
|
-
} catch {
|
|
754
|
-
}
|
|
986
|
+
const pageCssPath = join2(projectDir, "src", "app", "page.module.css");
|
|
987
|
+
writeFileSync(pageCssPath, generatePageCssModule(), "utf-8");
|
|
755
988
|
const globalsCssPath = join2(projectDir, "src", "app", "globals.css");
|
|
756
989
|
try {
|
|
757
990
|
unlinkSync(globalsCssPath);
|
|
@@ -760,16 +993,20 @@ function rewriteAppFiles(projectDir, template, themePath, theme) {
|
|
|
760
993
|
addNextTranspilePackages(projectDir);
|
|
761
994
|
} else {
|
|
762
995
|
const mainPath = join2(projectDir, "src", "main.tsx");
|
|
763
|
-
writeFileSync(mainPath, generateViteMain(themePath), "utf-8");
|
|
996
|
+
writeFileSync(mainPath, generateViteMain(themePath, useSeedPath), "utf-8");
|
|
764
997
|
const appPath = join2(projectDir, "src", "App.tsx");
|
|
765
998
|
writeFileSync(appPath, generateViteApp(), "utf-8");
|
|
999
|
+
const appCssModulePath = join2(projectDir, "src", "App.module.css");
|
|
1000
|
+
writeFileSync(appCssModulePath, generatePageCssModule(), "utf-8");
|
|
766
1001
|
for (const file of ["src/App.css", "src/index.css"]) {
|
|
767
1002
|
try {
|
|
768
1003
|
unlinkSync(join2(projectDir, file));
|
|
769
1004
|
} catch {
|
|
770
1005
|
}
|
|
771
1006
|
}
|
|
772
|
-
|
|
1007
|
+
if (theme) {
|
|
1008
|
+
injectFontIntoViteHtml(projectDir, theme);
|
|
1009
|
+
}
|
|
773
1010
|
}
|
|
774
1011
|
}
|
|
775
1012
|
function initGit(projectDir) {
|
|
@@ -803,6 +1040,7 @@ ${BRAND.name} Create
|
|
|
803
1040
|
const name = resolved.name;
|
|
804
1041
|
const template = resolved.template;
|
|
805
1042
|
const pm = resolved.packageManager;
|
|
1043
|
+
const hasSeedPath = !!resolved.seeds;
|
|
806
1044
|
if (!isValidProjectName(name)) {
|
|
807
1045
|
return { success: false, error: `Invalid project name: ${name}. Use lowercase letters, numbers, hyphens, dots, underscores.` };
|
|
808
1046
|
}
|
|
@@ -810,18 +1048,21 @@ ${BRAND.name} Create
|
|
|
810
1048
|
if (existsSync(projectDir)) {
|
|
811
1049
|
return { success: false, error: `Directory "${name}" already exists.` };
|
|
812
1050
|
}
|
|
813
|
-
|
|
814
|
-
if (!
|
|
815
|
-
|
|
1051
|
+
let theme = null;
|
|
1052
|
+
if (!hasSeedPath) {
|
|
1053
|
+
theme = await resolveTheme(resolved);
|
|
1054
|
+
if (!theme) {
|
|
1055
|
+
return { success: false, error: "Invalid theme configuration." };
|
|
1056
|
+
}
|
|
816
1057
|
}
|
|
817
1058
|
scaffoldFramework(name, template, pm);
|
|
818
1059
|
if (!existsSync(projectDir)) {
|
|
819
1060
|
return { success: false, error: "Framework scaffolding failed \u2014 project directory was not created." };
|
|
820
1061
|
}
|
|
821
|
-
installDeps(projectDir, pm
|
|
822
|
-
const themePath = writeThemeFile(projectDir,
|
|
823
|
-
rewriteAppFiles(projectDir, template, themePath, theme);
|
|
824
|
-
if (resolved.mcp) {
|
|
1062
|
+
installDeps(projectDir, pm);
|
|
1063
|
+
const themePath = hasSeedPath ? writeThemeFile(projectDir, template, { kind: "seeds", seeds: resolved.seeds }) : writeThemeFile(projectDir, template, { kind: "css", theme });
|
|
1064
|
+
rewriteAppFiles(projectDir, template, themePath, theme, hasSeedPath);
|
|
1065
|
+
if (resolved.mcp !== false) {
|
|
825
1066
|
configureMcp(projectDir);
|
|
826
1067
|
console.log(pc.dim(" Configured MCP server for AI tooling"));
|
|
827
1068
|
}
|
|
@@ -835,9 +1076,16 @@ ${BRAND.name} Create
|
|
|
835
1076
|
console.log(` ${pc.cyan("cd")} ${name}`);
|
|
836
1077
|
console.log(` ${pc.cyan(run)} dev`);
|
|
837
1078
|
console.log("");
|
|
838
|
-
if (
|
|
1079
|
+
if (hasSeedPath) {
|
|
1080
|
+
const seeds = resolved.seeds;
|
|
1081
|
+
const presetName = seeds.neutral.charAt(0).toUpperCase() + seeds.neutral.slice(1);
|
|
1082
|
+
console.log(pc.dim(` Theme: ${presetName} palette with ${seeds.brand} brand`));
|
|
1083
|
+
console.log(pc.dim(` Edit src/styles/theme.scss to customize seed values.`));
|
|
1084
|
+
console.log(pc.dim(` Fine-tune: ${pc.cyan("fragments init --configure")}
|
|
1085
|
+
`));
|
|
1086
|
+
} else if (theme && theme.name !== "default") {
|
|
839
1087
|
console.log(pc.dim(` Theme "${theme.name}" applied with full token set.`));
|
|
840
|
-
console.log(pc.dim(` Edit src/styles/theme
|
|
1088
|
+
console.log(pc.dim(` Edit src/styles/theme.scss to customize.
|
|
841
1089
|
`));
|
|
842
1090
|
}
|
|
843
1091
|
return { success: true, projectDir };
|
|
@@ -847,6 +1095,7 @@ export {
|
|
|
847
1095
|
create,
|
|
848
1096
|
generateNextjsLayout,
|
|
849
1097
|
generateNextjsPage,
|
|
850
|
-
generateNextjsProviders
|
|
1098
|
+
generateNextjsProviders,
|
|
1099
|
+
generatePageCssModule
|
|
851
1100
|
};
|
|
852
|
-
//# sourceMappingURL=create-
|
|
1101
|
+
//# sourceMappingURL=create-AC2PMGBF.js.map
|