Theming in React
하려고 하는 것.
ThemeProvider로 theme 상태를 저장 및 Component tree가 theme 상태에 접근할 수 있게 하고 useTheme 훅을 통해서 theme상태를 토글 또는 설정하는 함수를 사용하려고 한다.
// entry tsx
<ThemeProvider>
<App />
</ThemeProvider>
// component tsx
const { toggleTheme } = useTheme()
const handleClick = () => { toggleTheme() }
CSS Variables
theming을 위해서 root에 css variable을 설정해준다.
:root {
--foreground: #000;
--background: #fff;
}
.dark {
--foreground: #fff;
--background: #000;
}
Class based theming
dark 클래스가 적용되면 dark theme color가 적용 된다. class를 통해서 css variable 설정을 바꿔준다.
<html class="light">
<!-- or -->
<html class="dark">
Persistent theme state
localStorage에 theme 상태를 저장해서 theme을 유지할 수 있도록 한다.
// at last
window.localStorage.set("theme", "light") // or
window.localStorage.set("theme", "dark")
ThemeProvider
ThemeProvider는 ThemeContext를 children tree에 전달하는 역할을 하는 컴포넌트다. ThemeProvider의 children tree는 ThemeContext에 access할 수 있다.
ThemeContext는 theme 상태와 상태와 관련된 로직을 가지고 있다.
type ColorTheme = "light" | "dark";
type TheemContextType = {
theme: ColorTheme;
setTheme: (theme: ColorTheme) => void;
toggleTheme: () => void;
};
export const ThemeContext = createContext<TheemContextType | null>(null);
export const ThemeProvider = ({children}: PropsWithChildren) => {
// ...
return <ThemeContext.Provider>{children}</ThemeContext.Provider>
}
default theme
기본 theme은 로컬 스토리지에 저장된 theme을 사용한다. 만약 값이 없다면 system preference의 theme으로 설정해준다. 이를 위한 helper 함수를 만든다.
const isServer = () => typeof window === "undefined";
const getPersistedTheme = (): ColorTheme => {
if (isServer()) {
return "light";
}
const value = window.localStorage.getItem(KEY);
if (!value) {
const systemTheme = getSystemTheme();
window.localStorage.setItem(KEY, systemTheme);
return systemTheme;
}
return value as ColorTheme;
};
const getSystemTheme = (): ColorTheme => {
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
return isDark ? "dark" : "light";
};
setTheme
setTheme
은 다음 세 가지 행동을 해야 한다.
- change react state
- change html class name
- change localStorage’value
References
https://github.com/pacocoursey/next-themes/blob/main/packages/next-themes/src/index.tsx