Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 9 additions & 14 deletions docs/docs/guides/02-theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -227,31 +227,26 @@ export default function Main() {

### Sync dynamic colors with system colors

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.
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.

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

To get started, follow the [installation instructions](https://github.com/pchmn/expo-material3-theme#installation) and check the following code:

```tsx
import { useMaterial3Theme } from '@pchmn/expo-material3-theme';
import { useColorScheme } from 'react-native';
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper';
import {
DynamicDarkTheme,
DynamicLightTheme,
PaperProvider,
} from 'react-native-paper';
import App from './src/App';

export default function Main() {
const colorScheme = useColorScheme();
const { theme } = useMaterial3Theme();

const paperTheme =
colorScheme === 'dark'
? { ...MD3DarkTheme, colors: theme.dark }
: { ...MD3LightTheme, colors: theme.light };
const isDarkMode = useColorScheme() === 'dark';

return (
<PaperProvider theme={paperTheme}>
<PaperProvider theme={isDarkMode ? DynamicDarkTheme : DynamicLightTheme}>
<App />
</PaperProvider>
);
Expand Down
6 changes: 1 addition & 5 deletions docs/docs/guides/06-recommended-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,4 @@ Material Design themed [date picker](https://material.io/components/date-pickers

## Time Picker
[web-ridge/react-native-paper-dates](https://github.com/web-ridge/react-native-paper-dates)
Material Design themed [time picker](https://material.io/components/time-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout)

## System Colors
[pchmn/expo-material3-theme](https://github.com/pchmn/expo-material3-theme)
Retrieve Material 3 system colors from Android 12+ devices
Material Design themed [time picker](https://material.io/components/time-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout)
1 change: 0 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"dependencies": {
"@expo/vector-icons": "^15.0.2",
"@expo/webpack-config": "~19.0.1",
"@pchmn/expo-material3-theme": "^1.3.2",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-native-masked-view/masked-view": "0.3.2",
"@react-navigation/bottom-tabs": "^7.3.10",
Expand Down
16 changes: 8 additions & 8 deletions example/src/DrawerItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
Portal,
} from 'react-native-paper';

import { deviceColorsSupported, isWeb } from '../utils';
import { dynamicThemeSupported, isWeb } from '../utils';
import { useExampleTheme } from './hooks/useExampleTheme';
import { PreferencesContext } from './PreferencesContext';

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

const {
toggleShouldUseDeviceColors,
toggleShouldUseDynamicTheme,
toggleTheme,
toggleRtl: toggleRTL,
toggleCollapsed,
Expand All @@ -114,7 +114,7 @@ function DrawerItems() {
collapsed,
rtl: isRTL,
theme: { dark: isDarkTheme },
shouldUseDeviceColors,
shouldUseDynamicTheme,
} = preferences;

const _handleToggleRTL = () => {
Expand Down Expand Up @@ -181,12 +181,12 @@ function DrawerItems() {
</Drawer.Section>

<Drawer.Section title="Preferences">
{deviceColorsSupported && isV3 ? (
<TouchableRipple onPress={toggleShouldUseDeviceColors}>
<View style={[styles.preference, isV3 && styles.v3Preference]}>
<Text variant="labelLarge">Use device colors *</Text>
{dynamicThemeSupported ? (
<TouchableRipple onPress={toggleShouldUseDynamicTheme}>
<View style={styles.preference}>
<Text variant="labelLarge">Use Dynamic Theme</Text>
<View pointerEvents="none">
<Switch value={shouldUseDeviceColors} />
<Switch value={shouldUseDynamicTheme} />
</View>
</View>
</TouchableRipple>
Expand Down
2 changes: 1 addition & 1 deletion example/src/Examples/AppbarExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ const AppbarExample = ({ navigation }: Props) => {
height: height + bottom,
},
{
backgroundColor: theme.colors.elevation.level2,
backgroundColor: theme.colors.surfaceContainerHigh,
},
]}
safeAreaInsets={{ bottom, left, right }}
Expand Down
8 changes: 0 additions & 8 deletions example/src/Examples/FABExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,6 @@ const FABExample = () => {
onPress={() => {}}
visible={visible}
/>
<FAB
icon="cancel"
label="Disabled FAB"
style={styles.fab}
onPress={() => {}}
visible={visible}
disabled
/>
<FAB
icon="format-letter-case"
label="Mixed case"
Expand Down
4 changes: 2 additions & 2 deletions example/src/PreferencesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export const PreferencesContext = React.createContext<{
toggleCollapsed: () => void;
toggleCustomFont: () => void;
toggleRippleEffect: () => void;
toggleShouldUseDeviceColors?: () => void;
toggleShouldUseDynamicTheme?: () => void;
theme: MD3Theme;
rtl: boolean;
collapsed: boolean;
customFontLoaded: boolean;
rippleEffectEnabled: boolean;
shouldUseDeviceColors?: boolean;
shouldUseDynamicTheme?: boolean;
} | null>(null);
37 changes: 18 additions & 19 deletions example/src/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import * as React from 'react';
import { I18nManager } from 'react-native';

import { useMaterial3Theme } from '@pchmn/expo-material3-theme';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { InitialState, NavigationContainer } from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { useKeepAwake } from 'expo-keep-awake';
import { StatusBar } from 'expo-status-bar';
import * as Updates from 'expo-updates';
import { PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
import {
PaperProvider,
MD3DarkTheme,
MD3LightTheme,
DynamicLightTheme,
DynamicDarkTheme,
} from 'react-native-paper';
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';

import DrawerItems from './DrawerItems';
import { PreferencesContext } from './PreferencesContext';
import App from './RootNavigator';
import { deviceColorsSupported } from '../utils';
import { dynamicThemeSupported } from '../utils';
import {
CombinedDefaultTheme,
CombinedDarkTheme,
Expand All @@ -40,7 +45,7 @@ export default function PaperExample() {
InitialState | undefined
>();

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

const { theme: mdTheme } = useMaterial3Theme();
const theme = React.useMemo(() => {
if (!deviceColorsSupported || !shouldUseDeviceColors) {
return isDarkMode ? MD3DarkTheme : MD3LightTheme;
if (dynamicThemeSupported && shouldUseDynamicTheme) {
return isDarkMode ? DynamicDarkTheme : DynamicLightTheme;
}

return isDarkMode
? { ...MD3DarkTheme, colors: { ...MD3DarkTheme.colors, ...mdTheme.dark } }
: {
...MD3LightTheme,
colors: { ...MD3LightTheme.colors, ...mdTheme.light },
};
}, [isDarkMode, mdTheme, shouldUseDeviceColors]);
return isDarkMode ? MD3DarkTheme : MD3LightTheme;
}, [isDarkMode, shouldUseDynamicTheme]);

React.useEffect(() => {
const restoreState = async () => {
Expand Down Expand Up @@ -129,8 +128,8 @@ export default function PaperExample() {

const preferences = React.useMemo(
() => ({
toggleShouldUseDeviceColors: () =>
setShouldUseDeviceColors((oldValue) => !oldValue),
toggleShouldUseDynamicTheme: () =>
setShouldUseDynamicTheme((oldValue) => !oldValue),
toggleTheme: () => setIsDarkMode((oldValue) => !oldValue),
toggleRtl: () => setRtl((rtl) => !rtl),
toggleCollapsed: () => setCollapsed(!collapsed),
Expand All @@ -141,14 +140,14 @@ export default function PaperExample() {
collapsed,
rtl,
theme,
shouldUseDeviceColors,
shouldUseDynamicTheme: shouldUseDynamicTheme,
}),
[
rtl,
theme,
collapsed,
customFontLoaded,
shouldUseDeviceColors,
shouldUseDynamicTheme,
rippleEffectEnabled,
]
);
Expand Down Expand Up @@ -199,7 +198,7 @@ export default function PaperExample() {
);
}}
</SafeAreaInsetsContext.Consumer>
<StatusBar style={!theme.isV3 || theme.dark ? 'light' : 'dark'} />
<StatusBar style={theme.dark ? 'light' : 'dark'} />
</NavigationContainer>
</PreferencesContext.Provider>
</PaperProvider>
Expand Down
4 changes: 2 additions & 2 deletions example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ export default function PaperExample() {
rippleEffectEnabled,
// noop for web, specified to avoid type errors
toggleRtl: noop,
toggleShouldUseDeviceColors: noop,
toggleShouldUseDynamicTheme: noop,
rtl: false,
shouldUseDeviceColors: false,
shouldUseDynamicTheme: false,
}),
[theme, collapsed, customFontLoaded, rippleEffectEnabled]
);
Expand Down
7 changes: 2 additions & 5 deletions example/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Platform } from 'react-native';

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

type ReducerAction<T extends keyof State> = {
Expand Down Expand Up @@ -1420,7 +1419,5 @@ export const restaurantsData = [
},
];

export const deviceColorsSupported =
Boolean(ExpoMaterial3ThemeModule) &&
Platform.OS === 'android' &&
Platform.Version >= 31;
export const dynamicThemeSupported =
Platform.OS === 'android' && (Platform.Version as number) >= 31;
2 changes: 1 addition & 1 deletion src/components/Appbar/Appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export type Props = Omit<
* styles.bottom,
* {
* height: BOTTOM_APPBAR_HEIGHT + bottom,
* backgroundColor: theme.colors.elevation.level2,
* backgroundColor: theme.colors.surfaceContainer,
* },
* ]}
* safeAreaInsets={{ bottom }}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Appbar/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const getAppbarBackgroundColor = (
}

if (elevated) {
return colors.elevation.level2;
return colors.surfaceContainer;
}

return colors.surface;
Expand Down
2 changes: 1 addition & 1 deletion src/components/BottomNavigation/BottomNavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ const BottomNavigationBar = <Route extends BaseRoute>({
backgroundColor?: ColorValue;
};

const backgroundColor = customBackground || colors.elevation.level2;
const backgroundColor = customBackground || colors.surfaceContainer;

const activeTintColor = getActiveTintColor({
activeColor,
Expand Down
41 changes: 30 additions & 11 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,23 +274,29 @@ const Button = (
const borderRadius = 5 * roundness;
const iconSize = 18;

const { backgroundColor, borderColor, textColor, borderWidth } =
getButtonColors({
customButtonColor,
customTextColor,
theme,
mode,
disabled,
dark,
});
const {
backgroundColor,
borderColor,
textColor,
textOpacity,
borderWidth,
backgroundOpacity,
} = getButtonColors({
customButtonColor,
customTextColor,
theme,
mode,
disabled,
dark,
});

const touchableStyle = {
...borderRadiusStyles,
borderRadius: borderRadiusStyles.borderRadius ?? borderRadius,
};

const buttonStyle = {
backgroundColor,
backgroundColor: backgroundOpacity < 1 ? 'transparent' : backgroundColor,
borderColor,
borderWidth,
...touchableStyle,
Expand Down Expand Up @@ -337,6 +343,19 @@ const Button = (
elevation={elevation}
container
>
{backgroundOpacity < 1 && (
<View
pointerEvents="none"
style={[
StyleSheet.absoluteFill,
{
backgroundColor,
opacity: backgroundOpacity,
borderRadius: touchableStyle.borderRadius,
},
]}
/>
)}
<TouchableRipple
borderless
background={background}
Expand All @@ -357,7 +376,7 @@ const Button = (
theme={theme}
ref={touchableRef}
>
<View style={[styles.content, contentStyle]}>
<View style={[styles.content, { opacity: textOpacity }, contentStyle]}>
{icon && loading !== true ? (
<View style={iconStyle} testID={`${testID}-icon-container`}>
<Icon
Expand Down
Loading
Loading