weitere perfektionierung der neuen mobile app
This commit is contained in:
@@ -1,68 +1,83 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export type Appearance = 'light' | 'dark' | 'system';
|
||||
|
||||
export function useAppearance(): { appearance: Appearance; updateAppearance: (mode: Appearance) => void } {
|
||||
const [appearance, setAppearance] = useState<Appearance>('system');
|
||||
type AppearanceContextValue = {
|
||||
appearance: Appearance;
|
||||
resolved: 'light' | 'dark';
|
||||
updateAppearance: (mode: Appearance) => void;
|
||||
};
|
||||
|
||||
const AppearanceContext = createContext<AppearanceContextValue>({
|
||||
appearance: 'system',
|
||||
resolved: 'light',
|
||||
updateAppearance: () => {},
|
||||
});
|
||||
|
||||
function resolveTheme(mode: Appearance): 'light' | 'dark' {
|
||||
if (mode === 'dark') return 'dark';
|
||||
if (mode === 'light') return 'light';
|
||||
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function applyDocumentClass(theme: 'light' | 'dark') {
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
|
||||
export function AppearanceProvider({ children }: { children: React.ReactNode }) {
|
||||
const [appearance, setAppearance] = useState<Appearance>(() => {
|
||||
const stored = localStorage.getItem('theme') as Appearance | null;
|
||||
return stored ?? 'system';
|
||||
});
|
||||
const [resolved, setResolved] = useState<'light' | 'dark'>(() => resolveTheme(appearance));
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem('theme') as Appearance | null;
|
||||
if (stored) {
|
||||
setAppearance(stored);
|
||||
} else {
|
||||
setAppearance('system');
|
||||
const nextResolved = resolveTheme(appearance);
|
||||
setResolved(nextResolved);
|
||||
applyDocumentClass(nextResolved);
|
||||
|
||||
if (appearance === 'system') {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const listener = () => {
|
||||
const resolvedTheme = mediaQuery.matches ? 'dark' : 'light';
|
||||
setResolved(resolvedTheme);
|
||||
applyDocumentClass(resolvedTheme);
|
||||
};
|
||||
mediaQuery.addEventListener('change', listener);
|
||||
return () => mediaQuery.removeEventListener('change', listener);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return undefined;
|
||||
}, [appearance]);
|
||||
|
||||
const updateAppearance = (mode: Appearance) => {
|
||||
setAppearance(mode);
|
||||
localStorage.setItem('theme', mode);
|
||||
if (mode === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (appearance === 'system') {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (mediaQuery.matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
const listener = (e: MediaQueryListEvent) => {
|
||||
if (e.matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
mediaQuery.addEventListener('change', listener);
|
||||
return () => mediaQuery.removeEventListener('change', listener);
|
||||
} else if (appearance === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}, [appearance]);
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
appearance,
|
||||
resolved,
|
||||
updateAppearance,
|
||||
}),
|
||||
[appearance, resolved]
|
||||
);
|
||||
|
||||
return { appearance, updateAppearance };
|
||||
return React.createElement(AppearanceContext.Provider, { value }, children);
|
||||
}
|
||||
|
||||
export function useAppearance(): AppearanceContextValue {
|
||||
return useContext(AppearanceContext);
|
||||
}
|
||||
|
||||
export function initializeTheme() {
|
||||
const stored = localStorage.getItem('theme') as Appearance | null;
|
||||
if (stored) {
|
||||
if (stored === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
} else {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (mediaQuery.matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
}
|
||||
const mode = stored ?? 'system';
|
||||
const resolved = resolveTheme(mode);
|
||||
applyDocumentClass(resolved);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user