dark/light toggle
This commit is contained in:
parent
472396055f
commit
33d0923fc7
|
|
@ -0,0 +1,22 @@
|
|||
# CLAUDE.md - Guidance for AI Assistance
|
||||
|
||||
## Build & Workflow Commands
|
||||
- Development: `npm run dev`
|
||||
- Build: `npm run build`
|
||||
- Start production: `npm run start`
|
||||
- Lint: `npm run lint`
|
||||
|
||||
## Code Style Guidelines
|
||||
- **Components**: Named function exports with "use client" directive where needed
|
||||
- **TypeScript**: Strict mode, explicit types for props and functions
|
||||
- **Naming**: PascalCase for components, camelCase for functions/variables
|
||||
- **Imports**: React → Next.js → external → internal, use path aliases (@/components, @/lib)
|
||||
- **Styling**: Tailwind CSS with utility classes, use `cn()` for conditional classes
|
||||
- **UI Components**: Reusable components in components/ui with consistent prop interfaces
|
||||
- **Formatting**: 2-space indentation, consistent spacing, trailing commas
|
||||
- **Error Handling**: Use try/catch for async operations, provide meaningful error messages
|
||||
|
||||
## Project Structure
|
||||
- `/src/app`: Next.js App Router pages and layouts
|
||||
- `/src/components`: Reusable React components, UI components in `/ui`
|
||||
- `/src/lib`: Utility functions and helpers
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import { ThemeProvider } from '@/components/ui/theme-provider'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
|
|
@ -15,9 +16,16 @@ export default function RootLayout({
|
|||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
{children}
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { ThemeToggle } from "@/components/ui/theme-toggle"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
function Navbar() {
|
||||
|
|
@ -33,6 +34,7 @@ function Navbar() {
|
|||
</nav>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<ThemeToggle />
|
||||
<Button variant="outline" className="hidden md:flex">
|
||||
Connect Wallet
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { getSystemTheme, isBrowser, type Theme } from "@/lib/utils"
|
||||
|
||||
type ThemeProviderProps = {
|
||||
children: React.ReactNode
|
||||
defaultTheme?: Theme
|
||||
storageKey?: string
|
||||
attribute?: string
|
||||
enableSystem?: boolean
|
||||
disableTransitionOnChange?: boolean
|
||||
}
|
||||
|
||||
type ThemeProviderState = {
|
||||
theme: Theme
|
||||
setTheme: (theme: Theme) => void
|
||||
}
|
||||
|
||||
const initialState: ThemeProviderState = {
|
||||
theme: "system",
|
||||
setTheme: () => null,
|
||||
}
|
||||
|
||||
export const ThemeProviderContext = React.createContext<ThemeProviderState>(initialState)
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
defaultTheme = "system",
|
||||
storageKey = "theme",
|
||||
...props
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setTheme] = React.useState<Theme>(
|
||||
() => {
|
||||
if (!isBrowser) return defaultTheme
|
||||
return (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
||||
}
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
const root = window.document.documentElement
|
||||
|
||||
// Remove old class
|
||||
root.classList.remove("light", "dark")
|
||||
|
||||
// Add new class based on theme
|
||||
if (theme === "system") {
|
||||
const systemTheme = getSystemTheme()
|
||||
root.classList.add(systemTheme)
|
||||
} else {
|
||||
root.classList.add(theme)
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
// Monitor for system theme change
|
||||
React.useEffect(() => {
|
||||
if (theme !== "system") return
|
||||
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
|
||||
const handleChange = () => {
|
||||
const root = window.document.documentElement
|
||||
const systemTheme = getSystemTheme()
|
||||
|
||||
root.classList.remove("light", "dark")
|
||||
root.classList.add(systemTheme)
|
||||
}
|
||||
|
||||
mediaQuery.addEventListener("change", handleChange)
|
||||
return () => mediaQuery.removeEventListener("change", handleChange)
|
||||
}, [theme])
|
||||
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
theme,
|
||||
setTheme: (newTheme: Theme) => {
|
||||
localStorage.setItem(storageKey, newTheme)
|
||||
setTheme(newTheme)
|
||||
},
|
||||
}),
|
||||
[theme, storageKey]
|
||||
)
|
||||
|
||||
return (
|
||||
<ThemeProviderContext.Provider {...props} value={value}>
|
||||
{children}
|
||||
</ThemeProviderContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Hook to use the theme context
|
||||
export const useTheme = () => {
|
||||
const context = React.useContext(ThemeProviderContext)
|
||||
if (context === undefined)
|
||||
throw new Error("useTheme must be used within a ThemeProvider")
|
||||
return context
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
|
||||
import { useTheme } from "@/components/ui/theme-provider"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="rounded-full"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<Sun className="h-5 w-5 rotate-0 scale-100 transition-transform dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-transform dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -3,4 +3,15 @@ import { twMerge } from "tailwind-merge"
|
|||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're running in the browser
|
||||
export const isBrowser = typeof window !== 'undefined'
|
||||
|
||||
// Theme utilities
|
||||
export type Theme = 'dark' | 'light' | 'system'
|
||||
|
||||
export function getSystemTheme(): Theme {
|
||||
if (!isBrowser) return 'light'
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
|
|
|
|||
Loading…
Reference in New Issue