Skip to content

Commit 8e902fe

Browse files
committed
chore: update types
1 parent d4ce177 commit 8e902fe

File tree

3 files changed

+131
-85
lines changed

3 files changed

+131
-85
lines changed

README.md

+93-37
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ It's recommended to initialize Palettez in a synchronous script to avoid theme f
4343
<script src="https://cdn.jsdelivr.net/npm/palettez"></script>
4444

4545
<script>
46-
;(() => {
46+
;(async () => {
4747
const themeStore = window.palettez.createThemeStore({
4848
config: {
4949
colorScheme: {
@@ -140,41 +140,43 @@ const themeStore = createThemeStore({
140140

141141
// optional, specify your own storage solution. localStorage is used by default.
142142
storage: ({ abortController }: { abortController?: AbortController }) => {
143-
return {
144-
getItem: (key: string) => {
145-
try {
146-
return JSON.parse(window.localStorage.getItem(key) || 'null')
147-
} catch {
148-
return null
149-
}
150-
},
151-
152-
setItem: (key: string, value: object) => {
153-
window.localStorage.setItem(key, JSON.stringify(value))
154-
},
155-
156-
watch: (cb) => {
157-
const controller = new AbortController()
158-
159-
window.addEventListener(
160-
'storage',
161-
(e) => {
162-
const persistedThemes = JSON.parse(e.newValue || 'null')
163-
cb(e.key, persistedThemes)
164-
},
165-
{
166-
signal: abortController
167-
? AbortSignal.any([abortController.signal, controller.signal])
168-
: controller.signal,
169-
},
170-
)
171-
172-
return () => {
173-
controller.abort()
174-
}
175-
},
176-
}
177-
}
143+
return {
144+
getItem: (key: string) => {
145+
try {
146+
return JSON.parse(window.localStorage.getItem(key) || 'null')
147+
} catch {
148+
return null
149+
}
150+
},
151+
152+
setItem: (key: string, value: object) => {
153+
window.localStorage.setItem(key, JSON.stringify(value))
154+
},
155+
156+
watch: (cb) => {
157+
const controller = new AbortController()
158+
159+
window.addEventListener(
160+
'storage',
161+
(e) => {
162+
if (e.storageArea !== localStorage) return
163+
const persistedThemes = JSON.parse(e.newValue || 'null')
164+
cb(e.key, persistedThemes)
165+
},
166+
{
167+
signal: AbortSignal.any([
168+
abortController.signal,
169+
controller.signal,
170+
]),
171+
},
172+
)
173+
174+
return () => {
175+
controller.abort()
176+
}
177+
},
178+
}
179+
}
178180
})
179181
```
180182

@@ -194,6 +196,60 @@ themeStore.getResolvedThemes() // { colorScheme: 'light', contrast: 'standard' }
194196
themeStore.setThemes({ contrast: 'high' })
195197
await themeStore.restore() // restore persisted theme selection
196198
themeStore.sync() // useful for syncing theme selection across tabs and windows
197-
themeStore.subscribe((themes, resolvedThemes) => { /* ... */ }) // return unsubscribe function
199+
const unsubscribe = themeStore.subscribe((themes, resolvedThemes) => { /* ... */ })
200+
themeStore.destroy()
198201
```
199202

203+
## React Integration
204+
205+
### Client-only persistence
206+
207+
Ensure that you have initialized Palettez as per instructions under [Basic Usage](#basic-usage). As theme selection is only known on the client, you should only render component with `usePalettez` once the app has mounted.
208+
209+
```tsx
210+
import * as React from 'react'
211+
import { usePalettez } from 'palettez/react'
212+
213+
function Component() {
214+
const [mounted, setMounted] = useState(false)
215+
216+
useEffect(() => {
217+
setMounted(true)
218+
}, [])
219+
220+
return mounted ? <ThemeSelect /> : null
221+
}
222+
223+
function ThemeSelect() {
224+
const {
225+
themesAndOptions,
226+
themes,
227+
setThemes,
228+
229+
getResolvedThemes,
230+
restore,
231+
sync,
232+
subscribe,
233+
} = usePalettez(window.palettez.getThemeStore())
234+
235+
return themesAndOptions.map((theme) => (
236+
<div key={theme.key}>
237+
<label htmlFor={theme.key}>{theme.label}</label>
238+
<select
239+
id={theme.key}
240+
name={theme.key}
241+
onChange={(e) => {
242+
setThemes({ [theme.key]: e.target.value })
243+
}}
244+
value={themes[theme.key]}
245+
>
246+
{theme.options.map((option) => (
247+
<option key={option.key} value={option.key}>
248+
{option.value}
249+
</option>
250+
))}
251+
</select>
252+
</div>
253+
))
254+
}
255+
```

palettez/src/index.ts

+20-31
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ const isClient = !!(
5656
typeof window.document.createElement !== 'undefined'
5757
)
5858

59-
const DEFAULT_OPTIONS = {
60-
key: packageName,
61-
storage: localStorageAdapter(),
62-
}
63-
6459
function getThemeAndOptions(config: ThemeConfig) {
6560
return Object.entries(config).reduce<
6661
Array<{
@@ -95,8 +90,13 @@ class ThemeStore<T extends ThemeStoreOptions['config']> {
9590
#listeners: Set<Listener<T>> = new Set<Listener<T>>()
9691
#abortController = new AbortController()
9792

98-
constructor(options: ThemeStoreOptions) {
99-
const { config } = options
93+
constructor({
94+
key = packageName,
95+
config,
96+
initialThemes = {},
97+
storage = localStorageAdapter(),
98+
}: ThemeStoreOptions) {
99+
this.#options = { key, config, initialThemes, storage }
100100

101101
this.#defaultThemes = Object.fromEntries(
102102
Object.entries(config).map(([theme, themeConfig]) => {
@@ -109,21 +109,12 @@ class ThemeStore<T extends ThemeStoreOptions['config']> {
109109
}),
110110
) as Themes<T>
111111

112-
this.#options = {
113-
...DEFAULT_OPTIONS,
114-
...options,
115-
initialThemes: options.initialThemes || {},
116-
}
112+
this.#currentThemes = { ...this.#defaultThemes, ...initialThemes }
117113

118114
this.#storage = this.#options.storage({
119115
abortController: this.#abortController,
120116
})
121117

122-
this.#currentThemes = {
123-
...this.#defaultThemes,
124-
...this.#options.initialThemes,
125-
}
126-
127118
this.#resolvedOptionsByTheme = Object.fromEntries(
128119
Object.keys(config).map((theme) => [theme, {}]),
129120
)
@@ -138,29 +129,27 @@ class ThemeStore<T extends ThemeStoreOptions['config']> {
138129
}
139130

140131
setThemes = async (themes: Partial<Themes<T>>): Promise<void> => {
141-
this.#setThemeAndNotify({ ...this.#currentThemes, ...themes })
142-
143-
// this.#storage.broadcast?.(this.#options.key, this.#currentThemes)
132+
this.#setThemesAndNotify({ ...this.#currentThemes, ...themes })
144133

145134
await this.#storage.setItem(this.#options.key, this.#currentThemes)
135+
136+
this.#storage.broadcast?.(this.#options.key, this.#currentThemes)
146137
}
147138

148139
restore = async (): Promise<void> => {
149140
const persistedThemes = await this.#storage.getItem(this.#options.key)
150141

151-
this.#setThemeAndNotify(
142+
this.#setThemesAndNotify(
152143
(persistedThemes as Themes<T>) || this.#defaultThemes,
153144
)
154-
155-
// this.#storage.broadcast?.(this.#options.key, this.#currentThemes)
156145
}
157146

158147
// clear = async (): Promise<void> => {
159-
// this.#setThemeAndNotify({ ...this.#defaultThemes })
160-
161-
// this.#storage.broadcast?.(this.#options.key, this.#currentThemes)
148+
// this.#setThemesAndNotify({ ...this.#defaultThemes })
162149

163150
// await this.#storage.removeItem(this.#options.key)
151+
152+
// this.#storage.broadcast?.(this.#options.key, this.#currentThemes)
164153
// }
165154

166155
subscribe = (callback: Listener<T>): (() => void) => {
@@ -185,7 +174,7 @@ class ThemeStore<T extends ThemeStoreOptions['config']> {
185174
return this.#storage.watch((key, persistedThemes) => {
186175
if (key !== this.#options.key) return
187176

188-
this.#setThemeAndNotify(
177+
this.#setThemesAndNotify(
189178
(persistedThemes as Themes<T>) || this.#defaultThemes,
190179
)
191180
})
@@ -197,7 +186,7 @@ class ThemeStore<T extends ThemeStoreOptions['config']> {
197186
registry.delete(this.#options.key)
198187
}
199188

200-
#setThemeAndNotify = (theme: Themes<T>): void => {
189+
#setThemesAndNotify = (theme: Themes<T>): void => {
201190
this.#currentThemes = theme
202191
const resolvedThemes = this.#resolveThemes()
203192
this.#notify(resolvedThemes)
@@ -253,7 +242,7 @@ class ThemeStore<T extends ThemeStoreOptions['config']> {
253242
: ifNotMatch
254243

255244
if (this.#currentThemes[theme] === option.key) {
256-
this.#setThemeAndNotify({ ...this.#currentThemes })
245+
this.#setThemesAndNotify({ ...this.#currentThemes })
257246
}
258247
},
259248
{ signal: this.#abortController.signal },
@@ -275,7 +264,7 @@ const registry = new Map<string, ThemeStore<ThemeConfig>>()
275264
function createThemeStore<T extends ThemeStoreOptions>(
276265
options: T,
277266
): ThemeStore<T['config']> {
278-
const storeKey = options.key || DEFAULT_OPTIONS.key
267+
const storeKey = options.key || packageName
279268
if (registry.has(storeKey)) {
280269
registry.get(storeKey)!.destroy()
281270
}
@@ -287,7 +276,7 @@ function createThemeStore<T extends ThemeStoreOptions>(
287276
function getThemeStore<T extends ThemeStoreOptions>(
288277
key: string,
289278
): ThemeStore<T['config']> {
290-
const storeKey = key || DEFAULT_OPTIONS.key
279+
const storeKey = key || packageName
291280
if (!registry.has(storeKey)) {
292281
throw new Error(
293282
`[${packageName}] Theme store with key '${storeKey}' could not be found. Please run \`createThemeStore\` with key '${storeKey}' first.`,

palettez/src/storage.ts

+18-17
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,16 @@ type StorageAdapter = {
1717
watch?: (cb: (key: string | null, value: object) => void) => () => void
1818
}
1919

20-
type StorageAdapterCreate =
21-
| (({
22-
abortController,
23-
}: { abortController?: AbortController }) => StorageAdapter)
24-
| (() => StorageAdapter)
20+
type StorageAdapterCreate = ({
21+
abortController,
22+
}: { abortController: AbortController }) => StorageAdapter
2523

2624
type StorageAdapterCreator<Options> = (
2725
options?: Options,
2826
) => StorageAdapterCreate
2927

3028
const localStorageAdapter: StorageAdapterCreator<never> = () => {
31-
return ({ abortController }: { abortController?: AbortController }) => {
29+
return ({ abortController }) => {
3230
return {
3331
getItem: (key: string) => {
3432
try {
@@ -57,9 +55,10 @@ const localStorageAdapter: StorageAdapterCreator<never> = () => {
5755
cb(e.key, persistedThemes)
5856
},
5957
{
60-
signal: abortController
61-
? AbortSignal.any([abortController.signal, controller.signal])
62-
: controller.signal,
58+
signal: AbortSignal.any([
59+
abortController.signal,
60+
controller.signal,
61+
]),
6362
},
6463
)
6564

@@ -72,7 +71,7 @@ const localStorageAdapter: StorageAdapterCreator<never> = () => {
7271
}
7372

7473
const sessionStorageAdapter: StorageAdapterCreator<never> = () => {
75-
return ({ abortController }: { abortController?: AbortController }) => {
74+
return ({ abortController }) => {
7675
return {
7776
getItem: (key: string) => {
7877
try {
@@ -101,9 +100,10 @@ const sessionStorageAdapter: StorageAdapterCreator<never> = () => {
101100
cb(e.key, persistedThemes)
102101
},
103102
{
104-
signal: abortController
105-
? AbortSignal.any([abortController.signal, controller.signal])
106-
: controller.signal,
103+
signal: AbortSignal.any([
104+
abortController.signal,
105+
controller.signal,
106+
]),
107107
},
108108
)
109109

@@ -116,7 +116,7 @@ const sessionStorageAdapter: StorageAdapterCreator<never> = () => {
116116
}
117117

118118
const memoryStorageAdapter: StorageAdapterCreator<never> = () => {
119-
return ({ abortController }: { abortController?: AbortController }) => {
119+
return ({ abortController }) => {
120120
const storage = new Map<string, object>()
121121
const channel = new BroadcastChannel(packageName)
122122

@@ -146,9 +146,10 @@ const memoryStorageAdapter: StorageAdapterCreator<never> = () => {
146146
cb(e.data.key, e.data.themes)
147147
},
148148
{
149-
signal: abortController
150-
? AbortSignal.any([abortController.signal, controller.signal])
151-
: controller.signal,
149+
signal: AbortSignal.any([
150+
abortController.signal,
151+
controller.signal,
152+
]),
152153
},
153154
)
154155

0 commit comments

Comments
 (0)