@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rific/auto-paper",
3
- "version": "0.10.3",
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": "^0.1.8",
72
- "@infinitetoken/jest-config": "^0.2.0",
73
- "@infinitetoken/tsconfig": "^0.3.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",
@@ -1,4 +1,4 @@
1
- import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
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 { defaultThemeSettings, type ThemeSettings, ThemeSettingsContext } from './ThemeSettingsContext'
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
- const [settings, setSettings] = useState<ThemeSettings>(() => ({ ...defaultThemeSettings, ...initialValue }))
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
- const set = useCallback((patch: Partial<ThemeSettings>) => {
54
- setSettings((prev) => ({ ...prev, ...patch }))
55
- }, [])
66
+ const ThemeSettingsProvider = themeSettingsProvider
56
67
 
57
- const onChangeRef = useRef(onChange)
58
- useEffect(() => {
59
- onChangeRef.current = onChange
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
- <ThemeSettingsContext.Provider value={{ settings, set }}>
93
- <PaperProvider theme={theme}>
94
- <StatusBar backgroundColor={theme.colors.background} barStyle={theme.dark ? 'light-content' : 'dark-content'} {...statusBarProps} />
95
- <PaperDefaultsContext.Provider value={defaults ?? {}}>
96
- <NavBarContext.Provider value={{ navigationBar, onNavBarChange }}>
97
- <BlurModuleContext.Provider value={expoBlur}>
98
- <ReanimatedModuleContext.Provider value={reanimated}>
99
- <View style={[styles.flex, { backgroundColor: theme.colors.background }, style]}>{children}</View>
100
- </ReanimatedModuleContext.Provider>
101
- </BlurModuleContext.Provider>
102
- </NavBarContext.Provider>
103
- </PaperDefaultsContext.Provider>
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 { createContext } from 'react'
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
- settings: ThemeSettings
24
- set: (patch: Partial<ThemeSettings>) => void
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 = createContext<ThemeSettingsContextType>({
28
- settings: defaultThemeSettings,
29
- set: () => {}
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'
@@ -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
- // Hand-rolled slice (no @reduxjs/toolkit dependency). Action types and creator
21
- // behavior match the previous createSlice implementation exactly, so this works
22
- // with RTK stores, vanilla Redux, or any reducer-based state container.
23
- type PayloadAction<P> = { payload: P; type: string }
24
-
25
- const createAction = <P>(type: string) => {
26
- const actionCreator = (payload: P): PayloadAction<P> => ({ payload, type })
27
- actionCreator.type = type
28
- actionCreator.match = (action: { type: string }): action is PayloadAction<P> => action.type === type
29
- return actionCreator
30
- }
31
-
32
- const initialize = createAction<Partial<ThemeState>>('theme/initialize')
33
- const setAppearance = createAction<ThemeAppearance>('theme/setAppearance')
34
- const setBlur = createAction<boolean>('theme/setBlur')
35
- const setColor = createAction<string | TriadicPalette>('theme/setColor')
36
- const setHarmony = createAction<ColorHarmony>('theme/setHarmony')
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
+ }
@@ -1,5 +1,3 @@
1
- import { useContext } from 'react'
1
+ import { useThemeSettingsInternal } from './ThemeSettingsContext'
2
2
 
3
- import { ThemeSettingsContext } from './ThemeSettingsContext'
4
-
5
- export const useThemeSettings = () => useContext(ThemeSettingsContext)
3
+ export const useThemeSettings = useThemeSettingsInternal