@codeleap/styles 6.2.3 → 6.8.0
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.
- package/dist/classes/Cacher.d.ts +87 -0
- package/dist/classes/Cacher.d.ts.map +1 -0
- package/dist/classes/StaleControl.d.ts +65 -0
- package/dist/classes/StaleControl.d.ts.map +1 -0
- package/dist/classes/StyleCache.d.ts +63 -0
- package/dist/classes/StyleCache.d.ts.map +1 -0
- package/dist/classes/StylePersistor.d.ts +52 -0
- package/dist/classes/StylePersistor.d.ts.map +1 -0
- package/dist/classes/StyleRegistry.d.ts +108 -0
- package/dist/classes/StyleRegistry.d.ts.map +1 -0
- package/dist/classes/index.d.ts +3 -0
- package/dist/classes/index.d.ts.map +1 -0
- package/dist/constants.d.ts +22 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useCompositionStyles.d.ts +12 -0
- package/dist/hooks/useCompositionStyles.d.ts.map +1 -0
- package/dist/hooks/useNestedStylesByKey.d.ts +11 -0
- package/dist/hooks/useNestedStylesByKey.d.ts.map +1 -0
- package/dist/hooks/useStyleObserver.d.ts +9 -0
- package/dist/hooks/useStyleObserver.d.ts.map +1 -0
- package/dist/hooks/useTheme.d.ts +19 -0
- package/dist/hooks/useTheme.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/calc.d.ts +27 -0
- package/dist/lib/calc.d.ts.map +1 -0
- package/dist/lib/createStyles.d.ts +30 -0
- package/dist/lib/createStyles.d.ts.map +1 -0
- package/dist/lib/createTheme.d.ts +28 -0
- package/dist/lib/createTheme.d.ts.map +1 -0
- package/dist/lib/cssVariables.d.ts +35 -0
- package/dist/lib/cssVariables.d.ts.map +1 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/theme/generateColorScheme.d.ts +22 -0
- package/dist/theme/generateColorScheme.d.ts.map +1 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/themeStore.d.ts +106 -0
- package/dist/theme/themeStore.d.ts.map +1 -0
- package/dist/theme/validateTheme.d.ts +19 -0
- package/dist/theme/validateTheme.d.ts.map +1 -0
- package/dist/tools/colors.d.ts +70 -0
- package/dist/tools/colors.d.ts.map +1 -0
- package/dist/tools/deepClone.d.ts +7 -0
- package/dist/tools/deepClone.d.ts.map +1 -0
- package/dist/tools/deepmerge.d.ts +13 -0
- package/dist/tools/deepmerge.d.ts.map +1 -0
- package/dist/tools/hashKey.d.ts +8 -0
- package/dist/tools/hashKey.d.ts.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/minifier.d.ts +24 -0
- package/dist/tools/minifier.d.ts.map +1 -0
- package/dist/tools/multiplierProperty.d.ts +4 -0
- package/dist/tools/multiplierProperty.d.ts.map +1 -0
- package/dist/types/cache.d.ts +12 -0
- package/dist/types/cache.d.ts.map +1 -0
- package/dist/types/component.d.ts +58 -0
- package/dist/types/component.d.ts.map +1 -0
- package/dist/types/core.d.ts +77 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/icon.d.ts +15 -0
- package/dist/types/icon.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/spacing.d.ts +28 -0
- package/dist/types/spacing.d.ts.map +1 -0
- package/dist/types/store.d.ts +12 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/style.d.ts +42 -0
- package/dist/types/style.d.ts.map +1 -0
- package/dist/types/theme.d.ts +109 -0
- package/dist/types/theme.d.ts.map +1 -0
- package/dist/utils.d.ts +40 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/variants/borderCreator.d.ts +22 -0
- package/dist/variants/borderCreator.d.ts.map +1 -0
- package/dist/variants/createAppVariants.d.ts +18 -0
- package/dist/variants/createAppVariants.d.ts.map +1 -0
- package/dist/variants/defaultVariants.d.ts +140 -0
- package/dist/variants/defaultVariants.d.ts.map +1 -0
- package/dist/variants/dynamicVariants.d.ts +43 -0
- package/dist/variants/dynamicVariants.d.ts.map +1 -0
- package/dist/variants/index.d.ts +7 -0
- package/dist/variants/index.d.ts.map +1 -0
- package/dist/variants/mediaQuery.d.ts +30 -0
- package/dist/variants/mediaQuery.d.ts.map +1 -0
- package/dist/variants/spacing.d.ts +26 -0
- package/dist/variants/spacing.d.ts.map +1 -0
- package/package.json +19 -5
- package/src/classes/Cacher.ts +9 -9
- package/src/classes/StaleControl.ts +1 -1
- package/src/classes/StyleCache.ts +9 -3
- package/src/classes/StylePersistor.ts +11 -0
- package/src/classes/StyleRegistry.ts +124 -43
- package/src/classes/tests/StyleRegistry.spec.ts +169 -0
- package/src/constants.ts +14 -0
- package/src/hooks/useCompositionStyles.ts +9 -7
- package/src/hooks/useNestedStylesByKey.ts +8 -0
- package/src/hooks/useStyleObserver.ts +6 -5
- package/src/hooks/useTheme.ts +14 -0
- package/src/lib/calc.ts +13 -0
- package/src/lib/createStyles.ts +35 -4
- package/src/lib/createTheme.ts +74 -25
- package/src/lib/cssVariables.ts +74 -0
- package/src/lib/index.ts +2 -1
- package/src/lib/tests/createStyles.spec.ts +80 -23
- package/src/lib/tests/createStylesWithContext.spec.ts +108 -0
- package/src/tests/theme.ts +6 -2
- package/src/theme/generateColorScheme.ts +13 -10
- package/src/theme/tests/themeStore.spec.ts +72 -71
- package/src/theme/themeStore.ts +10 -7
- package/src/theme/validateTheme.ts +1 -1
- package/src/tools/colors.ts +24 -36
- package/src/tools/deepClone.ts +3 -5
- package/src/tools/deepmerge.ts +8 -6
- package/src/tools/hashKey.ts +4 -5
- package/src/tools/minifier.ts +11 -12
- package/src/tools/tests/deepClone.spec.ts +2 -2
- package/src/types/cache.ts +10 -0
- package/src/types/component.ts +41 -5
- package/src/types/core.ts +66 -6
- package/src/types/icon.ts +11 -0
- package/src/types/spacing.ts +21 -0
- package/src/types/store.ts +6 -0
- package/src/types/style.ts +37 -10
- package/src/types/theme.ts +37 -1
- package/src/utils.ts +34 -4
- package/src/variants/borderCreator.ts +14 -5
- package/src/variants/createAppVariants.ts +11 -0
- package/src/variants/defaultVariants.ts +28 -8
- package/src/variants/dynamicVariants.ts +76 -18
- package/src/variants/mediaQuery.ts +18 -0
- package/src/variants/spacing.ts +15 -1
- package/package.json.bak +0 -30
|
@@ -2,6 +2,14 @@ import { useMemo } from 'react'
|
|
|
2
2
|
import { ICSS } from '../types'
|
|
3
3
|
import { getNestedStylesByKey } from '../utils'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Memoised wrapper around `getNestedStylesByKey`. Returns the sub-record of
|
|
7
|
+
* `componentStyles` whose keys start with `match`, with the prefix stripped and the
|
|
8
|
+
* remainder lowercased. Re-computes only when `componentStyles` changes by reference.
|
|
9
|
+
*
|
|
10
|
+
* Use instead of `useCompositionStyles` when you need the slice for a single element
|
|
11
|
+
* and don't want the overhead of iterating over an array.
|
|
12
|
+
*/
|
|
5
13
|
export function useNestedStylesByKey<T extends string>(match: string, componentStyles: Partial<Record<T, ICSS>>) {
|
|
6
14
|
const styles = {
|
|
7
15
|
...componentStyles
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Produces a stable string that changes only when the style value changes, suitable
|
|
5
|
+
* for use as a `useEffect`/`useMemo` dependency when the style prop is an object or
|
|
6
|
+
* array (which would otherwise trigger on every render due to referential inequality).
|
|
7
|
+
* Falsy entries are stripped from arrays before serialisation, so `[null, style]` and
|
|
8
|
+
* `[style]` yield the same string.
|
|
8
9
|
*/
|
|
9
|
-
export const useStyleObserver = (style) => {
|
|
10
|
+
export const useStyleObserver = (style: any) => {
|
|
10
11
|
return useMemo(() => {
|
|
11
12
|
if (Array.isArray(style)) {
|
|
12
13
|
return JSON.stringify(style?.filter(v => !!v))
|
package/src/hooks/useTheme.ts
CHANGED
|
@@ -3,6 +3,20 @@ import { useStore } from '@nanostores/react'
|
|
|
3
3
|
|
|
4
4
|
type ThemeSelector<T> = (state: ThemeState) => T
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Subscribes to the global theme state and returns either the full `ThemeState`
|
|
8
|
+
* (when called without arguments) or a derived value (when called with a selector).
|
|
9
|
+
* Uses nanostores' `useStore` under the hood, so re-renders are triggered only when
|
|
10
|
+
* the selected slice changes by reference.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Full state
|
|
14
|
+
* const { theme, colorScheme } = useTheme()
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Derived slice — component only re-renders when colorScheme changes
|
|
18
|
+
* const colorScheme = useTheme(s => s.colorScheme)
|
|
19
|
+
*/
|
|
6
20
|
export const useTheme = <T = ThemeState>(
|
|
7
21
|
selector?: ThemeSelector<T>
|
|
8
22
|
): T => {
|
package/src/lib/calc.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
type Unit = 'px' | 'vh' | 'dvh' | 'vw' | 'dvw' | '%' | 'lvh' | 'svh'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Chainable builder for CSS `calc()` expressions. Each arithmetic method appends
|
|
5
|
+
* to the expression and returns `this`, so calls can be chained. Call `build()`
|
|
6
|
+
* at the end to get the final `calc(...)` string.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* calc(100, '%').sub(16).build() // → 'calc((100%) - (16px))'
|
|
10
|
+
*/
|
|
3
11
|
class CalcBuilder {
|
|
4
12
|
private expression: string
|
|
5
13
|
|
|
@@ -38,4 +46,9 @@ class CalcBuilder {
|
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Entry point for building CSS `calc()` expressions. Pass an initial numeric value
|
|
51
|
+
* and optional unit (default `'px'`), then chain `.add()`, `.sub()`, `.mult()`, `.div()`,
|
|
52
|
+
* and finally call `.build()` to get the complete string.
|
|
53
|
+
*/
|
|
41
54
|
export const calc = (base: number, unit: Unit = 'px') => new CalcBuilder(base, unit)
|
package/src/lib/createStyles.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { AnyRecord, ICSS, ITheme } from '../types'
|
|
1
|
+
import { AnyRecord, ComponentContext, ICSS, ITheme } from '../types'
|
|
2
2
|
import { themeStore } from '../theme'
|
|
3
3
|
|
|
4
4
|
type Value = AnyRecord
|
|
5
5
|
|
|
6
6
|
type StylesShape<K extends string, V extends Value> = Partial<Record<K, ICSS & Partial<Omit<V, keyof ICSS>>>>
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Symbol used to attach a context-aware factory function to a styles proxy.
|
|
10
|
+
* `CodeleapStyleRegistry.registerVariants` reads this symbol from each entry to
|
|
11
|
+
* identify which variants require a `ComponentContext` at resolution time.
|
|
12
|
+
* Not intended for direct use by consumers.
|
|
13
|
+
*/
|
|
14
|
+
export const CONTEXT_FACTORY_SYMBOL = Symbol('contextFactory')
|
|
15
|
+
|
|
8
16
|
/**
|
|
9
17
|
* Creates a reactive styles object that automatically updates when theme changes.
|
|
10
18
|
* Uses a proxy to re-compute styles on each access, ensuring theme changes are reflected.
|
|
11
|
-
*
|
|
19
|
+
*
|
|
12
20
|
* @template K - Style keys (extends string)
|
|
13
21
|
* @template V - Additional value type (extends AnyRecord)
|
|
14
22
|
* @param {StylesShape<K, V> | ((theme: ITheme) => StylesShape<K, V>)} styles - Static styles object or function that receives theme
|
|
@@ -26,14 +34,37 @@ export function createStyles<K extends string, V extends Value = {}>(
|
|
|
26
34
|
return styles
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
|
-
|
|
37
|
+
|
|
30
38
|
// We use a proxy here so that the color scheme is recomputed every time the
|
|
31
39
|
// theme changes. This is necessary because the theme is a singleton which does not cause
|
|
32
40
|
// a re-render when it changes. The end-user will only have to worry about remounting the root component
|
|
33
41
|
// when the theme changes in order to get the new color scheme due to this proxy.
|
|
34
42
|
return new Proxy(compute() as StylesShape<K, V>, {
|
|
35
43
|
get(target, prop) {
|
|
36
|
-
return compute()[prop as string]
|
|
44
|
+
return (compute() as Record<string, any>)[prop as string]
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Like `createStyles`, but the factory also receives a `ComponentContext` so that
|
|
51
|
+
* individual style values can vary based on runtime boolean/numeric state (e.g.,
|
|
52
|
+
* `isDisabled`, `isSelected`). The resulting proxy exposes a `CONTEXT_FACTORY_SYMBOL`
|
|
53
|
+
* getter so the registry can invoke the factory with the actual context at render time.
|
|
54
|
+
* Use this instead of `createStyles` when variant styles depend on component state.
|
|
55
|
+
*/
|
|
56
|
+
export function createStylesWithContext<K extends string, V extends Value = {}, C extends ComponentContext = {}>(
|
|
57
|
+
styles: (theme: ITheme, context: C) => StylesShape<K, V>,
|
|
58
|
+
) {
|
|
59
|
+
const compute = (context: C = {} as C) => {
|
|
60
|
+
const current = themeStore.theme
|
|
61
|
+
return !current ? {} as StylesShape<K, V> : styles(current, context)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return new Proxy(compute() as StylesShape<K, V>, {
|
|
65
|
+
get(target, prop) {
|
|
66
|
+
if (prop === CONTEXT_FACTORY_SYMBOL) return styles
|
|
67
|
+
return (compute() as Record<string, any>)[prop as string]
|
|
37
68
|
},
|
|
38
69
|
})
|
|
39
70
|
}
|
package/src/lib/createTheme.ts
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
import { AppTheme, Theme } from '../types'
|
|
1
|
+
import { AppTheme, Theme, ITheme } from '../types'
|
|
2
2
|
import { borderCreator, createMediaQueries, defaultVariants, spacingFactory } from '../variants'
|
|
3
3
|
import { minifier, multiplierProperty } from '../tools'
|
|
4
4
|
import { themeStore } from '../theme'
|
|
5
|
+
import { applyColorSchemeToDOM, buildCssVarProxy, flattenColorMap, DOM_COLOR_SCHEME_KEY } from './cssVariables'
|
|
5
6
|
|
|
6
7
|
type ThemePersistor = {
|
|
7
8
|
get: (name: string) => any
|
|
8
9
|
set: (name: string, value: any) => void
|
|
10
|
+
getNoCompress?: (name: string) => any
|
|
11
|
+
setNoCompress?: (name: string, value: any) => void
|
|
12
|
+
getSystemColorScheme?: () => string | null
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
const colorSchemeKey = '@styles.theme.colorScheme'
|
|
12
16
|
const alternateColorsKey = '@styles.theme.alternateColors'
|
|
13
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Builds and registers the runtime `AppTheme<T>` object from a raw `Theme` definition.
|
|
20
|
+
*
|
|
21
|
+
* Key behaviours:
|
|
22
|
+
* - On web (`theme.isBrowser = true`), `theme.colors` is replaced with a CSS-var proxy
|
|
23
|
+
* so all color references become `var(--cl-<token>)` strings. The active scheme's
|
|
24
|
+
* real values are still accessible via `theme.currentSchemeColors`.
|
|
25
|
+
* - Persists the selected color scheme to storage and restores it on next load.
|
|
26
|
+
* Falls back to `getSystemColorScheme()` if no persisted value exists.
|
|
27
|
+
* - `alternateColors` from storage are merged with those in the theme definition
|
|
28
|
+
* (runtime-injected schemes take priority).
|
|
29
|
+
* - The constructed theme is immediately set on `themeStore` so hooks and the registry
|
|
30
|
+
* can read it synchronously during the same render pass.
|
|
31
|
+
*
|
|
32
|
+
* @param theme - Raw theme object (output of `validateTheme` or a compliant literal).
|
|
33
|
+
* @param themePersistor - Storage adapter for persisting color-scheme selection and injected schemes.
|
|
34
|
+
*/
|
|
14
35
|
export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePersistor): AppTheme<T> => {
|
|
15
36
|
const {
|
|
16
37
|
colors,
|
|
@@ -35,10 +56,19 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
|
|
|
35
56
|
},
|
|
36
57
|
set: (key: string, value: any) => {
|
|
37
58
|
return themePersistor.set(key, !value ? '' : minifier.compress(value))
|
|
38
|
-
}
|
|
59
|
+
},
|
|
60
|
+
getNoCompress: (key: string) => {
|
|
61
|
+
if (themePersistor.getNoCompress) return themePersistor.getNoCompress(key)
|
|
62
|
+
if (typeof localStorage !== 'undefined') return localStorage.getItem(key)
|
|
63
|
+
return null
|
|
64
|
+
},
|
|
65
|
+
setNoCompress: (key: string, value: any) => {
|
|
66
|
+
if (themePersistor.setNoCompress) return themePersistor.setNoCompress(key, value)
|
|
67
|
+
if (typeof localStorage !== 'undefined') localStorage.setItem(key, value)
|
|
68
|
+
},
|
|
39
69
|
}
|
|
40
70
|
|
|
41
|
-
themeStore.setColorScheme(persistor.get(colorSchemeKey) ?? 'default')
|
|
71
|
+
themeStore.setColorScheme(persistor.get(colorSchemeKey) ?? themePersistor.getSystemColorScheme?.() ?? 'default')
|
|
42
72
|
|
|
43
73
|
const persistedAlternateColors = persistor.get(alternateColorsKey)
|
|
44
74
|
|
|
@@ -49,6 +79,15 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
|
|
|
49
79
|
|
|
50
80
|
themeStore.setAlternateColorsScheme(alternateColors)
|
|
51
81
|
|
|
82
|
+
// On web: build CSS var proxy once — theme.colors returns var(--cl-X) strings.
|
|
83
|
+
// On RN (isBrowser falsy): keep raw RGBA values as before.
|
|
84
|
+
const cssVarColors = theme.isBrowser ? buildCssVarProxy(colors) : colors
|
|
85
|
+
|
|
86
|
+
// Apply persisted scheme to DOM immediately (browser-only, no-op on server/RN)
|
|
87
|
+
applyColorSchemeToDOM(persistor.getNoCompress(DOM_COLOR_SCHEME_KEY) ?? 'default')
|
|
88
|
+
|
|
89
|
+
const baseSpacing = theme.baseSpacing ?? 1
|
|
90
|
+
|
|
52
91
|
const themeObj: AppTheme<T> = {
|
|
53
92
|
...otherThemeValues,
|
|
54
93
|
|
|
@@ -64,18 +103,26 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
|
|
|
64
103
|
|
|
65
104
|
breakpoints: breakpoints ?? {},
|
|
66
105
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (colorScheme === 'default') return colors
|
|
106
|
+
// On web: var(--cl-X) strings — browser CSS handles color switching.
|
|
107
|
+
// On RN: raw RGBA values, scheme-reactive as before.
|
|
108
|
+
colors: cssVarColors as T['colors'],
|
|
71
109
|
|
|
110
|
+
// Always the active scheme's real RGBA values — use when you need the actual color in JS.
|
|
111
|
+
get currentSchemeColors(): T['colors'] {
|
|
112
|
+
const colorScheme = themeStore.colorScheme ?? 'default'
|
|
113
|
+
if (colorScheme === 'default') return colors as T['colors']
|
|
72
114
|
const scheme = themeStore.alternateColorsScheme?.[colorScheme]
|
|
73
|
-
|
|
74
115
|
if (!scheme) {
|
|
75
116
|
console.warn(`Color scheme ${colorScheme} not found in theme`)
|
|
76
117
|
}
|
|
118
|
+
return (scheme ?? colors) as T['colors']
|
|
119
|
+
},
|
|
77
120
|
|
|
78
|
-
|
|
121
|
+
getCssVariables(schemeName?: string): Record<string, string> {
|
|
122
|
+
const map = !schemeName || schemeName === 'default'
|
|
123
|
+
? colors
|
|
124
|
+
: themeStore.alternateColorsScheme?.[schemeName] ?? colors
|
|
125
|
+
return flattenColorMap(map as Record<string, any>)
|
|
79
126
|
},
|
|
80
127
|
|
|
81
128
|
setColorScheme(colorScheme: string) {
|
|
@@ -87,8 +134,10 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
|
|
|
87
134
|
}
|
|
88
135
|
|
|
89
136
|
themeStore.setColorScheme(colorScheme)
|
|
90
|
-
|
|
91
137
|
persistor.set(colorSchemeKey, colorScheme)
|
|
138
|
+
// Store uncompressed for the FOUC prevention script (lz can't run inline)
|
|
139
|
+
persistor.setNoCompress(DOM_COLOR_SCHEME_KEY, colorScheme)
|
|
140
|
+
applyColorSchemeToDOM(colorScheme)
|
|
92
141
|
},
|
|
93
142
|
|
|
94
143
|
injectColorScheme(name, colorMap) {
|
|
@@ -116,24 +165,24 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
|
|
|
116
165
|
persistor.set(alternateColorsKey, persistedAlternateColors)
|
|
117
166
|
},
|
|
118
167
|
|
|
119
|
-
baseSpacing
|
|
168
|
+
baseSpacing,
|
|
120
169
|
|
|
121
|
-
value: (n = 1) =>
|
|
170
|
+
value: (n = 1) => baseSpacing * n,
|
|
122
171
|
|
|
123
172
|
spacing: {
|
|
124
|
-
value: (n = 1) =>
|
|
125
|
-
gap: multiplierProperty(
|
|
126
|
-
...spacingFactory(
|
|
127
|
-
...spacingFactory(
|
|
128
|
-
...spacingFactory(
|
|
129
|
-
...spacingFactory(
|
|
173
|
+
value: (n = 1) => baseSpacing * n,
|
|
174
|
+
gap: multiplierProperty(baseSpacing, 'gap'),
|
|
175
|
+
...spacingFactory(baseSpacing, 'padding'),
|
|
176
|
+
...spacingFactory(baseSpacing, 'margin'),
|
|
177
|
+
...spacingFactory(baseSpacing, 'p', true),
|
|
178
|
+
...spacingFactory(baseSpacing, 'm', true),
|
|
130
179
|
},
|
|
131
180
|
|
|
132
181
|
inset: {
|
|
133
|
-
top: multiplierProperty(
|
|
134
|
-
bottom: multiplierProperty(
|
|
135
|
-
left: multiplierProperty(
|
|
136
|
-
right: multiplierProperty(
|
|
182
|
+
top: multiplierProperty(baseSpacing, 'top'),
|
|
183
|
+
bottom: multiplierProperty(baseSpacing, 'bottom'),
|
|
184
|
+
left: multiplierProperty(baseSpacing, 'left'),
|
|
185
|
+
right: multiplierProperty(baseSpacing, 'right'),
|
|
137
186
|
},
|
|
138
187
|
|
|
139
188
|
presets: {
|
|
@@ -160,16 +209,16 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
|
|
|
160
209
|
values: values ?? {},
|
|
161
210
|
|
|
162
211
|
sized: (size) => {
|
|
163
|
-
const value = typeof size == 'number' ? size *
|
|
212
|
+
const value = typeof size == 'number' ? size * baseSpacing : size
|
|
164
213
|
|
|
165
214
|
return {
|
|
166
215
|
width: value,
|
|
167
216
|
height: value,
|
|
168
217
|
}
|
|
169
218
|
},
|
|
170
|
-
}
|
|
219
|
+
} as AppTheme<T>
|
|
171
220
|
|
|
172
|
-
themeStore.setTheme(themeObj)
|
|
221
|
+
themeStore.setTheme(themeObj as unknown as ITheme)
|
|
173
222
|
|
|
174
223
|
return themeObj
|
|
175
224
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS custom-property prefix used for all codeleap design-token variables.
|
|
3
|
+
* Color tokens are injected as `--cl-<tokenPath>` on `:root` (or a scheme-specific
|
|
4
|
+
* `[data-color-scheme]` selector). Changing this value after tokens are injected
|
|
5
|
+
* will break existing CSS that references `var(--cl-*)`.
|
|
6
|
+
*/
|
|
7
|
+
export const CSS_VAR_PREFIX = '--cl-'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Storage key used to persist the active color scheme name in an uncompressed format
|
|
11
|
+
* (plain string, not LZ-encoded). Stored separately so a lightweight inline script
|
|
12
|
+
* can read it before React hydrates and set `data-color-scheme` on `<html>` to prevent
|
|
13
|
+
* a flash of the wrong color scheme (FOUC).
|
|
14
|
+
*/
|
|
15
|
+
export const DOM_COLOR_SCHEME_KEY = '@styles.dom.colorScheme'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Recursively flattens a nested color token object into a flat map of CSS custom-property
|
|
19
|
+
* names to their values. Nesting levels are joined with `-` (e.g., `{ primary: { solid: '#fff' } }`
|
|
20
|
+
* → `{ '--cl-primary-solid': '#fff' }`). Non-string leaf values are silently skipped.
|
|
21
|
+
*/
|
|
22
|
+
export function flattenColorMap(
|
|
23
|
+
obj: Record<string, any>,
|
|
24
|
+
path = '',
|
|
25
|
+
prefix = CSS_VAR_PREFIX,
|
|
26
|
+
): Record<string, string> {
|
|
27
|
+
const result: Record<string, string> = {}
|
|
28
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
29
|
+
const cssPath = path ? `${path}-${key}` : key
|
|
30
|
+
if (typeof value === 'string') {
|
|
31
|
+
result[`${prefix}${cssPath}`] = value
|
|
32
|
+
} else if (value && typeof value === 'object') {
|
|
33
|
+
Object.assign(result, flattenColorMap(value, cssPath, prefix))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Recursively replaces every string leaf in `obj` with a `var(--cl-<path>)` reference,
|
|
41
|
+
* preserving the original object shape. Used on web to replace raw color values with
|
|
42
|
+
* CSS variable references so that color-scheme switching via `data-color-scheme` CSS
|
|
43
|
+
* selectors works without JavaScript re-renders.
|
|
44
|
+
*/
|
|
45
|
+
export function buildCssVarProxy<T>(obj: T, path = '', prefix = CSS_VAR_PREFIX): T {
|
|
46
|
+
if (typeof obj !== 'object' || obj === null) return obj
|
|
47
|
+
const result: Record<string, any> = {}
|
|
48
|
+
for (const [key, value] of Object.entries(obj as Record<string, any>)) {
|
|
49
|
+
const cssPath = path ? `${path}-${key}` : key
|
|
50
|
+
if (typeof value === 'string') {
|
|
51
|
+
result[key] = `var(${prefix}${cssPath})`
|
|
52
|
+
} else if (value && typeof value === 'object') {
|
|
53
|
+
result[key] = buildCssVarProxy(value, cssPath, prefix)
|
|
54
|
+
} else {
|
|
55
|
+
result[key] = value
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result as T
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sets or removes `data-color-scheme` on `document.documentElement` to trigger the
|
|
63
|
+
* matching CSS selector that applies alternate-scheme custom-property overrides.
|
|
64
|
+
* The `'default'` scheme removes the attribute entirely so the `:root` base variables
|
|
65
|
+
* apply. No-ops on non-browser environments (SSR, React Native).
|
|
66
|
+
*/
|
|
67
|
+
export function applyColorSchemeToDOM(colorScheme: string) {
|
|
68
|
+
if (typeof document === 'undefined') return
|
|
69
|
+
if (colorScheme === 'default') {
|
|
70
|
+
delete document.documentElement.dataset.colorScheme
|
|
71
|
+
} else {
|
|
72
|
+
document.documentElement.dataset.colorScheme = colorScheme
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/lib/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { createStyles } from './createStyles'
|
|
1
|
+
export { createStyles, createStylesWithContext, CONTEXT_FACTORY_SYMBOL } from './createStyles'
|
|
2
2
|
export { createTheme } from './createTheme'
|
|
3
3
|
export { calc } from './calc'
|
|
4
|
+
export { flattenColorMap, buildCssVarProxy, applyColorSchemeToDOM, CSS_VAR_PREFIX, DOM_COLOR_SCHEME_KEY } from './cssVariables'
|
|
@@ -3,11 +3,11 @@ import { createStyles } from '../createStyles'
|
|
|
3
3
|
import { themeStore } from '../../theme'
|
|
4
4
|
import { mockTheme, MockedTheme } from '../../tests/theme'
|
|
5
5
|
|
|
6
|
-
describe('createStyles', () => {
|
|
7
|
-
let currentTheme: MockedTheme = null
|
|
6
|
+
describe('createStyles (web — isBrowser: true)', () => {
|
|
7
|
+
let currentTheme: MockedTheme = null as any
|
|
8
8
|
|
|
9
9
|
beforeEach(() => {
|
|
10
|
-
mockTheme()
|
|
10
|
+
mockTheme({ isBrowser: true })
|
|
11
11
|
|
|
12
12
|
currentTheme = themeStore.theme as unknown as MockedTheme
|
|
13
13
|
})
|
|
@@ -30,10 +30,10 @@ describe('createStyles', () => {
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
it('should compute styles from function when theme is available', () => {
|
|
33
|
-
const functionStyles = (theme:
|
|
33
|
+
const functionStyles = (theme: any) => ({
|
|
34
34
|
button: {
|
|
35
|
-
color: theme.colors.
|
|
36
|
-
backgroundColor: theme.colors.
|
|
35
|
+
color: theme.colors.neutralSolid500,
|
|
36
|
+
backgroundColor: theme.colors.buttonRegularPrimaryBgDefault,
|
|
37
37
|
},
|
|
38
38
|
container: {
|
|
39
39
|
padding: theme.spacing.value(2)
|
|
@@ -42,33 +42,34 @@ describe('createStyles', () => {
|
|
|
42
42
|
|
|
43
43
|
const styles = createStyles(functionStyles)
|
|
44
44
|
|
|
45
|
+
// On web (isBrowser: true), theme.colors returns CSS var references
|
|
45
46
|
expect(styles.button).toEqual({
|
|
46
|
-
color:
|
|
47
|
-
backgroundColor:
|
|
47
|
+
color: 'var(--cl-neutralSolid500)',
|
|
48
|
+
backgroundColor: 'var(--cl-buttonRegularPrimaryBgDefault)',
|
|
48
49
|
})
|
|
49
50
|
expect(styles.container).toEqual({
|
|
50
51
|
padding: 16
|
|
51
52
|
})
|
|
52
53
|
})
|
|
53
54
|
|
|
54
|
-
it('should
|
|
55
|
-
const functionStyles = (theme:
|
|
55
|
+
it('should return CSS var references regardless of active color scheme (proxy behavior)', () => {
|
|
56
|
+
const functionStyles = (theme: any) => ({
|
|
56
57
|
button: { color: theme.colors.buttonRegularPrimaryBgDefault }
|
|
57
58
|
})
|
|
58
59
|
|
|
59
60
|
const styles = createStyles(functionStyles)
|
|
60
61
|
|
|
61
|
-
//
|
|
62
|
-
|
|
62
|
+
// CSS var reference is static — same string before and after scheme change.
|
|
63
|
+
// The browser CSS cascade (via data-color-scheme attribute) handles the actual color switch.
|
|
64
|
+
expect(styles.button).toEqual({ color: 'var(--cl-buttonRegularPrimaryBgDefault)' })
|
|
63
65
|
|
|
64
66
|
currentTheme.setColorScheme('dark')
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
expect(styles.button).toEqual({ color: currentTheme.colors.primarySolid500 })
|
|
68
|
+
expect(styles.button).toEqual({ color: 'var(--cl-buttonRegularPrimaryBgDefault)' })
|
|
68
69
|
})
|
|
69
70
|
|
|
70
71
|
it('should handle complex theme-based styles', () => {
|
|
71
|
-
const functionStyles = (theme:
|
|
72
|
+
const functionStyles = (theme: any) => ({
|
|
72
73
|
button: {
|
|
73
74
|
color: theme.colors.redSolid100,
|
|
74
75
|
border: `1px solid ${theme.colors.neutralSolid1000}`,
|
|
@@ -84,14 +85,14 @@ describe('createStyles', () => {
|
|
|
84
85
|
const styles = createStyles(functionStyles)
|
|
85
86
|
|
|
86
87
|
expect(styles.button).toEqual({
|
|
87
|
-
color:
|
|
88
|
-
border:
|
|
88
|
+
color: 'var(--cl-redSolid100)',
|
|
89
|
+
border: '1px solid var(--cl-neutralSolid1000)',
|
|
89
90
|
'@media (max-width: 768px)': {
|
|
90
91
|
fontSize: '14px'
|
|
91
92
|
}
|
|
92
93
|
})
|
|
93
94
|
expect(styles.alert).toEqual({
|
|
94
|
-
backgroundColor:
|
|
95
|
+
backgroundColor: 'var(--cl-redSolid600)',
|
|
95
96
|
})
|
|
96
97
|
})
|
|
97
98
|
|
|
@@ -106,12 +107,10 @@ describe('createStyles', () => {
|
|
|
106
107
|
})
|
|
107
108
|
|
|
108
109
|
it('should work with mixed ICSS and custom properties', () => {
|
|
109
|
-
const functionStyles = (theme:
|
|
110
|
+
const functionStyles = (theme: any) => ({
|
|
110
111
|
component: {
|
|
111
|
-
// ICSS properties
|
|
112
112
|
color: theme.colors.blueSolid900,
|
|
113
113
|
padding: '10px',
|
|
114
|
-
// Custom properties (merged with ICSS)
|
|
115
114
|
customProp: 'custom-value',
|
|
116
115
|
dataAttribute: 'test'
|
|
117
116
|
}
|
|
@@ -120,7 +119,7 @@ describe('createStyles', () => {
|
|
|
120
119
|
const styles = createStyles(functionStyles)
|
|
121
120
|
|
|
122
121
|
expect(styles.component).toEqual({
|
|
123
|
-
color:
|
|
122
|
+
color: 'var(--cl-blueSolid900)',
|
|
124
123
|
padding: '10px',
|
|
125
124
|
customProp: 'custom-value',
|
|
126
125
|
dataAttribute: 'test'
|
|
@@ -130,7 +129,7 @@ describe('createStyles', () => {
|
|
|
130
129
|
it('should maintain proxy behavior across multiple accesses', () => {
|
|
131
130
|
let computeCount = 0
|
|
132
131
|
|
|
133
|
-
const functionStyles = (theme:
|
|
132
|
+
const functionStyles = (theme: any) => {
|
|
134
133
|
computeCount++
|
|
135
134
|
return {
|
|
136
135
|
button: { color: theme.colors.blueSolid100 }
|
|
@@ -149,3 +148,61 @@ describe('createStyles', () => {
|
|
|
149
148
|
expect(computeCount).toBe(3)
|
|
150
149
|
})
|
|
151
150
|
})
|
|
151
|
+
|
|
152
|
+
describe('createStyles (RN — isBrowser: false)', () => {
|
|
153
|
+
let currentTheme: MockedTheme = null as any
|
|
154
|
+
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
mockTheme({ isBrowser: false })
|
|
157
|
+
currentTheme = themeStore.theme as unknown as MockedTheme
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should compute styles from function with raw RGBA values', () => {
|
|
161
|
+
const functionStyles = (theme: any) => ({
|
|
162
|
+
button: {
|
|
163
|
+
color: theme.colors.neutralSolid500,
|
|
164
|
+
backgroundColor: theme.colors.buttonRegularPrimaryBgDefault,
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const styles = createStyles(functionStyles)
|
|
169
|
+
|
|
170
|
+
expect(styles.button).toEqual({
|
|
171
|
+
color: 'rgba(136, 136, 136, 1.00)',
|
|
172
|
+
backgroundColor: 'rgba(43, 105, 122, 1.00)',
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should return raw RGBA for active scheme after setColorScheme', () => {
|
|
177
|
+
const functionStyles = (theme: any) => ({
|
|
178
|
+
button: { color: theme.colors.buttonRegularPrimaryBgDefault },
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const styles = createStyles(functionStyles)
|
|
182
|
+
|
|
183
|
+
// On RN, colors is raw — before scheme change it's the light value
|
|
184
|
+
expect(styles.button).toEqual({ color: 'rgba(43, 105, 122, 1.00)' })
|
|
185
|
+
|
|
186
|
+
// After switching, same key in the proxy still returns the light value
|
|
187
|
+
// because theme.colors is the static raw colors object (not scheme-reactive on RN).
|
|
188
|
+
// Use theme.currentSchemeColors for the active scheme's values.
|
|
189
|
+
currentTheme.setColorScheme('dark')
|
|
190
|
+
|
|
191
|
+
expect(styles.button).toEqual({ color: 'rgba(43, 105, 122, 1.00)' })
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('currentSchemeColors returns the active scheme raw values', () => {
|
|
195
|
+
const styles = createStyles((theme: any) => ({
|
|
196
|
+
button: { color: theme.currentSchemeColors.buttonRegularPrimaryBgDefault },
|
|
197
|
+
}))
|
|
198
|
+
|
|
199
|
+
// Light mode
|
|
200
|
+
expect(styles.button).toEqual({ color: 'rgba(43, 105, 122, 1.00)' })
|
|
201
|
+
|
|
202
|
+
currentTheme.setColorScheme('dark')
|
|
203
|
+
|
|
204
|
+
// Dark mode colors — dark scheme overrides buttonRegularPrimaryBgDefault
|
|
205
|
+
const darkValue = currentTheme.currentSchemeColors.buttonRegularPrimaryBgDefault as string
|
|
206
|
+
expect(styles.button).toEqual({ color: darkValue })
|
|
207
|
+
})
|
|
208
|
+
})
|