@rific/auto-paper 0.10.3 → 0.12.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/README.md +35 -0
- package/dist/index.d.mts +69 -36
- package/dist/index.d.ts +69 -36
- package/dist/index.js +78 -97
- package/dist/index.mjs +53 -73
- package/package.json +6 -4
- package/src/ThemeProvider.tsx +33 -31
- package/src/ThemeSettingsContext.ts +10 -9
- package/src/components/PalettePicker.tsx +1 -1
- package/src/index.ts +2 -0
- package/src/redux/themeSlice.ts +19 -40
- package/src/useThemeBridgeProps.ts +48 -0
- package/src/useThemeSettings.ts +2 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rific/auto-paper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Adaptive react-native-paper theming: triadic palette from a seed color + system/light/dark appearance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -68,10 +68,11 @@
|
|
|
68
68
|
"esbuild": "0.28.1"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@infinitetoken/eslint-config": "
|
|
72
|
-
"@infinitetoken/jest-config": "^0.
|
|
73
|
-
"@infinitetoken/tsconfig": "^0.
|
|
71
|
+
"@infinitetoken/eslint-config": "0.2.0",
|
|
72
|
+
"@infinitetoken/jest-config": "^0.4.0",
|
|
73
|
+
"@infinitetoken/tsconfig": "^0.5.0",
|
|
74
74
|
"@reduxjs/toolkit": "^2.0.0",
|
|
75
|
+
"@rific/core": "^0.2.0",
|
|
75
76
|
"@testing-library/dom": "^10.4.1",
|
|
76
77
|
"@testing-library/react": "^16.0.0",
|
|
77
78
|
"@types/jest": "^30.0.0",
|
|
@@ -93,6 +94,7 @@
|
|
|
93
94
|
"typescript": "^6.0.3"
|
|
94
95
|
},
|
|
95
96
|
"peerDependencies": {
|
|
97
|
+
"@rific/core": ">=0.1.1",
|
|
96
98
|
"expo-blur": ">=56.0.0",
|
|
97
99
|
"expo-navigation-bar": ">=56.0.0",
|
|
98
100
|
"react": ">=19.0.0",
|
package/src/ThemeProvider.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, type ReactNode,
|
|
1
|
+
import { createContext, type ReactNode, useContext, useEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import { StatusBar, type StatusBarProps, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'
|
|
3
3
|
import { configureFonts, Provider as PaperProvider } from 'react-native-paper'
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ import type { ExpoBlurModule } from './components/BlurView'
|
|
|
6
6
|
import type { ReanimatedModule } from './components/Dialog'
|
|
7
7
|
import type { ExpoNavigationBarModule } from './navigation-bar'
|
|
8
8
|
import { type PaperDefaults, PaperDefaultsContext } from './PaperDefaultsContext'
|
|
9
|
-
import {
|
|
9
|
+
import { type ThemeSettings, themeSettingsProvider, useThemeSettingsInternal } from './ThemeSettingsContext'
|
|
10
10
|
import { useComputedTheme } from './useComputedTheme'
|
|
11
11
|
|
|
12
12
|
// expo-navigation-bar/expo-blur are no longer auto-detected via require() - Metro doesn't
|
|
@@ -47,24 +47,28 @@ export type ProviderProps = {
|
|
|
47
47
|
style?: StyleProp<ViewStyle>
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// createSettingsContext's generated Provider only exposes `settings` to descendants (via its
|
|
51
|
+
// generated useSettings()) — it can't itself also compute the theme/gate mounting on it being
|
|
52
|
+
// ready, since that logic needs `settings` in scope. Provider (below) is just this thin outer
|
|
53
|
+
// wrapper for the settings half; ThemeProviderBody (an actual descendant, reached via
|
|
54
|
+
// useThemeSettingsInternal()) does all the theme-computation/gating work the single combined
|
|
55
|
+
// component used to do directly.
|
|
50
56
|
export function Provider({ children, defaults, expoBlur, fontFamily, initialValue, navigationBar, onChange, onNavBarChange, onReady, reanimated, statusBarProps, style }: ProviderProps) {
|
|
51
|
-
|
|
57
|
+
return (
|
|
58
|
+
<ThemeSettingsProvider initialValue={initialValue} onChange={onChange}>
|
|
59
|
+
<ThemeProviderBody defaults={defaults} expoBlur={expoBlur} fontFamily={fontFamily} navigationBar={navigationBar} onNavBarChange={onNavBarChange} onReady={onReady} reanimated={reanimated} statusBarProps={statusBarProps} style={style}>
|
|
60
|
+
{children}
|
|
61
|
+
</ThemeProviderBody>
|
|
62
|
+
</ThemeSettingsProvider>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
setSettings((prev) => ({ ...prev, ...patch }))
|
|
55
|
-
}, [])
|
|
66
|
+
const ThemeSettingsProvider = themeSettingsProvider
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
})
|
|
61
|
-
const prevSettings = useRef(settings)
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (prevSettings.current !== settings) {
|
|
64
|
-
prevSettings.current = settings
|
|
65
|
-
onChangeRef.current?.(settings)
|
|
66
|
-
}
|
|
67
|
-
}, [settings])
|
|
68
|
+
type ThemeProviderBodyProps = Omit<ProviderProps, 'initialValue' | 'onChange'>
|
|
69
|
+
|
|
70
|
+
function ThemeProviderBody({ children, defaults, expoBlur, fontFamily, navigationBar, onNavBarChange, onReady, reanimated, statusBarProps, style }: ThemeProviderBodyProps) {
|
|
71
|
+
const { settings } = useThemeSettingsInternal()
|
|
68
72
|
|
|
69
73
|
const computedTheme = useComputedTheme(settings.appearance, settings.color, settings.harmony)
|
|
70
74
|
// Layered on top of useComputedTheme's own output rather than folded into it — that hook only
|
|
@@ -89,20 +93,18 @@ export function Provider({ children, defaults, expoBlur, fontFamily, initialValu
|
|
|
89
93
|
if (!theme) return null
|
|
90
94
|
|
|
91
95
|
return (
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
</PaperProvider>
|
|
105
|
-
</ThemeSettingsContext.Provider>
|
|
96
|
+
<PaperProvider theme={theme}>
|
|
97
|
+
<StatusBar backgroundColor={theme.colors.background} barStyle={theme.dark ? 'light-content' : 'dark-content'} {...statusBarProps} />
|
|
98
|
+
<PaperDefaultsContext.Provider value={defaults ?? {}}>
|
|
99
|
+
<NavBarContext.Provider value={{ navigationBar, onNavBarChange }}>
|
|
100
|
+
<BlurModuleContext.Provider value={expoBlur}>
|
|
101
|
+
<ReanimatedModuleContext.Provider value={reanimated}>
|
|
102
|
+
<View style={[styles.flex, { backgroundColor: theme.colors.background }, style]}>{children}</View>
|
|
103
|
+
</ReanimatedModuleContext.Provider>
|
|
104
|
+
</BlurModuleContext.Provider>
|
|
105
|
+
</NavBarContext.Provider>
|
|
106
|
+
</PaperDefaultsContext.Provider>
|
|
107
|
+
</PaperProvider>
|
|
106
108
|
)
|
|
107
109
|
}
|
|
108
110
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSettingsContext, type SettingsContextValue } from '@rific/core'
|
|
2
2
|
|
|
3
3
|
import type { ThemeAppearance } from './useComputedTheme'
|
|
4
4
|
import type { ColorHarmony, TriadicPalette } from './utils/getTriadicPalette'
|
|
@@ -19,12 +19,13 @@ export const defaultThemeSettings: ThemeSettings = {
|
|
|
19
19
|
harmony: 'split-complementary'
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export type ThemeSettingsContextType =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
export type ThemeSettingsContextType = SettingsContextValue<ThemeSettings>
|
|
23
|
+
|
|
24
|
+
// Single createSettingsContext() call, shared by ThemeProvider.tsx and useThemeSettings.ts (each
|
|
25
|
+
// just re-exports the relevant piece under its original name) so there's exactly one Context
|
|
26
|
+
// instance backing all three files, same as before this migration.
|
|
27
|
+
const themeSettingsContext = createSettingsContext<ThemeSettings>(defaultThemeSettings)
|
|
26
28
|
|
|
27
|
-
export const ThemeSettingsContext =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
})
|
|
29
|
+
export const ThemeSettingsContext = themeSettingsContext.Context
|
|
30
|
+
export const themeSettingsProvider = themeSettingsContext.Provider
|
|
31
|
+
export const useThemeSettingsInternal = themeSettingsContext.useSettings
|
|
@@ -119,8 +119,8 @@ const styles = StyleSheet.create({
|
|
|
119
119
|
harmonyRow: { marginBottom: 4 },
|
|
120
120
|
innerRotator: { height: '100%', left: '-100%', overflow: 'hidden', position: 'absolute', top: 0, width: '200%' },
|
|
121
121
|
pie: { overflow: 'hidden' },
|
|
122
|
-
rotator: { height: '100%', left: 0, overflow: 'hidden', position: 'absolute', top: 0, width: '100%' },
|
|
123
122
|
ring: { alignItems: 'center', bottom: 0, justifyContent: 'center', left: 0, position: 'absolute', right: 0, top: 0 },
|
|
123
|
+
rotator: { height: '100%', left: 0, overflow: 'hidden', position: 'absolute', top: 0, width: '100%' },
|
|
124
124
|
swatch: { alignItems: 'center', justifyContent: 'center' },
|
|
125
125
|
swatches: { flexDirection: 'row', flexWrap: 'wrap', gap: SWATCH_GRID_GAP, paddingTop: 12 },
|
|
126
126
|
trigger: { alignItems: 'center', alignSelf: 'flex-start', borderRadius: SIZE / 2, height: SIZE, justifyContent: 'center', width: SIZE },
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,8 @@ export { defaultThemeSettings, ThemeSettingsContext } from './ThemeSettingsConte
|
|
|
35
35
|
export type { AutoPaperTheme, SemanticColorRoles, ThemeAppearance } from './useComputedTheme'
|
|
36
36
|
export { SEMANTIC_BASE_COLORS, useComputedTheme } from './useComputedTheme'
|
|
37
37
|
export { useAutoPaperTheme } from './useTheme'
|
|
38
|
+
export type { ThemeBridgeProps } from './useThemeBridgeProps'
|
|
39
|
+
export { useThemeBridgeProps } from './useThemeBridgeProps'
|
|
38
40
|
export { useThemeSettings } from './useThemeSettings'
|
|
39
41
|
export { getBlendedColor } from './utils/getBlendedColor'
|
|
40
42
|
export type { ColorRoles } from './utils/getColorRoles'
|
package/src/redux/themeSlice.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createSettingsSlice } from '@rific/core'
|
|
2
|
+
|
|
1
3
|
import { ThemeAppearance } from '../useComputedTheme'
|
|
2
4
|
import type { ColorHarmony, TriadicPalette } from '../utils/getTriadicPalette'
|
|
3
5
|
|
|
@@ -17,43 +19,20 @@ const defaultInitialState: ThemeState = {
|
|
|
17
19
|
harmony: 'split-complementary'
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
export const themeActions = { initialize, setAppearance, setBlur, setColor, setHarmony }
|
|
39
|
-
|
|
40
|
-
const reduce = (state: ThemeState, action: { type: string }): ThemeState => {
|
|
41
|
-
if (initialize.match(action)) return { ...state, ...action.payload }
|
|
42
|
-
if (setAppearance.match(action)) return { ...state, appearance: action.payload }
|
|
43
|
-
if (setBlur.match(action)) return { ...state, blur: action.payload }
|
|
44
|
-
if (setColor.match(action)) return { ...state, color: action.payload }
|
|
45
|
-
if (setHarmony.match(action)) return { ...state, harmony: action.payload }
|
|
46
|
-
return state
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function createThemeReducer(initialState?: Partial<ThemeState>) {
|
|
50
|
-
const initial = { ...defaultInitialState, ...initialState }
|
|
51
|
-
return (state: ThemeState = initial, action: { type: string }): ThemeState => reduce(state, action)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const themeReducer = (state: ThemeState = defaultInitialState, action: { type: string }): ThemeState => reduce(state, action)
|
|
55
|
-
|
|
56
|
-
export const selectThemeAppearance = (state: ThemeState) => state.appearance
|
|
57
|
-
export const selectThemeBlur = (state: ThemeState) => state.blur
|
|
58
|
-
export const selectThemeColor = (state: ThemeState) => state.color
|
|
59
|
-
export const selectThemeHarmony = (state: ThemeState) => state.harmony
|
|
22
|
+
const themeSlice = createSettingsSlice('theme', {
|
|
23
|
+
initialState: defaultInitialState,
|
|
24
|
+
initializeMode: 'merge' as const,
|
|
25
|
+
selectors: ['appearance', 'blur', 'color', 'harmony'] as const
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const themeActions = themeSlice.actions
|
|
29
|
+
export const themeReducer = themeSlice.reducer
|
|
30
|
+
export const createThemeReducer = themeSlice.createReducer
|
|
31
|
+
|
|
32
|
+
// This package's existing public selector names carry a `Theme` infix the factory's generic
|
|
33
|
+
// select${Field} naming doesn't produce - remapped explicitly rather than teaching the factory a
|
|
34
|
+
// one-off naming convention only this package wants.
|
|
35
|
+
export const selectThemeAppearance = themeSlice.selectors.selectAppearance
|
|
36
|
+
export const selectThemeBlur = themeSlice.selectors.selectBlur
|
|
37
|
+
export const selectThemeColor = themeSlice.selectors.selectColor
|
|
38
|
+
export const selectThemeHarmony = themeSlice.selectors.selectHarmony
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { ProviderProps } from './ThemeProvider'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The subset of ProviderProps a consuming app's own Redux-backed "Theme" component is responsible
|
|
7
|
+
* for supplying. Every field here has an identically-named counterpart on ProviderProps itself
|
|
8
|
+
* (see ThemeProvider.tsx) - deliberately not renamed, so wiring this up needs no new vocabulary
|
|
9
|
+
* beyond the provider's own.
|
|
10
|
+
*/
|
|
11
|
+
export type ThemeBridgeProps = Pick<ProviderProps, 'initialValue' | 'onChange' | 'onReady'>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reshapes an app's own Redux-sourced theme state and callbacks into the exact prop object
|
|
15
|
+
* `<Provider>` expects, so a consuming app's own "Theme" component - every game in the fleet
|
|
16
|
+
* defines one, reading `state.theme` from its own store via `useSelector(..., shallowEqual)` and
|
|
17
|
+
* dispatching `themeActions.initialize` on change - can hand its own `useSelector`/`dispatch`
|
|
18
|
+
* results straight to this hook instead of re-deriving the same three-prop shape by hand in every
|
|
19
|
+
* app:
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* export function Theme({ children }: Props) {
|
|
23
|
+
* const settings = useSelector((state: RootState) => state.theme, shallowEqual)
|
|
24
|
+
* const dispatch = useDispatch()
|
|
25
|
+
* const onChange = useCallback((next: ThemeSettings) => dispatch(themeActions.initialize(next)), [dispatch])
|
|
26
|
+
* const onReady = useCallback(() => markSplashReady('theme'), [])
|
|
27
|
+
* const bridgeProps = useThemeBridgeProps({ initialValue: settings, onChange, onReady })
|
|
28
|
+
* return <Provider {...bridgeProps}>{children}</Provider>
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Deliberately takes plain values and callbacks rather than a `dispatch` function or any
|
|
33
|
+
* Redux-specific type: this package has no opinion on any app's own RootState shape, or even that
|
|
34
|
+
* state management is Redux at all - the app does its own `useSelector`/`dispatch` wiring and
|
|
35
|
+
* passes just the results in.
|
|
36
|
+
*
|
|
37
|
+
* `onReady` is threaded through completely unchanged, never recomputed or wrapped here - it fires
|
|
38
|
+
* exactly once, the first time `<Provider>` itself reports its computed theme is ready, which is
|
|
39
|
+
* the timing a consuming app's own splash-gate mark depends on. A wrong `onReady` callback (e.g.
|
|
40
|
+
* one that marks the gate ready before `<Provider>` actually is) is a bug in the app's own
|
|
41
|
+
* callback, not something this hook can catch or fix - see BoxHockey's and LightCycles' own history
|
|
42
|
+
* of hand-fixing exactly that "marked ready before Provider was actually ready" bug in their own
|
|
43
|
+
* Theme.tsx before this hook existed.
|
|
44
|
+
*/
|
|
45
|
+
export function useThemeBridgeProps(props: ThemeBridgeProps): ThemeBridgeProps {
|
|
46
|
+
const { initialValue, onChange, onReady } = props
|
|
47
|
+
return useMemo(() => ({ initialValue, onChange, onReady }), [initialValue, onChange, onReady])
|
|
48
|
+
}
|
package/src/useThemeSettings.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useThemeSettingsInternal } from './ThemeSettingsContext'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export const useThemeSettings = () => useContext(ThemeSettingsContext)
|
|
3
|
+
export const useThemeSettings = useThemeSettingsInternal
|