Add dark mode toggle via next-themes
Uses next-themes with system default. Toggle button in sidebar switches between light/dark and persists to localStorage. CSS variables for dark mode were already defined in globals.css. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
"next": "16.2.4",
|
"next": "16.2.4",
|
||||||
"next-auth": "^5.0.0-beta.31",
|
"next-auth": "^5.0.0-beta.31",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"papaparse": "^5.5.3",
|
"papaparse": "^5.5.3",
|
||||||
"pg": "^8.20.0",
|
"pg": "^8.20.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import { ThemeProvider } from "@/components/layout/ThemeProvider";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -27,7 +28,9 @@ export default function RootLayout({
|
|||||||
lang="en"
|
lang="en"
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||||
>
|
>
|
||||||
<body className="min-h-full flex flex-col">{children}</body>
|
<body className="min-h-full flex flex-col">
|
||||||
|
<ThemeProvider>{children}</ThemeProvider>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { signOut } from 'next-auth/react'
|
import { signOut } from 'next-auth/react'
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import {
|
import {
|
||||||
LayoutDashboard, CreditCard, ArrowLeftRight,
|
LayoutDashboard, CreditCard, ArrowLeftRight,
|
||||||
Upload, PiggyBank, TrendingUp, LogOut,
|
Upload, PiggyBank, TrendingUp, LogOut, Sun, Moon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ const navItems = [
|
|||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
const { resolvedTheme, setTheme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="flex w-56 shrink-0 flex-col border-r bg-card">
|
<aside className="flex w-56 shrink-0 flex-col border-r bg-card">
|
||||||
@@ -46,7 +48,15 @@ export function Sidebar() {
|
|||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="p-2">
|
<div className="p-2 space-y-1">
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
|
||||||
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||||
|
>
|
||||||
|
{resolvedTheme === 'dark'
|
||||||
|
? <><Sun className="h-4 w-4 shrink-0" />Light mode</>
|
||||||
|
: <><Moon className="h-4 w-4 shrink-0" />Dark mode</>}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => signOut({ callbackUrl: '/login' })}
|
onClick={() => signOut({ callbackUrl: '/login' })}
|
||||||
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||||
|
|||||||
11
src/components/layout/ThemeProvider.tsx
Normal file
11
src/components/layout/ThemeProvider.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||||
|
|
||||||
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<NextThemesProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||||
|
{children}
|
||||||
|
</NextThemesProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user