Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 370f93e1f7 |
@@ -1,63 +1,4 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { ThemeContextType } from "@/types/theme.interface";
|
||||
import { createContext } from "react";
|
||||
|
||||
type ThemeMode = 'light' | 'dark';
|
||||
type AccentTheme = 'blue' | 'purple' | 'green';
|
||||
|
||||
interface ThemeContextType {
|
||||
mode: ThemeMode;
|
||||
accent: AccentTheme;
|
||||
setMode: (mode: ThemeMode) => void;
|
||||
setAccent: (accent: AccentTheme) => void;
|
||||
toggleMode: () => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [mode, setModeState] = useState<ThemeMode>('dark');
|
||||
const [accent, setAccentState] = useState<AccentTheme>('blue');
|
||||
|
||||
// Initialize theme from localStorage
|
||||
useEffect(() => {
|
||||
const savedMode = localStorage.getItem('techzaa-mode') as ThemeMode;
|
||||
const savedAccent = localStorage.getItem('techzaa-accent') as AccentTheme;
|
||||
|
||||
if (savedMode) setModeState(savedMode);
|
||||
if (savedAccent) setAccentState(savedAccent);
|
||||
}, []);
|
||||
|
||||
// Apply theme classes to document
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Apply dark/light mode
|
||||
root.classList.remove('light', 'dark');
|
||||
root.classList.add(mode);
|
||||
|
||||
// Apply accent theme
|
||||
root.classList.remove('theme-blue', 'theme-purple', 'theme-green');
|
||||
root.classList.add(`theme-${accent}`);
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('techzaa-mode', mode);
|
||||
localStorage.setItem('techzaa-accent', accent);
|
||||
}, [mode, accent]);
|
||||
|
||||
const setMode = (newMode: ThemeMode) => setModeState(newMode);
|
||||
const setAccent = (newAccent: AccentTheme) => setAccentState(newAccent);
|
||||
const toggleMode = () => setModeState(prev => prev === 'dark' ? 'light' : 'dark');
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ mode, accent, setMode, setAccent, toggleMode }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const context = useContext(ThemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
export const ThemeContext = createContext<ThemeContextType | null>(null);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ThemeContext } from "@/contexts/ThemeContext";
|
||||
import { ThemeContextType } from "@/types/theme.interface";
|
||||
import { useContext } from "react";
|
||||
|
||||
export function useTheme(): ThemeContextType {
|
||||
const context = useContext(ThemeContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useTheme must be used within a ThemeProvider"
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { ThemeContext } from "@/contexts/ThemeContext";
|
||||
import { ThemeMode, AccentTheme } from "@/types/theme.interface";
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
mode: "techzaa-mode",
|
||||
accent: "techzaa-accent",
|
||||
} as const;
|
||||
|
||||
const isValidMode = (value: unknown): value is ThemeMode =>
|
||||
value === "light" || value === "dark";
|
||||
|
||||
const isValidAccent = (value: unknown): value is AccentTheme =>
|
||||
value === "blue" || value === "purple" || value === "green";
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [mode, setMode] = useState<ThemeMode>(() => {
|
||||
if (typeof window === "undefined") return "dark";
|
||||
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.mode);
|
||||
|
||||
return isValidMode(stored) ? stored : "dark";
|
||||
});
|
||||
|
||||
const [accent, setAccent] = useState<AccentTheme>(() => {
|
||||
if (typeof window === "undefined") return "blue";
|
||||
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.accent);
|
||||
|
||||
return isValidAccent(stored) ? stored : "blue";
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === "undefined") return;
|
||||
|
||||
const root = document.documentElement;
|
||||
|
||||
root.classList.remove("light", "dark");
|
||||
root.classList.add(mode);
|
||||
|
||||
root.classList.remove("theme-blue", "theme-purple", "theme-green");
|
||||
root.classList.add(`theme-${accent}`);
|
||||
|
||||
localStorage.setItem(STORAGE_KEYS.mode, mode);
|
||||
localStorage.setItem(STORAGE_KEYS.accent, accent);
|
||||
}, [mode, accent]);
|
||||
|
||||
const toggleMode = useCallback(() => {
|
||||
setMode((prev) => (prev === "dark" ? "light" : "dark"));
|
||||
}, []);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
mode,
|
||||
accent,
|
||||
setMode,
|
||||
setAccent,
|
||||
toggleMode,
|
||||
}),
|
||||
[mode, accent, toggleMode],
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export type ThemeMode = "light" | "dark";
|
||||
export type AccentTheme = "blue" | "purple" | "green";
|
||||
|
||||
export interface ThemeContextType {
|
||||
mode: ThemeMode;
|
||||
accent: AccentTheme;
|
||||
setMode: React.Dispatch<React.SetStateAction<ThemeMode>>;
|
||||
setAccent: React.Dispatch<React.SetStateAction<AccentTheme>>;
|
||||
toggleMode: () => void;
|
||||
}
|
||||
Reference in New Issue
Block a user