@tamagui/next-theme 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/cjs/NextTheme.js +9 -273
  2. package/dist/cjs/NextTheme.js.map +3 -3
  3. package/dist/cjs/NextThemeProvider.js +245 -0
  4. package/dist/cjs/NextThemeProvider.js.map +7 -0
  5. package/dist/cjs/ThemeSettingContext.js +36 -0
  6. package/dist/cjs/ThemeSettingContext.js.map +7 -0
  7. package/dist/cjs/UseThemeProps.js +17 -0
  8. package/dist/cjs/UseThemeProps.js.map +7 -0
  9. package/dist/cjs/constants.js +35 -0
  10. package/dist/cjs/constants.js.map +7 -0
  11. package/dist/cjs/helpers.js +52 -0
  12. package/dist/cjs/helpers.js.map +7 -0
  13. package/dist/cjs/types.js +17 -0
  14. package/dist/cjs/types.js.map +7 -0
  15. package/dist/cjs/useIsomorphicLayoutEffect.js +30 -0
  16. package/dist/cjs/useIsomorphicLayoutEffect.js.map +7 -0
  17. package/dist/cjs/useRootTheme.js +49 -0
  18. package/dist/cjs/useRootTheme.js.map +7 -0
  19. package/dist/cjs/useTheme.js +34 -0
  20. package/dist/cjs/useTheme.js.map +7 -0
  21. package/dist/esm/NextTheme.js +8 -265
  22. package/dist/esm/NextTheme.js.map +3 -3
  23. package/dist/esm/NextThemeProvider.js +215 -0
  24. package/dist/esm/NextThemeProvider.js.map +7 -0
  25. package/dist/esm/ThemeSettingContext.js +12 -0
  26. package/dist/esm/ThemeSettingContext.js.map +7 -0
  27. package/dist/esm/UseThemeProps.js +1 -0
  28. package/dist/esm/UseThemeProps.js.map +7 -0
  29. package/dist/esm/constants.js +9 -0
  30. package/dist/esm/constants.js.map +7 -0
  31. package/dist/esm/helpers.js +26 -0
  32. package/dist/esm/helpers.js.map +7 -0
  33. package/dist/esm/types.js +1 -0
  34. package/dist/esm/types.js.map +7 -0
  35. package/dist/esm/useIsomorphicLayoutEffect.js +6 -0
  36. package/dist/esm/useIsomorphicLayoutEffect.js.map +7 -0
  37. package/dist/esm/useRootTheme.js +19 -0
  38. package/dist/esm/useRootTheme.js.map +7 -0
  39. package/dist/esm/useTheme.js +9 -0
  40. package/dist/esm/useTheme.js.map +7 -0
  41. package/package.json +3 -3
  42. package/src/NextTheme.tsx +10 -408
  43. package/src/NextThemeProvider.tsx +297 -0
  44. package/src/ThemeSettingContext.tsx +9 -0
  45. package/src/UseThemeProps.tsx +46 -0
  46. package/src/constants.tsx +3 -0
  47. package/src/helpers.tsx +25 -0
  48. package/src/types.tsx +5 -0
  49. package/src/useIsomorphicLayoutEffect.tsx +4 -0
  50. package/src/useRootTheme.tsx +21 -0
  51. package/src/useTheme.tsx +11 -0
  52. package/types/NextTheme.d.ts +8 -53
  53. package/types/NextThemeProvider.d.ts +4 -0
  54. package/types/ThemeSettingContext.d.ts +4 -0
  55. package/types/UseThemeProps.d.ts +43 -0
  56. package/types/constants.d.ts +4 -0
  57. package/types/helpers.d.ts +4 -0
  58. package/types/types.d.ts +5 -0
  59. package/types/useIsomorphicLayoutEffect.d.ts +3 -0
  60. package/types/useRootTheme.d.ts +3 -0
  61. package/types/useTheme.d.ts +6 -0
package/src/NextTheme.tsx CHANGED
@@ -1,409 +1,11 @@
1
1
  // https://raw.githubusercontent.com/pacocoursey/next-themes/master/index.tsx
2
- // forked temporarily due to buggy theme change
3
-
4
- import { useEvent } from '@tamagui/use-event'
5
- import NextHead from 'next/head'
6
- import * as React from 'react'
7
- import {
8
- createContext,
9
- memo,
10
- useContext,
11
- useEffect,
12
- useLayoutEffect,
13
- useMemo,
14
- useRef,
15
- useState,
16
- } from 'react'
17
-
18
- const useIsomorphicLayoutEffect =
19
- typeof window !== 'undefined' ? useLayoutEffect : useEffect
20
-
21
- export interface UseThemeProps {
22
- /** List of all available theme names */
23
- themes: string[]
24
- /** Forced theme name for the current page */
25
- forcedTheme?: string
26
- /** Update the theme */
27
- set: (theme: string) => void
28
- toggle: () => void
29
- /** Active theme name - will return "system" if not overriden, see "resolvedTheme" for getting resolved system value */
30
- current?: string
31
- /** @deprecated Use `current` instead (deprecating avoid confusion with useTheme) */
32
- theme?: string
33
- /** If `enableSystem` is true and the active theme is "system", this returns whether the system preference resolved to "dark" or "light". Otherwise, identical to `theme` */
34
- resolvedTheme?: string
35
- /** If enableSystem is true, returns the System theme preference ("dark" or "light"), regardless what the active theme is */
36
- systemTheme?: 'dark' | 'light'
37
- }
38
-
39
- export interface ThemeProviderProps {
40
- children?: any
41
- /** List of all available theme names */
42
- themes?: string[]
43
- /** Forced theme name for the current page */
44
- forcedTheme?: string
45
- /** Whether to switch between dark and light themes based on prefers-color-scheme */
46
- enableSystem?: boolean
47
- systemTheme?: string
48
- /** Disable all CSS transitions when switching themes */
49
- disableTransitionOnChange?: boolean
50
- /** Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons */
51
- enableColorScheme?: boolean
52
- /** Key used to store theme setting in localStorage */
53
- storageKey?: string
54
- /** Default theme name (for v0.0.12 and lower the default was light). If `enableSystem` is false, the default theme is light */
55
- defaultTheme?: string
56
- /** HTML attribute modified based on the active theme. Accepts `class` and `data-*` (meaning any data attribute, `data-mode`, `data-color`, etc.) */
57
- attribute?: string | 'class'
58
- /** Mapping of theme name to HTML attribute value. Object where key is the theme name and value is the attribute value */
59
- value?: ValueObject
60
- onChangeTheme?: (name: string) => void
61
-
62
- // avoids warning
63
-
64
- skipNextHead?: boolean
65
- }
66
-
67
- const ThemeSettingContext = createContext<UseThemeProps>({
68
- toggle: () => {},
69
- set: (_) => {},
70
- themes: [],
71
- })
72
-
73
- /**
74
- * @deprecated renamed to `useThemeSetting` to avoid confusion with core `useTheme` hook
75
- */
76
- export const useTheme = () => useContext(ThemeSettingContext)
77
-
78
- export const useThemeSetting = () => useContext(ThemeSettingContext)
79
-
80
- const colorSchemes = ['light', 'dark']
81
- const MEDIA = '(prefers-color-scheme: dark)'
82
-
83
- interface ValueObject {
84
- [themeName: string]: string
85
- }
86
-
87
- // note this only works for light being default for now...
88
- export const useRootTheme = () => {
89
- const [val, setVal] = useState('light')
90
-
91
- if (typeof document !== 'undefined') {
92
- useLayoutEffect(() => {
93
- // @ts-ignore
94
- const classes = [...document.documentElement.classList]
95
- const isDark = classes.includes('t_dark')
96
- React.startTransition(() => {
97
- setVal(isDark ? 'dark' : 'light')
98
- })
99
- }, [])
100
- }
101
-
102
- return [val, setVal] as const
103
- }
104
-
105
- export const NextThemeProvider: React.FC<ThemeProviderProps> = ({
106
- forcedTheme,
107
- disableTransitionOnChange = true,
108
- enableSystem = true,
109
- enableColorScheme = true,
110
- storageKey = 'theme',
111
- themes = colorSchemes,
112
- defaultTheme = enableSystem ? 'system' : 'light',
113
- attribute = 'class',
114
- skipNextHead,
115
- onChangeTheme,
116
- value = {
117
- dark: 't_dark',
118
- light: 't_light',
119
- },
120
- children,
121
- }) => {
122
- const [theme, setThemeState] = useState(() => getTheme(storageKey, defaultTheme))
123
- const [resolvedTheme, setResolvedTheme] = useState(() => getTheme(storageKey))
124
- // const resolvedTheme = React.useDeferredValue(resolvedThemeFast)
125
- const attrs = !value ? themes : Object.values(value)
126
-
127
- const handleMediaQuery = useEvent((e?) => {
128
- const systemTheme = getSystemTheme(e)
129
- React.startTransition(() => {
130
- setResolvedTheme(systemTheme)
131
- })
132
- if (theme === 'system' && !forcedTheme) {
133
- handleChangeTheme(systemTheme, false)
134
- }
135
- })
136
-
137
- // Ref hack to avoid adding handleMediaQuery as a dep
138
- const mediaListener = useRef(handleMediaQuery)
139
- mediaListener.current = handleMediaQuery
140
-
141
- const handleChangeTheme = useEvent((theme, updateStorage = true, updateDOM = true) => {
142
- let name = value?.[theme] || theme
143
-
144
- if (updateStorage) {
145
- try {
146
- localStorage.setItem(storageKey, theme)
147
- } catch (e) {
148
- // Unsupported
149
- }
150
- }
151
-
152
- if (theme === 'system' && enableSystem) {
153
- const resolved = getSystemTheme()
154
- name = value?.[resolved] || resolved
155
- }
156
-
157
- onChangeTheme?.(name.replace('t_', ''))
158
-
159
- if (updateDOM) {
160
- const d = document.documentElement
161
- if (attribute === 'class') {
162
- d.classList.remove(...attrs)
163
- d.classList.add(name)
164
- } else {
165
- d.setAttribute(attribute, name)
166
- }
167
- }
168
- })
169
-
170
- useIsomorphicLayoutEffect(() => {
171
- const handler = (...args: any) => mediaListener.current(...args)
172
- // Always listen to System preference
173
- const media = window.matchMedia(MEDIA)
174
- // Intentionally use deprecated listener methods to support iOS & old browsers
175
- media.addListener(handler)
176
- handler(media)
177
- return () => {
178
- media.removeListener(handler)
179
- }
180
- }, [])
181
-
182
- const set = useEvent((newTheme) => {
183
- if (forcedTheme) {
184
- handleChangeTheme(newTheme, true, false)
185
- } else {
186
- handleChangeTheme(newTheme)
187
- }
188
- setThemeState(newTheme)
189
- })
190
-
191
- // localStorage event handling
192
- useEffect(() => {
193
- const handleStorage = (e: StorageEvent) => {
194
- if (e.key !== storageKey) {
195
- return
196
- }
197
- // If default theme set, use it if localstorage === null (happens on local storage manual deletion)
198
- const theme = e.newValue || defaultTheme
199
- set(theme)
200
- }
201
- window.addEventListener('storage', handleStorage)
202
- return () => {
203
- window.removeEventListener('storage', handleStorage)
204
- }
205
- }, [defaultTheme, set, storageKey])
206
-
207
- // color-scheme handling
208
- useIsomorphicLayoutEffect(() => {
209
- if (!enableColorScheme) return
210
-
211
- const colorScheme =
212
- // If theme is forced to light or dark, use that
213
- forcedTheme && colorSchemes.includes(forcedTheme)
214
- ? forcedTheme
215
- : // If regular theme is light or dark
216
- theme && colorSchemes.includes(theme)
217
- ? theme
218
- : // If theme is system, use the resolved version
219
- theme === 'system'
220
- ? resolvedTheme || null
221
- : null
222
-
223
- // color-scheme tells browser how to render built-in elements like forms, scrollbars, etc.
224
- // if color-scheme is null, this will remove the property
225
- const userPrefers =
226
- typeof window !== 'undefined' &&
227
- window.matchMedia &&
228
- window.matchMedia('(prefers-color-scheme: dark)').matches
229
- ? 'dark'
230
- : 'light'
231
-
232
- const wePrefer = colorScheme || 'light'
233
-
234
- // avoid running this because it causes full page reflow
235
- if (userPrefers !== wePrefer) {
236
- document.documentElement.style.setProperty('color-scheme', colorScheme)
237
- }
238
- }, [enableColorScheme, theme, resolvedTheme, forcedTheme])
239
-
240
- const toggle = useEvent(() => {
241
- const order =
242
- resolvedTheme === 'dark' ? ['system', 'light', 'dark'] : ['system', 'dark', 'light']
243
- const next = order[(order.indexOf(theme) + 1) % order.length]
244
- set(next)
245
- })
246
-
247
- const contextResolvedTheme = theme === 'system' ? resolvedTheme : theme
248
- const systemTheme = (enableSystem ? resolvedTheme : undefined) as
249
- | 'light'
250
- | 'dark'
251
- | undefined
252
- const contextValue = useMemo(() => {
253
- const value: UseThemeProps = {
254
- theme,
255
- current: theme,
256
- set,
257
- toggle,
258
- forcedTheme,
259
- resolvedTheme: contextResolvedTheme,
260
- themes: enableSystem ? [...themes, 'system'] : themes,
261
- systemTheme,
262
- } as const
263
- return value
264
- }, [
265
- theme,
266
- set,
267
- toggle,
268
- forcedTheme,
269
- contextResolvedTheme,
270
- enableSystem,
271
- themes,
272
- systemTheme,
273
- ])
274
-
275
- return (
276
- <ThemeSettingContext.Provider value={contextValue}>
277
- <ThemeScript
278
- {...{
279
- forcedTheme,
280
- storageKey,
281
- systemTheme: resolvedTheme,
282
- attribute,
283
- value,
284
- enableSystem,
285
- defaultTheme,
286
- attrs,
287
- skipNextHead,
288
- }}
289
- />
290
- {/* because on SSR we re-run and can avoid whole tree re-render */}
291
- {useMemo(() => children, [children])}
292
- </ThemeSettingContext.Provider>
293
- )
294
- }
295
-
296
- const ThemeScript = memo(
297
- ({
298
- forcedTheme,
299
- storageKey,
300
- attribute,
301
- enableSystem,
302
- defaultTheme,
303
- value,
304
- attrs,
305
- skipNextHead,
306
- }: {
307
- forcedTheme?: string
308
- storageKey: string
309
- attribute?: string
310
- enableSystem?: boolean
311
- defaultTheme: string
312
- value?: ValueObject
313
- attrs: any
314
- skipNextHead
315
- }) => {
316
- // Code-golfing the amount of characters in the script
317
- const optimization = (() => {
318
- if (attribute === 'class') {
319
- const removeClasses = attrs.map((t: string) => `d.remove('${t}')`).join(';')
320
- return `var d=document.documentElement.classList;${removeClasses};`
321
- } else {
322
- return `var d=document.documentElement;`
323
- }
324
- })()
325
-
326
- const updateDOM = (name: string, literal?: boolean) => {
327
- name = value?.[name] || name
328
- const val = literal ? name : `'${name}'`
329
-
330
- if (attribute === 'class') {
331
- return `d.add(${val})`
332
- }
333
-
334
- return `d.setAttribute('${attribute}', ${val})`
335
- }
336
-
337
- const defaultSystem = defaultTheme === 'system'
338
-
339
- const contents = (
340
- <>
341
- {forcedTheme ? (
342
- <script
343
- key="next-themes-script"
344
- dangerouslySetInnerHTML={{
345
- // These are minified via Terser and then updated by hand, don't recommend
346
- __html: `!function(){${optimization}${updateDOM(forcedTheme)}}()`,
347
- }}
348
- />
349
- ) : enableSystem ? (
350
- <script
351
- key="next-themes-script"
352
- dangerouslySetInnerHTML={{
353
- __html: `!function(){try {${optimization}var e=localStorage.getItem('${storageKey}');${
354
- !defaultSystem ? updateDOM(defaultTheme) + ';' : ''
355
- }if("system"===e||(!e&&${defaultSystem})){var t="${MEDIA}",m=window.matchMedia(t);m.media!==t||m.matches?${updateDOM(
356
- 'dark'
357
- )}:${updateDOM('light')}}else if(e) ${
358
- value ? `var x=${JSON.stringify(value)};` : ''
359
- }${updateDOM(value ? 'x[e]' : 'e', true)}}catch(e){}}()`,
360
- }}
361
- />
362
- ) : (
363
- <script
364
- key="next-themes-script"
365
- dangerouslySetInnerHTML={{
366
- __html: `!function(){try{${optimization}var e=localStorage.getItem("${storageKey}");if(e){${
367
- value ? `var x=${JSON.stringify(value)};` : ''
368
- }${updateDOM(value ? 'x[e]' : 'e', true)}}else{${updateDOM(
369
- defaultTheme
370
- )};}}catch(t){}}();`,
371
- }}
372
- />
373
- )}
374
- </>
375
- )
376
-
377
- if (skipNextHead) return contents
378
-
379
- return <NextHead>{contents}</NextHead>
380
- },
381
- (prevProps, nextProps) => {
382
- // Only re-render when forcedTheme changes
383
- // the rest of the props should be completely stable
384
- if (prevProps.forcedTheme !== nextProps.forcedTheme) return false
385
- return true
386
- }
387
- )
388
-
389
- // Helpers
390
- const getTheme = (key: string, fallback?: string) => {
391
- if (typeof window === 'undefined') return undefined
392
- let theme
393
- try {
394
- theme = localStorage.getItem(key) || undefined
395
- } catch (e) {
396
- // Unsupported
397
- }
398
- return theme || fallback
399
- }
400
-
401
- const getSystemTheme = (e?: MediaQueryList) => {
402
- if (!e) {
403
- e = window.matchMedia(MEDIA)
404
- }
405
-
406
- const isDark = e.matches
407
- const systemTheme = isDark ? 'dark' : 'light'
408
- return systemTheme
409
- }
2
+ // forked temporarily
3
+
4
+ export * from './NextThemeProvider'
5
+ export * from './ThemeSettingContext'
6
+ export * from './UseThemeProps'
7
+ export * from './helpers'
8
+ export * from './constants'
9
+ export * from './useTheme'
10
+ export * from './types'
11
+ export * from './useRootTheme'
@@ -0,0 +1,297 @@
1
+ import { useEvent } from '@tamagui/use-event'
2
+ import NextHead from 'next/head'
3
+ import * as React from 'react'
4
+ import { memo, useEffect, useMemo, useRef, useState } from 'react'
5
+
6
+ import { MEDIA, colorSchemes } from './constants'
7
+ import { getSystemTheme, getTheme } from './helpers'
8
+ import { ThemeSettingContext } from './ThemeSettingContext'
9
+ import { ValueObject } from './types'
10
+ import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'
11
+ import { ThemeProviderProps, UseThemeProps } from './UseThemeProps'
12
+
13
+ export const NextThemeProvider: React.FC<ThemeProviderProps> = ({
14
+ forcedTheme,
15
+ disableTransitionOnChange = true,
16
+ enableSystem = true,
17
+ enableColorScheme = true,
18
+ storageKey = 'theme',
19
+ themes = colorSchemes,
20
+ defaultTheme = enableSystem ? 'system' : 'light',
21
+ attribute = 'class',
22
+ skipNextHead,
23
+ onChangeTheme,
24
+ value = {
25
+ dark: 't_dark',
26
+ light: 't_light',
27
+ },
28
+ children,
29
+ }) => {
30
+ const [theme, setThemeState] = useState(() => getTheme(storageKey, defaultTheme))
31
+ const [resolvedTheme, setResolvedTheme] = useState(() => getTheme(storageKey))
32
+ // const resolvedTheme = React.useDeferredValue(resolvedThemeFast)
33
+ const attrs = !value ? themes : Object.values(value)
34
+
35
+ const handleMediaQuery = useEvent((e?) => {
36
+ const systemTheme = getSystemTheme(e)
37
+ React.startTransition(() => {
38
+ setResolvedTheme(systemTheme)
39
+ })
40
+ if (theme === 'system' && !forcedTheme) {
41
+ handleChangeTheme(systemTheme, false)
42
+ }
43
+ })
44
+
45
+ // Ref hack to avoid adding handleMediaQuery as a dep
46
+ const mediaListener = useRef(handleMediaQuery)
47
+ mediaListener.current = handleMediaQuery
48
+
49
+ const handleChangeTheme = useEvent((theme, updateStorage = true, updateDOM = true) => {
50
+ let name = value?.[theme] || theme
51
+
52
+ if (updateStorage) {
53
+ try {
54
+ localStorage.setItem(storageKey, theme)
55
+ } catch (e) {
56
+ // Unsupported
57
+ }
58
+ }
59
+
60
+ if (theme === 'system' && enableSystem) {
61
+ const resolved = getSystemTheme()
62
+ name = value?.[resolved] || resolved
63
+ }
64
+
65
+ onChangeTheme?.(name.replace('t_', ''))
66
+
67
+ if (updateDOM) {
68
+ const d = document.documentElement
69
+ if (attribute === 'class') {
70
+ d.classList.remove(...attrs)
71
+ d.classList.add(name)
72
+ } else {
73
+ d.setAttribute(attribute, name)
74
+ }
75
+ }
76
+ })
77
+
78
+ useIsomorphicLayoutEffect(() => {
79
+ const handler = (...args: any) => mediaListener.current(...args)
80
+ // Always listen to System preference
81
+ const media = window.matchMedia(MEDIA)
82
+ // Intentionally use deprecated listener methods to support iOS & old browsers
83
+ media.addListener(handler)
84
+ handler(media)
85
+ return () => {
86
+ media.removeListener(handler)
87
+ }
88
+ }, [])
89
+
90
+ const set = useEvent((newTheme) => {
91
+ if (forcedTheme) {
92
+ handleChangeTheme(newTheme, true, false)
93
+ } else {
94
+ handleChangeTheme(newTheme)
95
+ }
96
+ setThemeState(newTheme)
97
+ })
98
+
99
+ // localStorage event handling
100
+ useEffect(() => {
101
+ const handleStorage = (e: StorageEvent) => {
102
+ if (e.key !== storageKey) {
103
+ return
104
+ }
105
+ // If default theme set, use it if localstorage === null (happens on local storage manual deletion)
106
+ const theme = e.newValue || defaultTheme
107
+ set(theme)
108
+ }
109
+ window.addEventListener('storage', handleStorage)
110
+ return () => {
111
+ window.removeEventListener('storage', handleStorage)
112
+ }
113
+ }, [defaultTheme, set, storageKey])
114
+
115
+ // color-scheme handling
116
+ useIsomorphicLayoutEffect(() => {
117
+ if (!enableColorScheme) return
118
+
119
+ const colorScheme =
120
+ // If theme is forced to light or dark, use that
121
+ forcedTheme && colorSchemes.includes(forcedTheme)
122
+ ? forcedTheme
123
+ : // If regular theme is light or dark
124
+ theme && colorSchemes.includes(theme)
125
+ ? theme
126
+ : // If theme is system, use the resolved version
127
+ theme === 'system'
128
+ ? resolvedTheme || null
129
+ : null
130
+
131
+ // color-scheme tells browser how to render built-in elements like forms, scrollbars, etc.
132
+ // if color-scheme is null, this will remove the property
133
+ const userPrefers =
134
+ typeof window !== 'undefined' &&
135
+ window.matchMedia &&
136
+ window.matchMedia('(prefers-color-scheme: dark)').matches
137
+ ? 'dark'
138
+ : 'light'
139
+
140
+ const wePrefer = colorScheme || 'light'
141
+
142
+ // avoid running this because it causes full page reflow
143
+ if (userPrefers !== wePrefer) {
144
+ document.documentElement.style.setProperty('color-scheme', colorScheme)
145
+ }
146
+ }, [enableColorScheme, theme, resolvedTheme, forcedTheme])
147
+
148
+ const toggle = useEvent(() => {
149
+ const order =
150
+ resolvedTheme === 'dark' ? ['system', 'light', 'dark'] : ['system', 'dark', 'light']
151
+ const next = order[(order.indexOf(theme) + 1) % order.length]
152
+ set(next)
153
+ })
154
+
155
+ const contextResolvedTheme = theme === 'system' ? resolvedTheme : theme
156
+ const systemTheme = (enableSystem ? resolvedTheme : undefined) as
157
+ | 'light'
158
+ | 'dark'
159
+ | undefined
160
+ const contextValue = useMemo(() => {
161
+ const value: UseThemeProps = {
162
+ theme,
163
+ current: theme,
164
+ set,
165
+ toggle,
166
+ forcedTheme,
167
+ resolvedTheme: contextResolvedTheme,
168
+ themes: enableSystem ? [...themes, 'system'] : themes,
169
+ systemTheme,
170
+ } as const
171
+ return value
172
+ }, [
173
+ theme,
174
+ set,
175
+ toggle,
176
+ forcedTheme,
177
+ contextResolvedTheme,
178
+ enableSystem,
179
+ themes,
180
+ systemTheme,
181
+ ])
182
+
183
+ return (
184
+ <ThemeSettingContext.Provider value={contextValue}>
185
+ <ThemeScript
186
+ {...{
187
+ forcedTheme,
188
+ storageKey,
189
+ systemTheme: resolvedTheme,
190
+ attribute,
191
+ value,
192
+ enableSystem,
193
+ defaultTheme,
194
+ attrs,
195
+ skipNextHead,
196
+ }}
197
+ />
198
+ {/* because on SSR we re-run and can avoid whole tree re-render */}
199
+ {useMemo(() => children, [children])}
200
+ </ThemeSettingContext.Provider>
201
+ )
202
+ }
203
+ const ThemeScript = memo(
204
+ ({
205
+ forcedTheme,
206
+ storageKey,
207
+ attribute,
208
+ enableSystem,
209
+ defaultTheme,
210
+ value,
211
+ attrs,
212
+ skipNextHead,
213
+ }: {
214
+ forcedTheme?: string
215
+ storageKey: string
216
+ attribute?: string
217
+ enableSystem?: boolean
218
+ defaultTheme: string
219
+ value?: ValueObject
220
+ attrs: any
221
+ skipNextHead?: boolean
222
+ }) => {
223
+ // Code-golfing the amount of characters in the script
224
+ const optimization = (() => {
225
+ if (attribute === 'class') {
226
+ const removeClasses = attrs.map((t: string) => `d.remove('${t}')`).join(';')
227
+ return `var d=document.documentElement.classList;${removeClasses};`
228
+ } else {
229
+ return `var d=document.documentElement;`
230
+ }
231
+ })()
232
+
233
+ const updateDOM = (name: string, literal?: boolean) => {
234
+ name = value?.[name] || name
235
+ const val = literal ? name : `'${name}'`
236
+
237
+ if (attribute === 'class') {
238
+ return `d.add(${val})`
239
+ }
240
+
241
+ return `d.setAttribute('${attribute}', ${val})`
242
+ }
243
+
244
+ const defaultSystem = defaultTheme === 'system'
245
+
246
+ const contents = (
247
+ <>
248
+ {forcedTheme ? (
249
+ <script
250
+ // nonce={nonce}
251
+ key="next-themes-script"
252
+ dangerouslySetInnerHTML={{
253
+ // These are minified via Terser and then updated by hand, don't recommend
254
+ __html: `!function(){${optimization}${updateDOM(forcedTheme)}}()`,
255
+ }}
256
+ />
257
+ ) : enableSystem ? (
258
+ <script
259
+ // nonce={nonce}
260
+ key="next-themes-script"
261
+ dangerouslySetInnerHTML={{
262
+ __html: `!function(){try {${optimization}var e=localStorage.getItem('${storageKey}');${
263
+ !defaultSystem ? updateDOM(defaultTheme) + ';' : ''
264
+ }if("system"===e||(!e&&${defaultSystem})){var t="${MEDIA}",m=window.matchMedia(t);m.media!==t||m.matches?${updateDOM(
265
+ 'dark'
266
+ )}:${updateDOM('light')}}else if(e) ${
267
+ value ? `var x=${JSON.stringify(value)};` : ''
268
+ }${updateDOM(value ? 'x[e]' : 'e', true)}}catch(e){}}()`,
269
+ }}
270
+ />
271
+ ) : (
272
+ <script
273
+ // nonce={nonce}
274
+ key="next-themes-script"
275
+ dangerouslySetInnerHTML={{
276
+ __html: `!function(){try{${optimization}var e=localStorage.getItem("${storageKey}");if(e){${
277
+ value ? `var x=${JSON.stringify(value)};` : ''
278
+ }${updateDOM(value ? 'x[e]' : 'e', true)}}else{${updateDOM(
279
+ defaultTheme
280
+ )};}}catch(t){}}();`,
281
+ }}
282
+ />
283
+ )}
284
+ </>
285
+ )
286
+
287
+ if (skipNextHead) return contents
288
+
289
+ return <NextHead>{contents}</NextHead>
290
+ },
291
+ (prevProps, nextProps) => {
292
+ // Only re-render when forcedTheme changes
293
+ // the rest of the props should be completely stable
294
+ if (prevProps.forcedTheme !== nextProps.forcedTheme) return false
295
+ return true
296
+ }
297
+ )