1+ /* eslint-disable @typescript-eslint/no-empty-function */
12/* eslint-disable react/no-danger */
23/* eslint-env browser */
34import * as React from 'react'
@@ -6,28 +7,51 @@ import {
67 toCustomPropertiesReferences ,
78} from './customProperties'
89
9- type Mode = string | null
10- type ColorModeState = [ Mode , ( mode : Mode ) => void ]
10+ type ColorModeState = [ string | null , ( mode : string | null ) => void ]
11+ type Color = string | ( ( props : Record < string , unknown > ) => Color )
12+ type Colors = Record < string , Color >
13+
14+ interface ITheme {
15+ useCustomProperties ?: boolean
16+ useColorSchemeMediaQuery ?: boolean
17+ initialColorModeName ?: string
18+ defaultColorModeName ?: string
19+ colors ?: Colors & {
20+ modes ?: Record < string , Colors >
21+ }
22+ }
23+
24+ interface IColorModeTheme extends ITheme {
25+ colors : Colors & { modes : Record < string , Colors > }
26+ }
1127
1228const STORAGE_KEY = 'xstyled-color-mode'
1329
14- const isLocalStorageAvailable =
30+ const isLocalStorageAvailable : boolean =
1531 typeof window !== 'undefined' &&
1632 ( ( ) => {
1733 try {
18- const STORAGE_TEST_KEY = ` ${ STORAGE_KEY } -test`
19- window . localStorage . setItem ( STORAGE_TEST_KEY , STORAGE_TEST_KEY )
20- window . localStorage . removeItem ( STORAGE_TEST_KEY )
34+ const key = 'xstyled -test-key'
35+ window . localStorage . setItem ( key , key )
36+ window . localStorage . removeItem ( key )
2137 return true
2238 } catch ( err ) {
2339 return false
2440 }
2541 } ) ( )
2642
27- const storage = isLocalStorageAvailable
43+ interface Storage {
44+ get ( ) : string | null
45+ set ( value : string ) : void
46+ clear ( ) : void
47+ }
48+
49+ const storage : Storage = isLocalStorageAvailable
2850 ? {
2951 get : ( ) => window . localStorage . getItem ( STORAGE_KEY ) ,
30- set : ( value : string ) => window . localStorage . setItem ( STORAGE_KEY , value ) ,
52+ set : ( value : string ) => {
53+ window . localStorage . setItem ( STORAGE_KEY , value )
54+ } ,
3155 clear : ( ) => window . localStorage . removeItem ( STORAGE_KEY ) ,
3256 }
3357 : {
@@ -43,82 +67,76 @@ const getColorModeClassName = (mode: string) =>
4367const XSTYLED_COLORS_PREFIX = 'xstyled-colors'
4468const SYSTEM_MODES = [ 'light' , 'dark' ]
4569
46- interface Theme {
47- useCustomProperties ?: boolean
48- useColorSchemeMediaQuery ?: boolean
49- initialColorModeName ?: string
50- defaultColorModeName ?: string
51- colors ?: {
52- modes ?: {
53- [ key : string ] : any
54- }
55- }
56- }
57-
58- interface ModeTheme extends Theme {
59- colors : {
60- modes : {
61- [ key : string ] : any
62- }
63- }
64- }
65-
66- function getModeTheme ( theme : ModeTheme , mode : string ) {
70+ function getModeTheme ( theme : IColorModeTheme , mode : string ) : IColorModeTheme {
6771 return {
6872 ...theme ,
6973 colors : { ...theme . colors , ...theme . colors . modes [ mode ] } ,
7074 }
7175}
7276
73- const getMediaQuery = ( query : string ) => `@media ${ query } `
74- const getColorModeQuery = ( mode : string ) => `(prefers-color-scheme: ${ mode } )`
77+ const getMediaQuery = ( query : string ) : string => `@media ${ query } `
78+ const getColorModeQuery = ( mode : string ) : string =>
79+ `(prefers-color-scheme: ${ mode } )`
7580
76- function hasColorModes ( theme : Theme ) : theme is ModeTheme {
81+ function checkHasColorModes ( theme : ITheme | null ) : theme is IColorModeTheme {
7782 return Boolean ( theme && theme . colors && theme . colors . modes )
7883}
7984
80- function hasCustomPropertiesEnabled ( theme : Theme ) {
81- return (
85+ function checkHasCustomPropertiesEnabled ( theme : ITheme | null ) : boolean {
86+ return Boolean (
8287 theme &&
83- ( theme . useCustomProperties === undefined || theme . useCustomProperties )
88+ ( theme . useCustomProperties === undefined || theme . useCustomProperties ) ,
8489 )
8590}
8691
87- function hasMediaQueryEnabled ( theme : Theme ) {
88- return (
92+ function checkHasMediaQueryEnabled ( theme : ITheme | null ) : boolean {
93+ return Boolean (
8994 theme &&
90- ( theme . useColorSchemeMediaQuery === undefined ||
91- theme . useColorSchemeMediaQuery )
95+ ( theme . useColorSchemeMediaQuery === undefined ||
96+ theme . useColorSchemeMediaQuery ) ,
9297 )
9398}
9499
95- function getInitialColorModeName ( theme : Theme ) {
100+ function getInitialColorModeName ( theme : ITheme ) : string {
96101 return theme . initialColorModeName || 'default'
97102}
98103
99- function getDefaultColorModeName ( theme : Theme ) {
104+ function getDefaultColorModeName ( theme : ITheme ) : string {
100105 return theme . defaultColorModeName || getInitialColorModeName ( theme )
101106}
102107
108+ function getUsedColorKeys ( modes : Record < string , Record < string , Color > > ) {
109+ let keys : string [ ] = [ ]
110+ for ( const key in modes ) {
111+ keys = [ ...keys , ...Object . keys ( modes [ key ] ) ]
112+ }
113+ return keys
114+ }
115+
103116export function createColorStyles (
104- theme : Theme ,
117+ theme : ITheme ,
105118 { targetSelector = 'body' } = { } ,
106- ) {
107- if ( ! hasColorModes ( theme ) ) return null
119+ ) : string | null {
120+ if ( ! checkHasColorModes ( theme ) ) return null
121+
108122 const { modes, ...colors } = theme . colors
123+ const colorKeys = getUsedColorKeys ( modes )
124+
109125 let styles = toCustomPropertiesDeclarations (
110126 colors ,
111- XSTYLED_COLORS_PREFIX ,
112127 theme ,
128+ colorKeys ,
129+ XSTYLED_COLORS_PREFIX ,
113130 )
114131
115132 function getModePropertiesDeclarations ( mode : string ) {
116- const modeTheme = getModeTheme ( theme as ModeTheme , mode )
133+ const modeTheme = getModeTheme ( theme as IColorModeTheme , mode )
117134 const { modes, ...colors } = modeTheme . colors
118135 return toCustomPropertiesDeclarations (
119136 { ...colors , ...modes [ mode ] } ,
120- XSTYLED_COLORS_PREFIX ,
121137 modeTheme ,
138+ colorKeys ,
139+ XSTYLED_COLORS_PREFIX ,
122140 )
123141 }
124142
@@ -148,10 +166,11 @@ function getSystemModeMql(mode: string) {
148166 return window . matchMedia ( query )
149167}
150168
151- function useSystemMode ( theme : ModeTheme ) {
169+ function useSystemMode ( theme : ITheme ) {
152170 const configs : { mode : string ; mql : MediaQueryList } [ ] = React . useMemo ( ( ) => {
153- if ( ! hasMediaQueryEnabled ( theme ) ) return [ ]
171+ if ( ! checkHasMediaQueryEnabled ( theme ) ) return [ ]
154172 return SYSTEM_MODES . map ( ( mode ) => {
173+ if ( ! checkHasColorModes ( theme ) ) return null
155174 if ( ! theme . colors . modes [ mode ] ) return null
156175 const mql = getSystemModeMql ( mode )
157176 return mql ? { mode, mql } : null
@@ -189,19 +208,19 @@ const useIsomorphicLayoutEffect =
189208 typeof window !== 'undefined' ? React . useLayoutEffect : React . useEffect
190209
191210export function useColorModeState (
192- theme : ModeTheme ,
211+ theme : ITheme ,
193212 { target } : { target ?: Element } = { } ,
194213) : ColorModeState {
195214 const systemMode = useSystemMode ( theme )
196215 const defaultColorMode = getDefaultColorModeName ( theme )
197216 const initialColorMode = getInitialColorModeName ( theme )
198217 const [ mode , setMode ] = React . useState ( ( ) => {
199- if ( ! hasColorModes ( theme ) ) return null
218+ if ( ! checkHasColorModes ( theme ) ) return null
200219 return defaultColorMode
201220 } )
202221
203222 // Add mode className
204- const customPropertiesEnabled = hasCustomPropertiesEnabled ( theme )
223+ const customPropertiesEnabled = checkHasCustomPropertiesEnabled ( theme )
205224
206225 const manualSetRef = React . useRef ( false )
207226 const manuallySetMode = React . useCallback ( ( value ) => {
@@ -211,7 +230,7 @@ export function useColorModeState(
211230
212231 // Set initial color mode in lazy
213232 useIsomorphicLayoutEffect ( ( ) => {
214- if ( ! hasColorModes ( theme ) ) return
233+ if ( ! checkHasColorModes ( theme ) ) return
215234 const storedMode = storage . get ( )
216235 const initialMode = storedMode || systemMode || defaultColorMode
217236 if ( mode !== initialMode ) {
@@ -257,27 +276,38 @@ export function useColorModeState(
257276 return [ mode , manuallySetMode ]
258277}
259278
260- export function useColorModeTheme ( theme : any , mode : Mode ) {
279+ export function useColorModeTheme (
280+ theme : ITheme ,
281+ mode : string | null ,
282+ ) : ITheme | null {
283+ const [ initialMode ] = React . useState ( mode )
261284 const customPropertiesTheme = React . useMemo ( ( ) => {
262- if ( ! mode ) return null
263- if ( ! hasCustomPropertiesEnabled ( theme ) ) return null
264- if ( ! hasColorModes ( theme ) ) return theme
265-
285+ if ( ! initialMode ) return null
286+ if ( ! checkHasCustomPropertiesEnabled ( theme ) ) return null
287+ if ( ! checkHasColorModes ( theme ) ) return theme
266288 const { modes, ...colors } = theme . colors
289+ const colorKeys = getUsedColorKeys ( modes )
290+
267291 return {
268292 ...theme ,
269293 colors : {
270- ...toCustomPropertiesReferences ( colors , XSTYLED_COLORS_PREFIX , theme ) ,
294+ ...colors ,
295+ ...toCustomPropertiesReferences (
296+ colors ,
297+ theme ,
298+ colorKeys ,
299+ XSTYLED_COLORS_PREFIX ,
300+ ) ,
271301 modes,
272302 } ,
273303 __rawColors : theme . colors ,
274304 }
275- } , [ theme ] )
305+ } , [ initialMode , theme ] )
276306
277307 const swapModeTheme = React . useMemo ( ( ) => {
278308 if ( ! mode ) return null
279- if ( hasCustomPropertiesEnabled ( theme ) ) return null
280- if ( ! hasColorModes ( theme ) ) return theme
309+ if ( checkHasCustomPropertiesEnabled ( theme ) ) return null
310+ if ( ! checkHasColorModes ( theme ) ) return theme
281311
282312 if ( mode === getInitialColorModeName ( theme ) ) {
283313 return { ...theme , __colorMode : mode }
@@ -294,12 +324,12 @@ export function useColorModeTheme(theme: any, mode: Mode) {
294324 }
295325 } , [ theme , mode ] )
296326
297- return customPropertiesTheme || swapModeTheme
327+ return ( customPropertiesTheme || swapModeTheme ) as ITheme
298328}
299329
300330export const ColorModeContext = React . createContext < ColorModeState | null > ( null )
301331
302- export function useColorMode ( ) {
332+ export function useColorMode ( ) : ColorModeState {
303333 const colorModeState = React . useContext ( ColorModeContext )
304334
305335 if ( ! colorModeState ) {
@@ -309,6 +339,12 @@ export function useColorMode() {
309339 return colorModeState
310340}
311341
342+ export interface ColorModeProviderProps {
343+ children : React . ReactNode
344+ target ?: Element
345+ targetSelector ?: string
346+ }
347+
312348export function createColorModeProvider ( {
313349 ThemeContext,
314350 ThemeProvider,
@@ -317,16 +353,12 @@ export function createColorModeProvider({
317353 ThemeContext : React . Context < any >
318354 ThemeProvider : React . ComponentType < any >
319355 ColorModeStyle : React . ComponentType < any >
320- } ) {
356+ } ) : React . FC < ColorModeProviderProps > {
321357 function ColorModeProvider ( {
322358 children,
323359 target,
324360 targetSelector,
325- } : {
326- children : React . ReactNode
327- target ?: Element
328- targetSelector ?: string
329- } ) {
361+ } : ColorModeProviderProps ) {
330362 const theme = React . useContext ( ThemeContext )
331363 if ( ! theme ) {
332364 throw new Error (
@@ -362,7 +394,9 @@ function getInitScript({
362394 } catch (e) {} })();`
363395}
364396
365- export function getColorModeInitScriptElement ( options ?: GetInitScriptOptions ) {
397+ export function getColorModeInitScriptElement (
398+ options ?: GetInitScriptOptions ,
399+ ) : JSX . Element {
366400 return (
367401 < script
368402 key = "xstyled-color-mode-init"
@@ -371,6 +405,8 @@ export function getColorModeInitScriptElement(options?: GetInitScriptOptions) {
371405 )
372406}
373407
374- export function getColorModeInitScriptTag ( options ?: GetInitScriptOptions ) {
408+ export function getColorModeInitScriptTag (
409+ options ?: GetInitScriptOptions ,
410+ ) : string {
375411 return `<script>${ getInitScript ( options ) } </script>`
376412}
0 commit comments