create-croissant 0.1.25 → 0.1.26

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.25",
6
+ "version": "0.1.26",
7
7
  "description": "Scaffold a new project using the Croissant Stack",
8
8
  "repository": {
9
9
  "type": "git",
@@ -16,6 +16,8 @@ import {
16
16
  SidebarRail,
17
17
  } from "@workspace/ui/components/sidebar"
18
18
  import { Avatar, AvatarFallback, AvatarImage } from "@workspace/ui/components/avatar"
19
+ import { ModeToggle } from "@workspace/ui/components/mode-toggle"
20
+
19
21
  import { authClient } from "@/lib/auth-client"
20
22
 
21
23
  // This is sample data.
@@ -113,8 +115,9 @@ export function AppSidebar({ items = authNavItems, ...props }: AppSidebarProps)
113
115
  return (
114
116
  <Sidebar {...props}>
115
117
  <SidebarHeader>
116
- <div className="flex items-center gap-2 px-4 py-2">
118
+ <div className="flex items-center justify-between px-4 py-2">
117
119
  <span className="font-bold">Croissant Stack</span>
120
+ <ModeToggle />
118
121
  </div>
119
122
  </SidebarHeader>
120
123
  <SidebarContent>
@@ -1,9 +1,11 @@
1
1
  import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"
2
2
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
3
3
  import { Toaster } from "@workspace/ui/components/sonner"
4
+ import { ThemeProvider } from "@workspace/ui/components/theme-provider"
4
5
 
5
6
  import appCss from "@workspace/ui/globals.css?url"
6
7
 
8
+
7
9
  const queryClient = new QueryClient()
8
10
 
9
11
  export const Route = createRootRoute({
@@ -37,10 +39,13 @@ function RootDocument({ children }: { children: React.ReactNode }) {
37
39
  <HeadContent />
38
40
  </head>
39
41
  <body>
42
+ <ThemeProvider defaultTheme="system" storageKey="theme">
43
+
40
44
  <QueryClientProvider client={queryClient}>
41
45
  {children}
42
46
  <Toaster />
43
47
  </QueryClientProvider>
48
+ </ThemeProvider>
44
49
  <Scripts />
45
50
  </body>
46
51
  </html>
@@ -69,7 +69,7 @@ function App() {
69
69
  ) : (
70
70
  <ul className="grid grid-cols-1 gap-2">
71
71
  {planets.map((planet: Planet) => (
72
- <li key={planet.id} className="rounded-md border p-3 bg-white shadow-sm">
72
+ <li key={planet.id} className="rounded-md border p-3 shadow-sm">
73
73
  <span className="font-bold">{planet.name}</span> - {planet.description}
74
74
  </li>
75
75
  ))}
@@ -0,0 +1,39 @@
1
+ import { Moon, Sun } from "lucide-react"
2
+
3
+ import { Button } from "@workspace/ui/components/button"
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuTrigger,
9
+ } from "@workspace/ui/components/dropdown-menu"
10
+ import { useTheme } from "@workspace/ui/components/theme-provider"
11
+
12
+ export function ModeToggle() {
13
+ const { setTheme } = useTheme()
14
+
15
+ return (
16
+ <DropdownMenu>
17
+ <DropdownMenuTrigger
18
+ render={
19
+ <Button variant="outline" size="icon">
20
+ <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
21
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
22
+ <span className="sr-only">Toggle theme</span>
23
+ </Button>
24
+ }
25
+ />
26
+ <DropdownMenuContent align="end">
27
+ <DropdownMenuItem onClick={() => setTheme("light")}>
28
+ Light
29
+ </DropdownMenuItem>
30
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
31
+ Dark
32
+ </DropdownMenuItem>
33
+ <DropdownMenuItem onClick={() => setTheme("system")}>
34
+ System
35
+ </DropdownMenuItem>
36
+ </DropdownMenuContent>
37
+ </DropdownMenu>
38
+ )
39
+ }
@@ -0,0 +1,93 @@
1
+ import { createContext, useContext, useEffect, useState } from "react"
2
+ import { ScriptOnce } from "@tanstack/react-router"
3
+
4
+ type Theme = "dark" | "light" | "system"
5
+
6
+ type ThemeProviderProps = {
7
+ children: React.ReactNode
8
+ defaultTheme?: Theme
9
+ storageKey?: string
10
+ }
11
+
12
+ type ThemeProviderState = {
13
+ theme: Theme
14
+ setTheme: (theme: Theme) => void
15
+ }
16
+
17
+ function getThemeScript(storageKey: string, defaultTheme: Theme) {
18
+ const key = JSON.stringify(storageKey)
19
+ const fallback = JSON.stringify(defaultTheme)
20
+
21
+ return `(function(){try{var t=localStorage.getItem(${key});if(t!=='light'&&t!=='dark'&&t!=='system'){t=${fallback}}var d=matchMedia('(prefers-color-scheme: dark)').matches;var r=t==='system'?(d?'dark':'light'):t;var e=document.documentElement;e.classList.add(r);e.style.colorScheme=r}catch(e){}})();`
22
+ }
23
+
24
+ const ThemeProviderContext = createContext<ThemeProviderState | undefined>(
25
+ undefined
26
+ )
27
+
28
+ function applyTheme(theme: Theme) {
29
+ const root = document.documentElement
30
+ root.classList.remove("light", "dark")
31
+
32
+ const resolved =
33
+ theme === "system"
34
+ ? window.matchMedia("(prefers-color-scheme: dark)").matches
35
+ ? "dark"
36
+ : "light"
37
+ : theme
38
+
39
+ root.classList.add(resolved)
40
+ root.style.colorScheme = resolved
41
+ }
42
+
43
+ export function ThemeProvider({
44
+ children,
45
+ defaultTheme = "system",
46
+ storageKey = "theme",
47
+ }: ThemeProviderProps) {
48
+ const [theme, setThemeState] = useState<Theme>(defaultTheme)
49
+ const [mounted, setMounted] = useState(false)
50
+
51
+ useEffect(() => {
52
+ const stored = localStorage.getItem(storageKey)
53
+ setThemeState(
54
+ stored === "light" || stored === "dark" || stored === "system"
55
+ ? stored
56
+ : defaultTheme
57
+ )
58
+ setMounted(true)
59
+ }, [defaultTheme, storageKey])
60
+
61
+ useEffect(() => {
62
+ if (!mounted) return
63
+ applyTheme(theme)
64
+ }, [theme, mounted])
65
+
66
+ useEffect(() => {
67
+ if (!mounted || theme !== "system") return
68
+
69
+ const media = window.matchMedia("(prefers-color-scheme: dark)")
70
+ const onChange = () => applyTheme("system")
71
+ media.addEventListener("change", onChange)
72
+ return () => media.removeEventListener("change", onChange)
73
+ }, [theme, mounted])
74
+
75
+ const setTheme = (next: Theme) => {
76
+ localStorage.setItem(storageKey, next)
77
+ setThemeState(next)
78
+ }
79
+
80
+ return (
81
+ <ThemeProviderContext value={{ theme, setTheme }}>
82
+ <ScriptOnce>{getThemeScript(storageKey, defaultTheme)}</ScriptOnce>
83
+ {children}
84
+ </ThemeProviderContext>
85
+ )
86
+ }
87
+
88
+ export function useTheme() {
89
+ const context = useContext(ThemeProviderContext)
90
+ if (context === undefined)
91
+ throw new Error("useTheme must be used within a ThemeProvider")
92
+ return context
93
+ }