Skip to content

Commit ad9f3f0

Browse files
committed
feat: add DynamicTheme using PlatformColor (#4901)
* chore: remove md2 support * fix: update colors as per material-components color tokens * see https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/color/res/values/tokens.xml * fix: update the color schema for LightTheme and DarkTheme * see: https://m3.material.io/styles/color/roles * feat: add DynamicTheme * Known limitation: surface/container roles on API 31-33 use * @color/m3_ref_palette_dynamic_neutral_variant* (MCL resources that require a * native DynamicColors setup). No @android:color/ equivalent exists for those * slots. Reference palette values are used as fallback on API 31-33. * refactor: remove usage of expo-material3-theme * refactor: rename "device colors" to "Dynamic Theme" * feat: update Colors and stateOpacity tokens for PlatformColor compatibility * surfaceDisabled, onSurfaceDisabled and backdrop which were not part of the MD standard were removed * the reference theme elevation colors now use the correct neutral tones; see https://m3.material.io/blog/tone-based-surface-color-m3 * replace usage of "alpha" with opacity or surface colors * remove disabled FAB prop
1 parent 709325e commit ad9f3f0

97 files changed

Lines changed: 2513 additions & 1924 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/docs/guides/02-theming.mdx

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -227,31 +227,26 @@ export default function Main() {
227227

228228
### Sync dynamic colors with system colors
229229

230-
Using [`pchmn/expo-material3-theme`](https://github.com/pchmn/expo-material3-theme) library you can easily access the Material 3 system colors from Android 12+ devices and seamlessly integrate them into your dynamic theme. Any changes made by the user to the system colors will be automatically reflected in the theme.
230+
React Native Paper provides built-in support for Android dynamic colors through `DynamicLightTheme` and `DynamicDarkTheme`. These themes use React Native's `PlatformColor` API to map all Material Design 3 color roles to Android system colors, so any changes the user makes to their system palette are automatically reflected in the app.
231231

232232
:::info
233-
In case of incompatible devices, the library will revert to a default theme.
233+
Dynamic colors require Android 12 (API 31+). On older Android versions and all other platforms, these themes fall back to the default `LightTheme` / `DarkTheme`.
234234
:::
235235

236-
To get started, follow the [installation instructions](https://github.com/pchmn/expo-material3-theme#installation) and check the following code:
237-
238236
```tsx
239-
import { useMaterial3Theme } from '@pchmn/expo-material3-theme';
240237
import { useColorScheme } from 'react-native';
241-
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper';
238+
import {
239+
DynamicDarkTheme,
240+
DynamicLightTheme,
241+
PaperProvider,
242+
} from 'react-native-paper';
242243
import App from './src/App';
243244

244245
export default function Main() {
245-
const colorScheme = useColorScheme();
246-
const { theme } = useMaterial3Theme();
247-
248-
const paperTheme =
249-
colorScheme === 'dark'
250-
? { ...MD3DarkTheme, colors: theme.dark }
251-
: { ...MD3LightTheme, colors: theme.light };
246+
const isDarkMode = useColorScheme() === 'dark';
252247

253248
return (
254-
<PaperProvider theme={paperTheme}>
249+
<PaperProvider theme={isDarkMode ? DynamicDarkTheme : DynamicLightTheme}>
255250
<App />
256251
</PaperProvider>
257252
);

docs/docs/guides/06-recommended-libraries.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,4 @@ Material Design themed [date picker](https://material.io/components/date-pickers
3333

3434
## Time Picker
3535
[web-ridge/react-native-paper-dates](https://github.com/web-ridge/react-native-paper-dates)
36-
Material Design themed [time picker](https://material.io/components/time-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout)
37-
38-
## System Colors
39-
[pchmn/expo-material3-theme](https://github.com/pchmn/expo-material3-theme)
40-
Retrieve Material 3 system colors from Android 12+ devices
36+
Material Design themed [time picker](https://material.io/components/time-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout)

example/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"dependencies": {
1717
"@expo/vector-icons": "^15.0.2",
1818
"@expo/webpack-config": "~19.0.1",
19-
"@pchmn/expo-material3-theme": "^1.3.2",
2019
"@react-native-async-storage/async-storage": "2.2.0",
2120
"@react-native-masked-view/masked-view": "0.3.2",
2221
"@react-navigation/bottom-tabs": "^7.3.10",

example/src/DrawerItems.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
Portal,
1717
} from 'react-native-paper';
1818

19-
import { deviceColorsSupported, isWeb } from '../utils';
19+
import { dynamicThemeSupported, isWeb } from '../utils';
2020
import { useExampleTheme } from './hooks/useExampleTheme';
2121
import { PreferencesContext } from './PreferencesContext';
2222

@@ -103,7 +103,7 @@ function DrawerItems() {
103103
if (!preferences) throw new Error('PreferencesContext not provided');
104104

105105
const {
106-
toggleShouldUseDeviceColors,
106+
toggleShouldUseDynamicTheme,
107107
toggleTheme,
108108
toggleRtl: toggleRTL,
109109
toggleCollapsed,
@@ -114,7 +114,7 @@ function DrawerItems() {
114114
collapsed,
115115
rtl: isRTL,
116116
theme: { dark: isDarkTheme },
117-
shouldUseDeviceColors,
117+
shouldUseDynamicTheme,
118118
} = preferences;
119119

120120
const _handleToggleRTL = () => {
@@ -181,12 +181,12 @@ function DrawerItems() {
181181
</Drawer.Section>
182182

183183
<Drawer.Section title="Preferences">
184-
{deviceColorsSupported ? (
185-
<TouchableRipple onPress={toggleShouldUseDeviceColors}>
186-
<View style={[styles.preference, styles.v3Preference]}>
187-
<Text variant="labelLarge">Use device colors *</Text>
184+
{dynamicThemeSupported ? (
185+
<TouchableRipple onPress={toggleShouldUseDynamicTheme}>
186+
<View style={(styles.preference, styles.v3Preference)}>
187+
<Text variant="labelLarge">Use Dynamic Theme</Text>
188188
<View pointerEvents="none">
189-
<Switch value={shouldUseDeviceColors} />
189+
<Switch value={shouldUseDynamicTheme} />
190190
</View>
191191
</View>
192192
</TouchableRipple>

example/src/Examples/AppbarExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ const AppbarExample = ({ navigation }: Props) => {
182182
height: height + bottom,
183183
},
184184
{
185-
backgroundColor: theme.colors.elevation.level2,
185+
backgroundColor: theme.colors.surfaceContainerHigh,
186186
},
187187
]}
188188
safeAreaInsets={{ bottom, left, right }}

example/src/Examples/FABExample.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,6 @@ const FABExample = () => {
9595
onPress={() => {}}
9696
visible={visible}
9797
/>
98-
<FAB
99-
icon="cancel"
100-
label="Disabled FAB"
101-
style={styles.fab}
102-
onPress={() => {}}
103-
visible={visible}
104-
disabled
105-
/>
10698
<FAB
10799
icon="format-letter-case"
108100
label="Mixed case"

example/src/PreferencesContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export const PreferencesContext = React.createContext<{
88
toggleCollapsed: () => void;
99
toggleCustomFont: () => void;
1010
toggleRippleEffect: () => void;
11-
toggleShouldUseDeviceColors?: () => void;
11+
toggleShouldUseDynamicTheme?: () => void;
1212
theme: MD3Theme;
1313
rtl: boolean;
1414
collapsed: boolean;
1515
customFontLoaded: boolean;
1616
rippleEffectEnabled: boolean;
17-
shouldUseDeviceColors?: boolean;
17+
shouldUseDynamicTheme?: boolean;
1818
} | null>(null);

example/src/index.native.tsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import * as React from 'react';
22
import { I18nManager } from 'react-native';
33

4-
import { useMaterial3Theme } from '@pchmn/expo-material3-theme';
54
import AsyncStorage from '@react-native-async-storage/async-storage';
65
import { createDrawerNavigator } from '@react-navigation/drawer';
76
import { InitialState, NavigationContainer } from '@react-navigation/native';
87
import { useFonts } from 'expo-font';
98
import { useKeepAwake } from 'expo-keep-awake';
109
import { StatusBar } from 'expo-status-bar';
1110
import * as Updates from 'expo-updates';
12-
import { PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
11+
import {
12+
PaperProvider,
13+
MD3DarkTheme,
14+
MD3LightTheme,
15+
DynamicLightTheme,
16+
DynamicDarkTheme,
17+
} from 'react-native-paper';
1318
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
1419

1520
import DrawerItems from './DrawerItems';
1621
import { PreferencesContext } from './PreferencesContext';
1722
import App from './RootNavigator';
18-
import { deviceColorsSupported } from '../utils';
23+
import { dynamicThemeSupported } from '../utils';
1924
import {
2025
CombinedDefaultTheme,
2126
CombinedDarkTheme,
@@ -40,7 +45,7 @@ export default function PaperExample() {
4045
InitialState | undefined
4146
>();
4247

43-
const [shouldUseDeviceColors, setShouldUseDeviceColors] =
48+
const [shouldUseDynamicTheme, setShouldUseDynamicTheme] =
4449
React.useState(true);
4550
const [isDarkMode, setIsDarkMode] = React.useState(false);
4651
const [rtl, setRtl] = React.useState<boolean>(
@@ -50,19 +55,13 @@ export default function PaperExample() {
5055
const [customFontLoaded, setCustomFont] = React.useState(false);
5156
const [rippleEffectEnabled, setRippleEffectEnabled] = React.useState(true);
5257

53-
const { theme: mdTheme } = useMaterial3Theme();
5458
const theme = React.useMemo(() => {
55-
if (!deviceColorsSupported || !shouldUseDeviceColors) {
56-
return isDarkMode ? MD3DarkTheme : MD3LightTheme;
59+
if (dynamicThemeSupported && shouldUseDynamicTheme) {
60+
return isDarkMode ? DynamicDarkTheme : DynamicLightTheme;
5761
}
5862

59-
return isDarkMode
60-
? { ...MD3DarkTheme, colors: { ...MD3DarkTheme.colors, ...mdTheme.dark } }
61-
: {
62-
...MD3LightTheme,
63-
colors: { ...MD3LightTheme.colors, ...mdTheme.light },
64-
};
65-
}, [isDarkMode, mdTheme, shouldUseDeviceColors]);
63+
return isDarkMode ? MD3DarkTheme : MD3LightTheme;
64+
}, [isDarkMode, shouldUseDynamicTheme]);
6665

6766
React.useEffect(() => {
6867
const restoreState = async () => {
@@ -129,8 +128,8 @@ export default function PaperExample() {
129128

130129
const preferences = React.useMemo(
131130
() => ({
132-
toggleShouldUseDeviceColors: () =>
133-
setShouldUseDeviceColors((oldValue) => !oldValue),
131+
toggleShouldUseDynamicTheme: () =>
132+
setShouldUseDynamicTheme((oldValue) => !oldValue),
134133
toggleTheme: () => setIsDarkMode((oldValue) => !oldValue),
135134
toggleRtl: () => setRtl((rtl) => !rtl),
136135
toggleCollapsed: () => setCollapsed(!collapsed),
@@ -141,14 +140,14 @@ export default function PaperExample() {
141140
collapsed,
142141
rtl,
143142
theme,
144-
shouldUseDeviceColors,
143+
shouldUseDynamicTheme: shouldUseDynamicTheme,
145144
}),
146145
[
147146
rtl,
148147
theme,
149148
collapsed,
150149
customFontLoaded,
151-
shouldUseDeviceColors,
150+
shouldUseDynamicTheme,
152151
rippleEffectEnabled,
153152
]
154153
);

example/src/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ export default function PaperExample() {
111111
rippleEffectEnabled,
112112
// noop for web, specified to avoid type errors
113113
toggleRtl: noop,
114-
toggleShouldUseDeviceColors: noop,
114+
toggleShouldUseDynamicTheme: noop,
115115
rtl: false,
116-
shouldUseDeviceColors: false,
116+
shouldUseDynamicTheme: false,
117117
}),
118118
[theme, collapsed, customFontLoaded, rippleEffectEnabled]
119119
);

example/utils/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Platform } from 'react-native';
22

3-
import ExpoMaterial3ThemeModule from '@pchmn/expo-material3-theme/build/ExpoMaterial3ThemeModule';
43
import { MD3DarkTheme, MD3LightTheme, MD3Theme } from 'react-native-paper';
54

65
type ReducerAction<T extends keyof State> = {
@@ -1420,7 +1419,5 @@ export const restaurantsData = [
14201419
},
14211420
];
14221421

1423-
export const deviceColorsSupported =
1424-
Boolean(ExpoMaterial3ThemeModule) &&
1425-
Platform.OS === 'android' &&
1426-
Platform.Version >= 31;
1422+
export const dynamicThemeSupported =
1423+
Platform.OS === 'android' && (Platform.Version as number) >= 31;

0 commit comments

Comments
 (0)