diff --git a/frontend/package.json b/frontend/package.json index b6a0a45..5c2f769 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "lucide-react": "^0.453.0", "next": "15.0.3", "next-intl": "^4.8.3", + "next-themes": "^0.4.6", "react": "^18", "react-chartjs-2": "^5.3.1", "react-dom": "^18", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 3d03b05..147628b 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { ThemeProvider } from "next-themes"; export const metadata: Metadata = { title: "Stellar Guilds", @@ -11,8 +12,18 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - {children} + + + {/* next-themes persists preference to localStorage under key "theme" by default */} + + {children} + + ); } diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index e00eac6..9e5146c 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -53,6 +53,5 @@ export { useSecurity } from '@/hooks/useSecurity' // Stores export { useSidebarStore } from '@/store/sidebarStore' -export { useThemeStore } from '@/store/themeStore' export { useWalletStore } from '@/store/walletStore' export { useSecurityStore } from '@/store/securityStore' \ No newline at end of file diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index 4e1ea81..9b32a55 100644 --- a/frontend/src/components/layout/Header.tsx +++ b/frontend/src/components/layout/Header.tsx @@ -7,6 +7,7 @@ import { useSidebarStore } from '@/store/sidebarStore' import { cn } from '@/lib/utils' import { WalletConnectButton } from '@/components/WalletConnector/WalletConnectButton' import { WalletModal } from '@/components/WalletConnector/WalletModal' +import { ThemeToggle } from '@/components/ui/ThemeToggle' interface HeaderProps { className?: string @@ -44,6 +45,7 @@ const Header = ({ className }: HeaderProps) => {
+ diff --git a/frontend/src/components/ui/ThemeToggle.tsx b/frontend/src/components/ui/ThemeToggle.tsx new file mode 100644 index 0000000..014d018 --- /dev/null +++ b/frontend/src/components/ui/ThemeToggle.tsx @@ -0,0 +1,38 @@ +'use client' + +import { useTheme } from 'next-themes' +import { useEffect, useState } from 'react' +import { Sun, Moon, Monitor } from 'lucide-react' + +type Theme = 'light' | 'dark' | 'system' + +const THEME_CYCLE: Theme[] = ['light', 'dark', 'system'] + +const icons: Record = { + light: , + dark: , + system: , +} + +export function ThemeToggle() { + const { theme, setTheme } = useTheme() + const [mounted, setMounted] = useState(false) + + useEffect(() => setMounted(true), []) + + // Render neutral placeholder until hydrated to avoid mismatch + if (!mounted) return
+ + const current = (theme as Theme) ?? 'system' + const next = THEME_CYCLE[(THEME_CYCLE.indexOf(current) + 1) % THEME_CYCLE.length] + + return ( + + ) +} diff --git a/frontend/src/store/themeStore.ts b/frontend/src/store/themeStore.ts deleted file mode 100644 index 0d9433f..0000000 --- a/frontend/src/store/themeStore.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { create } from 'zustand' - -type Theme = 'light' | 'dark' - -interface ThemeState { - theme: Theme - toggleTheme: () => void - setTheme: (theme: Theme) => void -} - -export const useThemeStore = create((set) => ({ - theme: 'dark', - toggleTheme: () => set((state) => ({ - theme: state.theme === 'light' ? 'dark' : 'light' - })), - setTheme: (theme) => set({ theme }), -})) \ No newline at end of file diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index 33c9f03..80f9fe0 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -2,6 +2,7 @@ import type { Config } from "tailwindcss"; import typography from "@tailwindcss/typography"; const config: Config = { + darkMode: 'class', content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}",