Skip to content

Commit caebbe4

Browse files
committed
Change useTheme logic
1 parent 4a40c5f commit caebbe4

File tree

2 files changed

+65
-43
lines changed

2 files changed

+65
-43
lines changed
Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,13 @@
11
'use client'
2-
import { useId, useState } from 'react'
32

4-
import type { DevupTheme } from '../types/theme'
5-
import { useSafeEffect } from './use-safe-effect'
3+
import { useSyncExternalStore } from 'react'
4+
import { themeStore } from 'src/stores/themeStore'
65

7-
let observer: null | MutationObserver = null
8-
const setThemeMap: Record<string, React.Dispatch<keyof DevupTheme>> = {}
9-
let globalTheme: keyof DevupTheme | null = null
10-
11-
export function useTheme(): keyof DevupTheme | null {
12-
const id = useId()
13-
const [theme, setTheme] = useState<keyof DevupTheme | null>(globalTheme)
14-
useSafeEffect(() => {
15-
if (globalTheme !== null) return
16-
const currentTheme = document.documentElement.getAttribute('data-theme')
17-
if (currentTheme !== null && currentTheme !== theme)
18-
setTheme(currentTheme as keyof DevupTheme)
19-
}, [theme])
20-
useSafeEffect(() => {
21-
const targetNode = document.documentElement
22-
setThemeMap[id] = setTheme
23-
if (!observer) {
24-
observer = new MutationObserver(() => {
25-
const theme = document.documentElement.getAttribute('data-theme')
26-
globalTheme = theme as keyof DevupTheme
27-
for (const key in setThemeMap)
28-
setThemeMap[key](theme as keyof DevupTheme)
29-
})
30-
observer.observe(targetNode, {
31-
attributes: true,
32-
attributeFilter: ['data-theme'],
33-
childList: false,
34-
subtree: false,
35-
characterData: false,
36-
attributeOldValue: false,
37-
characterDataOldValue: false,
38-
})
39-
}
40-
41-
return () => {
42-
delete setThemeMap[id]
43-
if (observer && Object.keys(setThemeMap).length === 0)
44-
observer.disconnect()
45-
}
46-
}, [id])
6+
export function useTheme() {
7+
const theme = useSyncExternalStore(
8+
themeStore.subscribe,
9+
themeStore.get,
10+
themeStore.get,
11+
)
4712
return theme
4813
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use client'
2+
3+
import { DevupTheme } from '@devup-ui/react'
4+
5+
type Theme = keyof DevupTheme | null
6+
type StoreChangeEvent = (newTheme: Theme) => void
7+
8+
const initTheme = null
9+
10+
export const themeStore = (() => {
11+
if (typeof window === 'undefined')
12+
return {
13+
get: () => initTheme,
14+
set: () => {},
15+
subscribe: () => () => {},
16+
}
17+
const el = document.documentElement
18+
19+
const subscribers: Set<StoreChangeEvent> = new Set()
20+
let theme: Theme = initTheme
21+
const get = () => {
22+
return theme
23+
}
24+
const set = (newTheme: Theme) => {
25+
theme = newTheme
26+
subscribers.forEach((subscriber) => subscriber(theme))
27+
}
28+
29+
const subscribe = (onStoreChange: StoreChangeEvent) => {
30+
subscribers.add(onStoreChange)
31+
set(el.getAttribute('data-theme') as Theme)
32+
return () => subscribers.delete(onStoreChange)
33+
}
34+
35+
const mo = new MutationObserver((mutations) => {
36+
mutations.forEach((mutation) => {
37+
if (
38+
mutation.type === 'attributes' &&
39+
mutation.target instanceof HTMLElement
40+
) {
41+
set(mutation.target.getAttribute('data-theme') as Theme)
42+
}
43+
})
44+
})
45+
46+
mo.observe(el, {
47+
attributes: true,
48+
attributeFilter: ['data-theme'],
49+
subtree: false,
50+
})
51+
52+
return {
53+
get,
54+
set,
55+
subscribe,
56+
}
57+
})()

0 commit comments

Comments
 (0)