gradient-forge 1.0.0 → 1.0.2
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/cli/index.mjs +132 -62
- package/package.json +1 -2
package/cli/index.mjs
CHANGED
|
@@ -567,16 +567,10 @@ const promptSelect = async (title, items, defaultIndex = 0) => {
|
|
|
567
567
|
}
|
|
568
568
|
|
|
569
569
|
let index = defaultIndex;
|
|
570
|
-
let
|
|
570
|
+
let buffer = "";
|
|
571
571
|
|
|
572
572
|
const render = () => {
|
|
573
|
-
|
|
574
|
-
process.stdout.write("\x1b[s");
|
|
575
|
-
isFirstRender = false;
|
|
576
|
-
} else {
|
|
577
|
-
process.stdout.write("\x1b[u");
|
|
578
|
-
process.stdout.write("\x1b[0J");
|
|
579
|
-
}
|
|
573
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
580
574
|
log(title);
|
|
581
575
|
log("");
|
|
582
576
|
items.forEach((item, i) => {
|
|
@@ -588,27 +582,35 @@ const promptSelect = async (title, items, defaultIndex = 0) => {
|
|
|
588
582
|
};
|
|
589
583
|
|
|
590
584
|
const onKey = (data) => {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
585
|
+
buffer += data.toString();
|
|
586
|
+
|
|
587
|
+
// Handle escape sequences for arrow keys
|
|
588
|
+
if (buffer.includes("\u001b[A")) {
|
|
589
|
+
// Up arrow
|
|
590
|
+
index = index > 0 ? index - 1 : items.length - 1;
|
|
591
|
+
buffer = "";
|
|
592
|
+
render();
|
|
593
|
+
} else if (buffer.includes("\u001b[B")) {
|
|
594
|
+
// Down arrow
|
|
595
|
+
index = index < items.length - 1 ? index + 1 : 0;
|
|
596
|
+
buffer = "";
|
|
597
|
+
render();
|
|
598
|
+
} else if (buffer.includes("\r") || buffer.includes("\n")) {
|
|
599
|
+
// Enter key
|
|
598
600
|
process.stdin.setRawMode(false);
|
|
599
601
|
process.stdin.pause();
|
|
600
602
|
process.stdin.removeListener("data", onKey);
|
|
601
603
|
process.stdout.write("\x1b[?25h");
|
|
602
604
|
resolve(items[index]);
|
|
603
605
|
return;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
606
|
+
} else if (buffer.includes("\u0003")) {
|
|
607
|
+
// Ctrl+C
|
|
608
|
+
process.stdin.setRawMode(false);
|
|
609
|
+
process.stdin.pause();
|
|
610
|
+
process.exit(1);
|
|
611
|
+
} else {
|
|
612
|
+
// Clear buffer after processing
|
|
613
|
+
buffer = "";
|
|
612
614
|
}
|
|
613
615
|
};
|
|
614
616
|
|
|
@@ -685,8 +687,13 @@ type ThemeContextValue = {
|
|
|
685
687
|
colorMode: ColorMode;
|
|
686
688
|
setThemeId: (themeId: ThemeId) => void;
|
|
687
689
|
setColorMode: (mode: ColorMode) => void;
|
|
690
|
+
themes: { id: ThemeId; label: string }[];
|
|
688
691
|
};
|
|
689
692
|
|
|
693
|
+
const allThemes: { id: ThemeId; label: string }[] = [
|
|
694
|
+
${themes.map(t => ` { id: "${t.id}", label: "${t.label}" },`).join("\n")}
|
|
695
|
+
];
|
|
696
|
+
|
|
690
697
|
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
691
698
|
|
|
692
699
|
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
|
|
@@ -696,7 +703,7 @@ export const ThemeProvider = ({ children }: { children: ReactNode }) => {
|
|
|
696
703
|
useEffect(() => {
|
|
697
704
|
const root = document.documentElement;
|
|
698
705
|
root.classList.remove("dark", "light");
|
|
699
|
-
|
|
706
|
+
${themes.map(t => ` root.classList.remove("${t.id}");`).join("\n")}
|
|
700
707
|
|
|
701
708
|
root.classList.add(colorMode, themeId);
|
|
702
709
|
root.setAttribute("data-theme", themeId);
|
|
@@ -707,7 +714,7 @@ export const ThemeProvider = ({ children }: { children: ReactNode }) => {
|
|
|
707
714
|
}, [themeId, colorMode]);
|
|
708
715
|
|
|
709
716
|
return (
|
|
710
|
-
<ThemeContext.Provider value={{ themeId, colorMode, setThemeId, setColorMode }}>
|
|
717
|
+
<ThemeContext.Provider value={{ themeId, colorMode, setThemeId, setColorMode, themes: allThemes }}>
|
|
711
718
|
{children}
|
|
712
719
|
</ThemeContext.Provider>
|
|
713
720
|
);
|
|
@@ -718,6 +725,31 @@ export const useTheme = () => {
|
|
|
718
725
|
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
|
|
719
726
|
return ctx;
|
|
720
727
|
};
|
|
728
|
+
|
|
729
|
+
// Simple theme switcher component - drop into your app!
|
|
730
|
+
export function ThemeSwitcher() {
|
|
731
|
+
const { themeId, colorMode, setThemeId, setColorMode, themes } = useTheme();
|
|
732
|
+
|
|
733
|
+
return (
|
|
734
|
+
<div className="flex items-center gap-2 p-2 rounded-lg bg-card border">
|
|
735
|
+
<select
|
|
736
|
+
value={themeId}
|
|
737
|
+
onChange={(e) => setThemeId(e.target.value as ThemeId)}
|
|
738
|
+
className="bg-background text-foreground px-2 py-1 rounded border"
|
|
739
|
+
>
|
|
740
|
+
{themes.map(t => (
|
|
741
|
+
<option key={t.id} value={t.id}>{t.label}</option>
|
|
742
|
+
))}
|
|
743
|
+
</select>
|
|
744
|
+
<button
|
|
745
|
+
onClick={() => setColorMode(colorMode === "dark" ? "light" : "dark")}
|
|
746
|
+
className="px-2 py-1 rounded bg-primary text-primary-foreground"
|
|
747
|
+
>
|
|
748
|
+
{colorMode === "dark" ? "🌙" : "☀️"}
|
|
749
|
+
</button>
|
|
750
|
+
</div>
|
|
751
|
+
);
|
|
752
|
+
}
|
|
721
753
|
`;
|
|
722
754
|
};
|
|
723
755
|
|
|
@@ -727,41 +759,73 @@ const generateSetupInstructions = (projectRoot, themeId, mode, themeLabel) => {
|
|
|
727
759
|
Theme: ${themeLabel}
|
|
728
760
|
Mode: ${mode}
|
|
729
761
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
- components/theme/theme-context.tsx - React context provider
|
|
733
|
-
- app/gradient-forge.css - Theme CSS variables
|
|
734
|
-
|
|
735
|
-
## Next steps:
|
|
762
|
+
✅ All files created automatically!
|
|
763
|
+
✅ Theme applied!
|
|
736
764
|
|
|
737
|
-
|
|
738
|
-
import "./app/gradient-forge.css";
|
|
765
|
+
Your app now has beautiful gradient themes! 🎉
|
|
739
766
|
|
|
740
|
-
|
|
741
|
-
import { ThemeProvider } from "./components/theme/theme-context";
|
|
742
|
-
|
|
743
|
-
export default function RootLayout({ children }) {
|
|
744
|
-
return (
|
|
745
|
-
<html lang="en" className="${mode} ${themeId}" data-theme="${themeId}" data-color-mode="${mode}">
|
|
746
|
-
<body>
|
|
747
|
-
<ThemeProvider>{children}</ThemeProvider>
|
|
748
|
-
</body>
|
|
749
|
-
</html>
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
3. Use the theme in your components:
|
|
754
|
-
import { useTheme } from "./components/theme/theme-context";
|
|
755
|
-
|
|
756
|
-
function MyComponent() {
|
|
757
|
-
const { themeId, colorMode } = useTheme();
|
|
758
|
-
// ...
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
## For more themes, visit: https://gradient-forge.vercel.app
|
|
767
|
+
For more themes, visit: https://gradient-forge.vercel.app
|
|
762
768
|
`;
|
|
763
769
|
};
|
|
764
770
|
|
|
771
|
+
const autoInjectLayout = (projectRoot, themeId, mode) => {
|
|
772
|
+
const layoutPaths = [
|
|
773
|
+
path.join(projectRoot, "app", "layout.tsx"),
|
|
774
|
+
path.join(projectRoot, "src", "app", "layout.tsx"),
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
for (const layoutPath of layoutPaths) {
|
|
778
|
+
if (fs.existsSync(layoutPath)) {
|
|
779
|
+
let content = fs.readFileSync(layoutPath, "utf8");
|
|
780
|
+
|
|
781
|
+
// Check if already has ThemeProvider
|
|
782
|
+
if (content.includes("ThemeProvider")) {
|
|
783
|
+
return { success: true, path: layoutPath, message: "Already configured" };
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Add import - insert after the first import or at the start
|
|
787
|
+
const importStatement = `import { ThemeProvider } from "./components/theme/theme-context";`;
|
|
788
|
+
|
|
789
|
+
// Find position after the last import line
|
|
790
|
+
const lines = content.split('\n');
|
|
791
|
+
let insertIndex = 0;
|
|
792
|
+
for (let i = 0; i < lines.length; i++) {
|
|
793
|
+
if (lines[i].trim().startsWith('import ') && !lines[i].includes('from "') && !lines[i].includes("from '")) {
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (lines[i].trim().startsWith('import ')) {
|
|
797
|
+
insertIndex = i + 1;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Insert the import
|
|
802
|
+
if (insertIndex > 0 && lines[insertIndex - 1].trim() !== '') {
|
|
803
|
+
lines.splice(insertIndex, 0, importStatement);
|
|
804
|
+
} else {
|
|
805
|
+
lines.splice(insertIndex, 0, importStatement);
|
|
806
|
+
}
|
|
807
|
+
content = lines.join('\n');
|
|
808
|
+
|
|
809
|
+
// Wrap body content with ThemeProvider
|
|
810
|
+
content = content.replace(
|
|
811
|
+
/(<body[^>]*>)([\s\S]*)(<\/body>)/,
|
|
812
|
+
'$1<ThemeProvider>$2</ThemeProvider>$3'
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
// Add theme classes to html tag - use className for JSX
|
|
816
|
+
content = content.replace(
|
|
817
|
+
/<html[^>]*>/,
|
|
818
|
+
`<html lang="en" className="${mode} ${themeId}" data-theme="${themeId}" data-color-mode="${mode}">`
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
fs.writeFileSync(layoutPath, content);
|
|
822
|
+
return { success: true, path: layoutPath };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return { success: false, message: "No layout.tsx found" };
|
|
827
|
+
};
|
|
828
|
+
|
|
765
829
|
const init = async () => {
|
|
766
830
|
log("");
|
|
767
831
|
log("🎨 Gradient Forge CLI - Initialize Theme");
|
|
@@ -841,17 +905,23 @@ const init = async () => {
|
|
|
841
905
|
}
|
|
842
906
|
}
|
|
843
907
|
|
|
844
|
-
//
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
908
|
+
// Auto-inject into layout.tsx
|
|
909
|
+
const layoutResult = autoInjectLayout(projectRoot, themeId, mode);
|
|
910
|
+
if (layoutResult.success) {
|
|
911
|
+
logSuccess(`Updated layout: ${path.relative(projectRoot, layoutResult.path)}`);
|
|
912
|
+
} else {
|
|
913
|
+
logInfo("Note: Could not auto-detect layout.tsx");
|
|
914
|
+
}
|
|
915
|
+
|
|
850
916
|
log("");
|
|
851
917
|
log("======================================");
|
|
852
918
|
logSuccess("Theme setup complete! 🎉");
|
|
853
919
|
log("");
|
|
854
|
-
log(
|
|
920
|
+
log(`Theme: ${selectedTheme.label} (${mode})`);
|
|
921
|
+
log("");
|
|
922
|
+
log("Your app now has beautiful gradient themes!");
|
|
923
|
+
log("Run: npm run dev");
|
|
924
|
+
log("");
|
|
855
925
|
};
|
|
856
926
|
|
|
857
927
|
const exportCommand = async () => {
|
package/package.json
CHANGED