Skip to content

Commit 18cb938

Browse files
authored
Merge pull request #57 from smooth-code/fix-color-modes-sync
fix: sync system color mode
2 parents 3601359 + 6428360 commit 18cb938

File tree

1 file changed

+64
-30
lines changed

1 file changed

+64
-30
lines changed

packages/core/src/colorModes.js

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ function getModeTheme(theme, mode) {
3030
const getMediaQuery = query => `@media ${query}`
3131
const getColorModeQuery = mode => `(prefers-color-scheme: ${mode})`
3232

33-
function detectSystemMode(mode) {
34-
if (window.matchMedia === undefined) return null
35-
const query = getColorModeQuery(mode)
36-
const mql = window.matchMedia(query)
37-
return mql.matches && mql.media === query
38-
}
39-
4033
function hasColorModes(theme) {
4134
return theme && theme.colors && theme.colors.modes
4235
}
@@ -101,55 +94,96 @@ export function createColorStyles(theme, { targetSelector = 'body' } = {}) {
10194
return `${targetSelector}{${styles}}`
10295
}
10396

97+
function getSystemModeMql(mode) {
98+
if (window.matchMedia === undefined) return null
99+
const query = getColorModeQuery(mode)
100+
return window.matchMedia(query)
101+
}
102+
104103
function useSystemMode(theme) {
105-
return React.useMemo(() => {
106-
if (!hasColorModes(theme) || !hasMediaQueryEnabled(theme)) return null
107-
return (
108-
SYSTEM_MODES.find(mode => {
109-
if (!theme.colors.modes[mode]) return null
110-
return detectSystemMode(mode)
111-
}) || null
112-
)
104+
const configs = React.useMemo(() => {
105+
if (!hasMediaQueryEnabled(theme)) return []
106+
return SYSTEM_MODES.map(mode => {
107+
if (!theme.colors.modes[mode]) return null
108+
const mql = getSystemModeMql(mode)
109+
return mql ? { mode, mql } : null
110+
}).filter(Boolean)
113111
}, [theme])
112+
113+
const [systemMode, setSystemMode] = React.useState(() => {
114+
const config = configs.find(config => config.mql.matches)
115+
return config ? config.mode : null
116+
})
117+
118+
React.useEffect(() => {
119+
const cleans = configs
120+
.filter(({ mql }) => mql.addListener && mql.removeListener)
121+
.map(({ mode, mql }) => {
122+
const handler = ({ matches }) => {
123+
if (matches) {
124+
setSystemMode(mode)
125+
} else {
126+
setSystemMode(previousMode => (previousMode === mode ? null : mode))
127+
}
128+
}
129+
mql.addListener(handler)
130+
return () => mql.removeListener(handler)
131+
})
132+
return () => cleans.forEach(clean => clean())
133+
})
134+
135+
return systemMode
114136
}
115137

116138
export function useColorModeState(theme, { target = document.body } = {}) {
117139
const systemMode = useSystemMode(theme)
140+
const defaultColorMode = getDefaultColorModeName(theme)
141+
const initialColorMode = getInitialColorModeName(theme)
118142
const [mode, setMode] = React.useState(() => {
119143
if (!hasColorModes(theme)) return null
120144
const storedMode = storage.get()
121-
return storedMode || systemMode || getDefaultColorModeName(theme)
145+
return storedMode || systemMode || defaultColorMode
122146
})
123147

124148
// Add mode className
125149
const customPropertiesEnabled = hasCustomPropertiesEnabled(theme)
126150

151+
const manualSetRef = React.useRef(false)
152+
const manuallySetMode = React.useCallback(value => {
153+
manualSetRef.current = true
154+
setMode(value)
155+
}, [])
156+
127157
// Store mode preference
128-
const changedRef = React.useRef(false)
129-
React.useEffect(() => {
130-
if (changedRef.current) {
158+
React.useLayoutEffect(() => {
159+
if (manualSetRef.current) {
131160
storage.set(mode)
132-
} else {
133-
changedRef.current = true
134161
}
135162
}, [mode])
136163

137-
const initialMode = getInitialColorModeName(theme)
138-
139-
React.useEffect(() => {
140-
if (!customPropertiesEnabled) return undefined
164+
// Sync system mode
165+
React.useLayoutEffect(() => {
141166
const storedMode = storage.get()
142-
const fromSystem = !storedMode && systemMode === mode
143-
const initial = !storedMode && initialMode === mode
144-
if (fromSystem || initial) return undefined
167+
if (storedMode) return
168+
const targetMode = systemMode || defaultColorMode
169+
if (targetMode === mode) return
170+
setMode(targetMode)
171+
}, [mode, systemMode, defaultColorMode])
172+
173+
// Add and remove class names
174+
React.useLayoutEffect(() => {
175+
if (!customPropertiesEnabled) return undefined
176+
const stored = storage.get()
177+
const initial = initialColorMode !== mode
178+
if (!stored && !initial) return undefined
145179
const className = getColorModeClassName(mode)
146180
target.classList.add(className)
147181
return () => {
148182
target.classList.remove(className)
149183
}
150-
}, [customPropertiesEnabled, target, mode, systemMode, initialMode])
184+
}, [customPropertiesEnabled, target, mode, initialColorMode])
151185

152-
return [mode, setMode]
186+
return [mode, manuallySetMode]
153187
}
154188

155189
export function useColorModeTheme(theme, mode) {

0 commit comments

Comments
 (0)