tangled
alpha
login
or
join now
roost.moe
/
recipes.blue
2
fork
atom
The recipes.blue monorepo
recipes.blue
recipes
appview
atproto
2
fork
atom
overview
issues
1
pulls
pipelines
feat: dark mode(!!!)
Hayden Young
1 year ago
1496e0dc
952c5518
+143
-26
6 changed files
expand all
collapse all
unified
split
apps
web
src
components
app-sidebar.tsx
mode-toggle.tsx
sidebar-title.tsx
theme-provider.tsx
index.css
main.tsx
+3
-1
apps/web/src/components/app-sidebar.tsx
···
18
SidebarRail,
19
} from "@/components/ui/sidebar"
20
import { NavUserOpts } from "./nav-user-opts"
0
21
22
const data = {
23
navMain: [
···
61
<Sidebar collapsible="icon" {...props}>
62
<SidebarHeader>
63
<SidebarMenu>
64
-
<SidebarMenuItem>
65
<SidebarMenuButton size="lg" asChild>
66
<a href="#">
67
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
···
72
</div>
73
</a>
74
</SidebarMenuButton>
0
75
</SidebarMenuItem>
76
</SidebarMenu>
77
</SidebarHeader>
···
18
SidebarRail,
19
} from "@/components/ui/sidebar"
20
import { NavUserOpts } from "./nav-user-opts"
21
+
import { ModeToggle } from "./mode-toggle"
22
23
const data = {
24
navMain: [
···
62
<Sidebar collapsible="icon" {...props}>
63
<SidebarHeader>
64
<SidebarMenu>
65
+
<SidebarMenuItem className="flex items-center justify-between gap-x-2">
66
<SidebarMenuButton size="lg" asChild>
67
<a href="#">
68
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
···
73
</div>
74
</a>
75
</SidebarMenuButton>
76
+
<ModeToggle />
77
</SidebarMenuItem>
78
</SidebarMenu>
79
</SidebarHeader>
+37
apps/web/src/components/mode-toggle.tsx
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import { Moon, Sun } from "lucide-react"
2
+
3
+
import { Button } from "@/components/ui/button"
4
+
import {
5
+
DropdownMenu,
6
+
DropdownMenuContent,
7
+
DropdownMenuItem,
8
+
DropdownMenuTrigger,
9
+
} from "@/components/ui/dropdown-menu"
10
+
import { useTheme } from "@/components/theme-provider"
11
+
12
+
export function ModeToggle() {
13
+
const { setTheme } = useTheme()
14
+
15
+
return (
16
+
<DropdownMenu>
17
+
<DropdownMenuTrigger asChild>
18
+
<Button variant="outline" size="icon" className="size-8 aspect-square flex items-center justify-center rounded-lg">
19
+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
20
+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
21
+
<span className="sr-only">Toggle theme</span>
22
+
</Button>
23
+
</DropdownMenuTrigger>
24
+
<DropdownMenuContent align="end">
25
+
<DropdownMenuItem onClick={() => setTheme("light")}>
26
+
Light
27
+
</DropdownMenuItem>
28
+
<DropdownMenuItem onClick={() => setTheme("dark")}>
29
+
Dark
30
+
</DropdownMenuItem>
31
+
<DropdownMenuItem onClick={() => setTheme("system")}>
32
+
System
33
+
</DropdownMenuItem>
34
+
</DropdownMenuContent>
35
+
</DropdownMenu>
36
+
)
37
+
}
+7
-5
apps/web/src/components/sidebar-title.tsx
···
4
SidebarMenu,
5
SidebarMenuItem,
6
} from "@/components/ui/sidebar"
0
7
8
export function SidebarTitle() {
9
return (
10
<SidebarMenu>
11
<SidebarMenuItem>
12
-
<div className="flex items-center gap-2 p-2">
13
-
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
14
-
<CookingPot className="size-4" />
15
-
</div>
16
-
<span className="font-semibold text-sm flex-1 text-left leading-tight">Recipes</span>
17
</div>
0
0
0
0
18
</SidebarMenuItem>
19
</SidebarMenu>
20
)
···
4
SidebarMenu,
5
SidebarMenuItem,
6
} from "@/components/ui/sidebar"
7
+
import { ModeToggle } from "./mode-toggle"
8
9
export function SidebarTitle() {
10
return (
11
<SidebarMenu>
12
<SidebarMenuItem>
13
+
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
14
+
<CookingPot className="size-4" />
0
0
0
15
</div>
16
+
<div className="flex flex-col gap-0.5 leading-none">
17
+
<span className="font-semibold">Recipes</span>
18
+
</div>
19
+
<ModeToggle />
20
</SidebarMenuItem>
21
</SidebarMenu>
22
)
+73
apps/web/src/components/theme-provider.tsx
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import { createContext, useContext, useEffect, useState } from "react"
2
+
3
+
type Theme = "dark" | "light" | "system"
4
+
5
+
type ThemeProviderProps = {
6
+
children: React.ReactNode
7
+
defaultTheme?: Theme
8
+
storageKey?: string
9
+
}
10
+
11
+
type ThemeProviderState = {
12
+
theme: Theme
13
+
setTheme: (theme: Theme) => void
14
+
}
15
+
16
+
const initialState: ThemeProviderState = {
17
+
theme: "system",
18
+
setTheme: () => null,
19
+
}
20
+
21
+
const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
22
+
23
+
export function ThemeProvider({
24
+
children,
25
+
defaultTheme = "system",
26
+
storageKey = "vite-ui-theme",
27
+
...props
28
+
}: ThemeProviderProps) {
29
+
const [theme, setTheme] = useState<Theme>(
30
+
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
31
+
)
32
+
33
+
useEffect(() => {
34
+
const root = window.document.documentElement
35
+
36
+
root.classList.remove("light", "dark")
37
+
38
+
if (theme === "system") {
39
+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
40
+
.matches
41
+
? "dark"
42
+
: "light"
43
+
44
+
root.classList.add(systemTheme)
45
+
return
46
+
}
47
+
48
+
root.classList.add(theme)
49
+
}, [theme])
50
+
51
+
const value = {
52
+
theme,
53
+
setTheme: (theme: Theme) => {
54
+
localStorage.setItem(storageKey, theme)
55
+
setTheme(theme)
56
+
},
57
+
}
58
+
59
+
return (
60
+
<ThemeProviderContext.Provider {...props} value={value}>
61
+
{children}
62
+
</ThemeProviderContext.Provider>
63
+
)
64
+
}
65
+
66
+
export const useTheme = () => {
67
+
const context = useContext(ThemeProviderContext)
68
+
69
+
if (context === undefined)
70
+
throw new Error("useTheme must be used within a ThemeProvider")
71
+
72
+
return context
73
+
}
+14
-14
apps/web/src/index.css
···
45
--card-foreground: 0 0% 98%;
46
--popover: 0 0% 3.9%;
47
--popover-foreground: 0 0% 98%;
48
-
--primary: 0 72.2% 50.6%;
49
-
--primary-foreground: 0 85.7% 97.3%;
50
-
--secondary: 0 0% 14.9%;
51
-
--secondary-foreground: 0 0% 98%;
52
-
--muted: 0 0% 14.9%;
53
-
--muted-foreground: 0 0% 63.9%;
54
-
--accent: 0 0% 14.9%;
55
-
--accent-foreground: 0 0% 98%;
56
--destructive: 0 62.8% 30.6%;
57
-
--destructive-foreground: 0 0% 98%;
58
-
--border: 0 0% 14.9%;
59
-
--input: 0 0% 14.9%;
60
-
--ring: 0 72.2% 50.6%;
61
--chart-1: 220 70% 50%;
62
--chart-2: 160 60% 45%;
63
--chart-3: 30 80% 55%;
···
65
--chart-5: 340 75% 55%;
66
--sidebar-background: 240 5.9% 10%;
67
--sidebar-foreground: 240 4.8% 95.9%;
68
-
--sidebar-primary: 224.3 76.3% 48%;
69
--sidebar-primary-foreground: 0 0% 100%;
70
-
--sidebar-accent: 240 3.7% 15.9%;
71
--sidebar-accent-foreground: 240 4.8% 95.9%;
72
--sidebar-border: 240 3.7% 15.9%;
73
--sidebar-ring: 217.2 91.2% 59.8%;
···
45
--card-foreground: 0 0% 98%;
46
--popover: 0 0% 3.9%;
47
--popover-foreground: 0 0% 98%;
48
+
--primary: 217.2 91.2% 59.8%;
49
+
--primary-foreground: 222.2 47.4% 11.2%;
50
+
--secondary: 217.2 32.6% 17.5%;
51
+
--secondary-foreground: 210 40% 98%;
52
+
--muted: 217.2 32.6% 17.5%;
53
+
--muted-foreground: 215 20.2% 65.1%;
54
+
--accent: 217.2 32.6% 17.5%;
55
+
--accent-foreground: 210 40% 98%;
56
--destructive: 0 62.8% 30.6%;
57
+
--destructive-foreground: 210 40% 98%;
58
+
--border: 217.2 32.6% 17.5%;
59
+
--input: 217.2 32.6% 17.5%;
60
+
--ring: 224.3 76.3% 48%;
61
--chart-1: 220 70% 50%;
62
--chart-2: 160 60% 45%;
63
--chart-3: 30 80% 55%;
···
65
--chart-5: 340 75% 55%;
66
--sidebar-background: 240 5.9% 10%;
67
--sidebar-foreground: 240 4.8% 95.9%;
68
+
--sidebar-primary: 217.2 91.2% 59.8%;
69
--sidebar-primary-foreground: 0 0% 100%;
70
+
--sidebar-accent: 217.2 32.6% 17.5%;
71
--sidebar-accent-foreground: 240 4.8% 95.9%;
72
--sidebar-border: 240 3.7% 15.9%;
73
--sidebar-ring: 217.2 91.2% 59.8%;
+9
-6
apps/web/src/main.tsx
···
7
import { configureOAuth } from '@atcute/oauth-browser-client';
8
import './index.css'
9
import { AuthProvider, useAuth } from './state/auth';
0
10
11
const router = createRouter({
12
routeTree,
···
45
46
createRoot(document.getElementById('root')!).render(
47
<StrictMode>
48
-
<AuthProvider>
49
-
<QueryClientProvider client={queryClient}>
50
-
<InnerApp />
51
-
<ReactQueryDevtools initialIsOpen={false} />
52
-
</QueryClientProvider>
53
-
</AuthProvider>
0
0
54
</StrictMode>,
55
)
···
7
import { configureOAuth } from '@atcute/oauth-browser-client';
8
import './index.css'
9
import { AuthProvider, useAuth } from './state/auth';
10
+
import { ThemeProvider } from './components/theme-provider';
11
12
const router = createRouter({
13
routeTree,
···
46
47
createRoot(document.getElementById('root')!).render(
48
<StrictMode>
49
+
<ThemeProvider defaultTheme="dark" storageKey="recipes-theme">
50
+
<AuthProvider>
51
+
<QueryClientProvider client={queryClient}>
52
+
<InnerApp />
53
+
<ReactQueryDevtools initialIsOpen={false} />
54
+
</QueryClientProvider>
55
+
</AuthProvider>
56
+
</ThemeProvider>
57
</StrictMode>,
58
)