Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion apps/web/src/ui/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ interface ThemeContextValue {
theme: Theme
setTheme: (theme: Theme) => void
resolvedTheme: "dark" | "light"
highContrast: boolean
setHighContrast: (enabled: boolean) => void
}

const ThemeContext = createContext<ThemeContextValue | undefined>(undefined)

const STORAGE_KEY = "so4-theme"
const HIGH_CONTRAST_KEY = "so4-high-contrast"

function getSystemTheme(): "dark" | "light" {
if (typeof window === "undefined") return "light"
Expand All @@ -28,11 +31,24 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
return (localStorage.getItem(STORAGE_KEY) as Theme | null) ?? "system"
})

const [highContrast, setHighContrastState] = useState<boolean>(() => {
if (typeof window === "undefined") return false
return localStorage.getItem(HIGH_CONTRAST_KEY) === "true"
})

const setTheme = (next: Theme) => {
if (typeof window !== "undefined") localStorage.setItem(STORAGE_KEY, next)
setThemeState(next)
}

const setHighContrast = (enabled: boolean) => {
if (typeof window !== "undefined") {
localStorage.setItem(HIGH_CONTRAST_KEY, String(enabled))
}
setHighContrastState(enabled)
}

// Apply theme class (dark/light)
useEffect(() => {
const resolved = resolve(theme)
const root = document.documentElement
Expand All @@ -41,6 +57,16 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
root.classList.add(resolved)
}, [theme])

// Apply high-contrast class
useEffect(() => {
const root = document.documentElement
if (highContrast) {
root.classList.add("high-contrast")
} else {
root.classList.remove("high-contrast")
}
}, [highContrast])

useEffect(() => {
if (theme !== "system") return
const mql = window.matchMedia("(prefers-color-scheme: dark)")
Expand All @@ -54,7 +80,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
}, [theme])

return (
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme: resolve(theme) }}>
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme: resolve(theme), highContrast, setHighContrast }}>
{children}
</ThemeContext.Provider>
)
Expand Down
Loading
Loading