Skip to content

Commit 4464b49

Browse files
committed
Update app-generator.ts
1 parent c3b14a1 commit 4464b49

File tree

1 file changed

+128
-1
lines changed

1 file changed

+128
-1
lines changed

packages/cli/src/utils/app-generator.ts

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,15 +467,94 @@ export function createTempAppWithRouting(tmpDir: string, routes: RouteInfo[], ap
467467
routeComponents.push(` <Route path="${route.path}" element={<SchemaRenderer schema={${schemaVarName}} />} />`);
468468
});
469469

470+
// Create theme-provider.tsx
471+
const themeProviderTsx = `import { createContext, useContext, useEffect, useState } from "react"
472+
473+
type Theme = "dark" | "light" | "system"
474+
475+
type ThemeProviderProps = {
476+
children: React.ReactNode
477+
defaultTheme?: Theme
478+
storageKey?: string
479+
}
480+
481+
type ThemeProviderState = {
482+
theme: Theme
483+
setTheme: (theme: Theme) => void
484+
}
485+
486+
const initialState: ThemeProviderState = {
487+
theme: "system",
488+
setTheme: () => null,
489+
}
490+
491+
const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
492+
493+
export function ThemeProvider({
494+
children,
495+
defaultTheme = "system",
496+
storageKey = "vite-ui-theme",
497+
...props
498+
}: ThemeProviderProps) {
499+
const [theme, setTheme] = useState<Theme>(
500+
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
501+
)
502+
503+
useEffect(() => {
504+
const root = window.document.documentElement
505+
506+
root.classList.remove("light", "dark")
507+
508+
if (theme === "system") {
509+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
510+
.matches
511+
? "dark"
512+
: "light"
513+
514+
root.classList.add(systemTheme)
515+
return
516+
}
517+
518+
root.classList.add(theme)
519+
}, [theme])
520+
521+
const value = {
522+
theme,
523+
setTheme: (theme: Theme) => {
524+
localStorage.setItem(storageKey, theme)
525+
setTheme(theme)
526+
},
527+
}
528+
529+
return (
530+
<ThemeProviderContext.Provider {...props} value={value}>
531+
{children}
532+
</ThemeProviderContext.Provider>
533+
)
534+
}
535+
536+
export const useTheme = () => {
537+
const context = useContext(ThemeProviderContext)
538+
539+
if (context === undefined)
540+
throw new Error("useTheme must be used within a ThemeProvider")
541+
542+
return context
543+
}`;
544+
writeFileSync(join(srcDir, 'theme-provider.tsx'), themeProviderTsx);
545+
470546
// Create main.tsx
471547
const mainTsx = `import React from 'react';
472548
import ReactDOM from 'react-dom/client';
473549
import App from './App';
474550
import './index.css';
551+
import { ThemeProvider } from "./theme-provider"
475552
476553
ReactDOM.createRoot(document.getElementById('root')!).render(
477554
<React.StrictMode>
478-
<App />
555+
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
556+
<App />
557+
</ThemeProvider>
479558
</React.StrictMode>
480559
);`;
481560

@@ -495,8 +574,15 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
495574
const layoutCode = `
496575
import { Link, useLocation } from 'react-router-dom';
497576
import * as LucideIcons from 'lucide-react';
577+
import { Moon, Sun } from "lucide-react"
578+
import { useTheme } from "./theme-provider"
498579
import {
499580
cn,
581+
Button,
582+
DropdownMenu,
583+
DropdownMenuContent,
584+
DropdownMenuItem,
585+
DropdownMenuTrigger,
500586
SidebarProvider,
501587
Sidebar,
502588
SidebarContent,
@@ -527,6 +613,42 @@ const DynamicIcon = ({ name, className }) => {
527613
return <Icon className={className} />;
528614
};
529615
616+
export function ModeToggle() {
617+
const { setTheme } = useTheme()
618+
619+
return (
620+
<DropdownMenu>
621+
<DropdownMenuTrigger asChild>
622+
<SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
623+
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
624+
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
625+
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
626+
</div>
627+
<div className="grid flex-1 text-left text-sm leading-tight">
628+
<span className="truncate font-semibold">Switch Theme</span>
629+
<span className="truncate text-xs">Light / Dark</span>
630+
</div>
631+
<LucideIcons.ChevronsUpDown className="ml-auto size-4" />
632+
</SidebarMenuButton>
633+
</DropdownMenuTrigger>
634+
<DropdownMenuContent className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" side="bottom" align="end" sideOffset={4}>
635+
<DropdownMenuItem onClick={() => setTheme("light")}>
636+
<LucideIcons.Sun className="mr-2 size-4" />
637+
Light
638+
</DropdownMenuItem>
639+
<DropdownMenuItem onClick={() => setTheme("dark")}>
640+
<LucideIcons.Moon className="mr-2 size-4" />
641+
Dark
642+
</DropdownMenuItem>
643+
<DropdownMenuItem onClick={() => setTheme("system")}>
644+
<LucideIcons.Monitor className="mr-2 size-4" />
645+
System
646+
</DropdownMenuItem>
647+
</DropdownMenuContent>
648+
</DropdownMenu>
649+
)
650+
}
651+
530652
const AppLayout = ({ app, children }) => {
531653
const location = useLocation();
532654
const menu = app.menu || [];
@@ -600,6 +722,11 @@ const AppLayout = ({ app, children }) => {
600722
</SidebarGroup>
601723
</SidebarContent>
602724
<SidebarFooter>
725+
<SidebarMenu>
726+
<SidebarMenuItem>
727+
<ModeToggle />
728+
</SidebarMenuItem>
729+
</SidebarMenu>
603730
</SidebarFooter>
604731
<SidebarRail />
605732
</Sidebar>

0 commit comments

Comments
 (0)