Last active
January 20, 2023 05:23
-
-
Save mihkeleidast/66ee266c05861f7b181a2e66f2e634f9 to your computer and use it in GitHub Desktop.
vanilla-extract themes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
lightTheme, | |
darkTheme, | |
} from 'component-library'; | |
// Define all themes that can be chosen. | |
const themes: Record<string, ThemeObject> = { | |
'light': { | |
className: lightTheme, | |
}, | |
'dark': { | |
className: darkTheme, | |
}, | |
}; | |
const App = () => { | |
// Use whatever solution for storing the value, for example save it to a database per user or to localStorage per browser. | |
const [theme, setTheme] = useStoredTheme(); | |
return ( | |
<ThemeProvider themes={themes} currentTheme={theme} onThemeChange={setTheme}> | |
{children} | |
</ThemeProvider> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createTheme } from '@vanilla-extract/css'; | |
import lightVars from './variables-light.json'; | |
import darkVars from './variables-dark.json'; | |
// Create default theme with contract for others | |
export const [lightTheme, vars] = createTheme(lightVars); | |
// Create other themes | |
export const darkTheme = createTheme(vars, darkVars); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import { useStyle } from './use_style'; | |
import { ThemeCssFile, ThemeObject } from './types'; | |
import { useGlobalClassName } from './use_global_classname'; | |
export interface ThemeProviderProps { | |
/** | |
* Your app. | |
*/ | |
children: React.ReactNode; | |
/** | |
* The current theme value. If there is a need to persist the user choice, it needs to be saved externally. | |
*/ | |
currentTheme: string; | |
/** | |
* Map of available themes. | |
*/ | |
themes: Record<string, ThemeObject>; | |
/** | |
* Callback triggered when something calls `setTheme` from the hook. | |
*/ | |
onThemeChange?: (theme: string) => void; | |
} | |
/** | |
* Provides the current active theme name and variables to its subtree. | |
*/ | |
export const ThemeProvider = ({ children, currentTheme, themes, onThemeChange }: ThemeProviderProps) => { | |
const classNameMap = React.useMemo(() => { | |
return Object.keys(themes).reduce<Record<string, string>>((prev, current) => { | |
return { | |
...prev, | |
[current]: themes[current].className, | |
}; | |
}, {}); | |
}, [themes]); | |
useGlobalClassName(currentTheme, classNameMap); | |
const contextValue = React.useMemo( | |
() => ({ currentTheme, themes, onThemeChange }), | |
[currentTheme, themes, onThemeChange], | |
); | |
return <ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>; | |
}; | |
interface ThemeContext { | |
currentTheme: string | null; | |
themes: Record<string, ThemeObject> | null; | |
onThemeChange?: (theme: string) => void; | |
} | |
export const ThemeContext = React.createContext<ThemeContext>({ | |
currentTheme: null, | |
themes: null, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
export const useGlobalClassName = (currentTheme: string, classNameMap: Record<string, string>) => { | |
React.useEffect(() => { | |
const currentClassName = classNameMap[currentTheme]; | |
if (!document.documentElement.classList.contains(currentClassName)) { | |
const allThemeClasses = Object.values(classNameMap); | |
document.documentElement.classList.remove(...allThemeClasses); | |
document.documentElement.classList.add(currentClassName); | |
} | |
}, [currentTheme, classNameMap]); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looks gorgeous, but how to deal with
ThemeObject
?