Skip to content

Commit 96603d4

Browse files
committed
feat: add dynamicColor prop and DynamicTheme refactor
1 parent b4a5696 commit 96603d4

10 files changed

Lines changed: 532 additions & 498 deletions

File tree

src/babel/__fixtures__/rewrite-imports/output.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ import { MD3Colors } from "react-native-paper/lib/module/deprecated";
88
import { NonExistent, NonExistentSecond as Stuff } from "react-native-paper/lib/module/index.js";
99
import { ThemeProvider } from "react-native-paper/lib/module/core/theming";
1010
import { withTheme } from "react-native-paper/lib/module/core/theming";
11-
import { DefaultTheme } from "react-native-paper/lib/module/core/theming";
11+
import { DefaultTheme } from "react-native-paper/lib/module/deprecated";

src/core/PaperProvider.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import { defaultThemes, ThemeProvider } from './theming';
77
import MaterialCommunityIcon from '../components/MaterialCommunityIcon';
88
import PortalHost from '../components/Portal/PortalHost';
99
import { useAccessibleTheme } from '../theme/accessibility';
10+
import {
11+
isDynamicColorSupported,
12+
lightDynamicColors,
13+
darkDynamicColors,
14+
} from '../theme/schemes/DynamicTheme';
1015
import type { Theme, ThemeProp } from '../types';
1116

1217
export type Props = {
@@ -19,10 +24,17 @@ export type Props = {
1924
* accessibility in your own code.
2025
*/
2126
accessibilityAdapters?: boolean;
27+
/**
28+
* Whether to use Android Material You (dynamic) colors from the system wallpaper seed.
29+
* When `true`, dynamic colors override `theme.colors` on Android API 31+.
30+
* Falls back silently on unsupported platforms and API levels.
31+
* Set to `false` (default) when providing a fully custom color theme.
32+
*/
33+
dynamicColor?: boolean;
2234
};
2335

2436
const PaperProvider = (props: Props) => {
25-
const { accessibilityAdapters = true } = props;
37+
const { accessibilityAdapters = true, dynamicColor = false } = props;
2638

2739
const colorSchemeName =
2840
(!props.theme && Appearance?.getColorScheme()) || 'light';
@@ -59,17 +71,28 @@ const PaperProvider = (props: Props) => {
5971
}, [props.theme]);
6072

6173
const rawTheme = React.useMemo(() => {
62-
const scheme = colorScheme === 'dark' ? 'dark' : 'light';
74+
const effectiveDark = props.theme?.dark ?? colorScheme === 'dark';
75+
const scheme = effectiveDark ? 'dark' : 'light';
6376
const base = defaultThemes[scheme];
77+
const isDynamic = dynamicColor && isDynamicColorSupported;
78+
const dynamicColors = isDynamic
79+
? scheme === 'dark'
80+
? darkDynamicColors
81+
: lightDynamicColors
82+
: null;
6483
return {
6584
...base,
6685
...props.theme,
86+
...(dynamicColors
87+
? { colors: { ...base.colors, ...dynamicColors } }
88+
: {}),
6789
animation: {
6890
...props.theme?.animation,
6991
scale: props.theme?.animation?.scale ?? 1,
7092
},
71-
} as Theme;
72-
}, [colorScheme, props.theme]);
93+
dynamic: isDynamic,
94+
} as unknown as Theme;
95+
}, [colorScheme, props.theme, dynamicColor]);
7396

7497
const theme = useAccessibleTheme(rawTheme, accessibilityAdapters !== false);
7598

src/core/__tests__/PaperProvider.test.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ const createProvider = (theme?: ThemeProp) => {
103103
);
104104
};
105105

106-
const ExtendedLightTheme = { ...LightTheme } as ThemeProp;
107-
const ExtendedDarkTheme = { ...DarkTheme } as ThemeProp;
106+
const ExtendedLightTheme = { ...LightTheme, dynamic: false } as ThemeProp;
107+
const ExtendedDarkTheme = { ...DarkTheme, dynamic: false } as ThemeProp;
108108

109109
describe('PaperProvider', () => {
110110
beforeEach(() => {
@@ -134,6 +134,7 @@ describe('PaperProvider', () => {
134134
expect(getByTestId('provider-child-view').props.theme).toStrictEqual({
135135
...LightTheme,
136136
animation: { scale: 1, defaultAnimationDuration: 250 },
137+
dynamic: false,
137138
});
138139
});
139140

@@ -229,8 +230,9 @@ describe('PaperProvider', () => {
229230
},
230231
} as ThemeProp;
231232
const { getByTestId } = render(createProvider(customTheme));
232-
expect(getByTestId('provider-child-view').props.theme).toStrictEqual(
233-
customTheme
234-
);
233+
expect(getByTestId('provider-child-view').props.theme).toStrictEqual({
234+
...customTheme,
235+
dynamic: false,
236+
});
235237
});
236238
});

src/deprecated.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Do not add anything here that isn't a deprecated alias.
99
*/
1010

11+
import { useTheme } from './core/theming';
1112
import { DarkTheme } from './theme/schemes/DarkTheme';
1213
import { LightTheme } from './theme/schemes/LightTheme';
1314
import { Palette } from './theme/tokens';
@@ -45,3 +46,13 @@ export type MD3Theme = Theme;
4546
* @deprecated Use `Elevation` instead. Will be removed in a future version.
4647
*/
4748
export type MD3Elevation = Elevation;
49+
50+
/**
51+
* @deprecated Use `LightTheme` instead. Will be removed in a future version.
52+
*/
53+
export const DefaultTheme = LightTheme;
54+
55+
/**
56+
* @deprecated Use `useTheme` instead. Will be removed in a future version.
57+
*/
58+
export { useTheme as useAppTheme };

src/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export {
44
useTheme,
55
withTheme,
66
ThemeProvider,
7-
DefaultTheme,
87
adaptNavigationTheme,
98
} from './core/theming';
109

@@ -168,4 +167,6 @@ export {
168167
MD3TypescaleKey,
169168
type MD3Theme,
170169
type MD3Elevation,
170+
DefaultTheme,
171+
useAppTheme,
171172
} from './deprecated';

src/theme/provider.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ComponentType } from 'react';
33
import { $DeepPartial, createTheming } from '@callstack/react-theme-provider';
44

55
import { DarkTheme, LightTheme } from './schemes';
6-
import type { InternalTheme, Theme, NavigationTheme } from '../types';
6+
import type { Theme, NavigationTheme } from '../types';
77

88
export const DefaultTheme = LightTheme;
99

@@ -18,11 +18,11 @@ export function useTheme<T = Theme>(overrides?: $DeepPartial<T>) {
1818
}
1919

2020
export const useInternalTheme = (
21-
themeOverrides: $DeepPartial<InternalTheme> | undefined
22-
) => useAppTheme<InternalTheme>(themeOverrides);
21+
themeOverrides: $DeepPartial<Theme> | undefined
22+
) => useAppTheme<Theme>(themeOverrides);
2323

24-
export const withInternalTheme = <Props extends { theme: InternalTheme }, C>(
25-
WrappedComponent: ComponentType<Props & { theme: InternalTheme }> & C
24+
export const withInternalTheme = <Props extends { theme: Theme }, C>(
25+
WrappedComponent: ComponentType<Props & { theme: Theme }> & C
2626
) => withTheme<Props, C>(WrappedComponent);
2727

2828
export const defaultThemes = {

0 commit comments

Comments
 (0)