useThemeSandbox.tsx117 lines · main
1'use client'
2
3import { useEffect, useState } from 'react'
4
5import { IS_PROD } from '../constants'
6
7const defaultDark: { [name: string]: string } = {
8 '--brand-accent': '160deg 100% 50%',
9 '--brand-default': '159.9deg 100% 38.6%',
10 '--brand-600': '136deg 59.5% 70%',
11 '--brand-500': '160.4deg 100% 19.2%',
12 '--brand-400': '160.4deg 100% 9.6%',
13 '--brand-300': '158.4deg 100% 4.9%',
14 '--brand-200': '162deg 100% 2%',
15 '--border-stronger': '200deg 16.1% 22%',
16 '--border-strong': '200deg 16.5% 17.8%',
17 '--border-alternative': '200deg 16.4% 13.1%',
18 '--border-control': '200deg 10% 14%',
19 '--border-overlay': '200deg 20% 9%',
20 '--border-secondary': '200deg 21.6% 10%',
21 '--border-muted': '200deg 14.9% 9.2%',
22 '--border-default': '200deg 10% 11%',
23 '--background-muted': '200deg 9.1% 10.8%',
24 '--background-overlay-hover': '200deg 20% 11%',
25 '--background-overlay-default': '200deg 14.3% 6.9%',
26 '--background-surface-300': '200deg 20% 10%',
27 '--background-surface-200': '200deg 20% 7%',
28 '--background-surface-100': '200deg 20% 6%',
29 '--background-control': '200deg 9% 11%',
30 '--background-selection': '200deg 9.8% 10%',
31 '--background-alternative': '200deg 20% 2.9%',
32 '--background-default': '200deg 20% 4%',
33 '--foreground-muted': '200deg 10% 35%',
34 '--foreground-lighter': '200deg 8% 55%',
35 '--foreground-light': '200deg 5% 69%',
36 '--foreground-default': '200deg 0% 93%',
37}
38
39/**
40 * Shows a GUI to test color themes in dev and preview env.
41 *
42 * To access sandbox mode:
43 * - switch theme to dark mode
44 * - append "#theme-sandbox" to the url
45 * - select "Apply Theme" to apply preset (localStorage will keep track of changes so you don't lose new values)
46 * - select "Reset localStorage" and refresh page to restart
47 */
48export const useThemeSandbox = (): any => {
49 const isWindowUndefined = typeof window === 'undefined'
50 if (isWindowUndefined || IS_PROD) return null
51 const hash = window.location.hash
52 const defaultConfig = defaultDark // use dark default tokens
53 // const defaultConfig = defaultLight // use light default tokens
54 const localPreset = localStorage.getItem('theme-sandbox')
55 const isSandbox = hash.includes('#theme-sandbox') || localPreset !== null
56 const [themeConfig, setThemeConfig] = useState(
57 localPreset ? JSON.parse(localPreset) : defaultConfig
58 )
59 const styles = document.querySelector(':root') as any
60
61 const handleSetThemeConfig = (name: string, value: any) => {
62 updateCSSVariables()
63 setThemeConfig((prevConfig: any) => ({ ...prevConfig, [name]: value }))
64 }
65
66 const updateCSSVariables = () => {
67 Object.entries(themeConfig).map(([key, value]) => styles.style.setProperty(key, value))
68 localStorage.setItem('theme-sandbox', JSON.stringify(themeConfig))
69 }
70
71 const init = async () => {
72 if (!isSandbox) return
73 const dat = await import('dat.gui')
74 const gui = new dat.GUI()
75
76 gui.width = 500
77
78 Object.entries(defaultConfig).map(([key, _value]) => {
79 if (!themeConfig[key]) return localStorage.removeItem('theme-sandbox')
80 const folderName = key.split('-')[2]
81 const folder = gui.__folders[folderName] ?? gui.addFolder(folderName)
82
83 return folder
84 .add(themeConfig, key)
85 .name(key)
86 .onChange((newValue) => {
87 handleSetThemeConfig(key, newValue)
88 })
89 })
90
91 var obj = {
92 'Apply Theme': function () {
93 updateCSSVariables()
94 },
95 'Exit Sandbox': function () {
96 gui.destroy()
97 },
98 'Reset localStorage': function () {
99 localStorage.removeItem('theme-sandbox')
100 setThemeConfig(defaultConfig)
101 },
102 }
103
104 gui.add(obj, 'Apply Theme')
105 gui.add(obj, 'Reset localStorage')
106 gui.add(obj, 'Exit Sandbox')
107 gui.load
108 }
109
110 useEffect(() => {
111 init()
112 }, [])
113
114 return { themeConfig, handleSetThemeConfig, isSandbox }
115}
116
117export default useThemeSandbox