Skip to content

Commit

Permalink
feat: add adaptNavigationTheme utility (#3399)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukewalczak authored Oct 18, 2022
1 parent e3f7ad6 commit 57e78bd
Show file tree
Hide file tree
Showing 6 changed files with 380 additions and 20 deletions.
42 changes: 41 additions & 1 deletion docs/pages/2.theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,46 @@ Passed source color into the util is translated into tones to automatically prov

<i>Source: [Material You Color System](https://m3.material.io/styles/color/the-color-system/custom-colors)</i>

## Adapting React Navigation theme

The `adaptNavigationTheme` function takes an existing React Navigation theme and returns a React Navigation theme using the colors from Material Design 3. This theme can be passed to `NavigationContainer` so that React Navigation's UI elements have the same color scheme as Paper.

```ts
adaptNavigationTheme(params)
```

<b>Parameters:</b>

| NAME | TYPE |
| ----------- | ----------- |
| params | object |

Valid `params` keys are:

* `light` () - React Navigation compliant light theme.
* `dark` () - React Navigation compliant dark theme.

```ts
// App.tsx
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { Provider, MD3LightTheme, adaptNavigationTheme } from 'react-native-paper';
const Stack = createStackNavigator();
const { LightTheme } = adaptNavigationTheme({ light: DefaultTheme });
export default function App() {
return (
<Provider theme={MD3LightTheme}>
<NavigationContainer theme={LightTheme}>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}
```

## TypeScript

By default extending the theme won't work well with TypeScript, but we can take advantage of `global augmentations` and specify the new properties that we added to the theme:
Expand Down Expand Up @@ -400,4 +440,4 @@ Otherwise, your custom theme will need to handle it manually, using React Native
The `Provider` exposes the theme to the components via [React's context API](https://reactjs.org/docs/context.html), which means that the component must be in the same tree as the `Provider`. Some React Native components will render a different tree such as a `Modal`, in which case the components inside the `Modal` won't be able to access the theme. The work around is to get the theme using the `withTheme` HOC and pass it down to the components as props, or expose it again with the exported `ThemeProvider` component.
The `Modal` component from the library already handles this edge case, so you won't need to do anything.
The `Modal` component from the library already handles this edge case, so you won't need to do anything.
126 changes: 107 additions & 19 deletions docs/pages/8.theming-with-react-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,51 @@ In this guide we will look into how to apply theming for an application using Re
Offering different theme options, especially dark/light ones, becomes increasingly a standard requirement of the modern mobile application. Fortunately, both React Navigation and React Native Paper support configurable theming out-of-the-box.
But how to make them work together?

## Themes adaptation

### Material Design 2

Fortunately, in Material Design 2, both React Navigation and React Native Paper offer very similar API when it comes to theming and theme color structure. It's possible to import them in light and dark variants from both.

```js
import {
DarkTheme as NavigationDarkTheme,
DefaultTheme as NavigationDefaultTheme,
} from '@react-navigation/native';

import {
MD2LightTheme,
MD2DarkTheme,
} from 'react-native-paper';
```

### Material Design 3

From v5, React Native Paper theme colors structure is following the Material Design 3 <i>(known as Material You)</i> colors system, which differs significantly from both previous Paper's theme and React Navigation theme.

However, to simplify adapting React Navigation theme colors, to use the ones from React Native Paper, it's worth using a utility called `adaptNavigationTheme` – it accepts navigation compliant themes in both modes and returns their equivalents adjusted to Material Design 3.

```ts
import {
DarkTheme as NavigationDarkTheme,
DefaultTheme as NavigationDefaultTheme,
} from '@react-navigation/native';

const { LightTheme, DarkTheme } = adaptNavigationTheme({
light: NavigationDefaultTheme,
dark: NavigationDarkTheme,
});
```

Library exports also Material Design 3 themes in both modes:

```js
import {
MD3LightTheme,
MD3DarkTheme,
} from 'react-native-paper';
```

## Combining theme objects

Both libraries require a wrapper to be used at the entry point of the application.
Expand Down Expand Up @@ -71,9 +116,7 @@ export default function App() {
}
```
Fortunately, both React Navigation and React Native Paper offer very similar API when it comes to theming. It's possible to import default themes in light and dark variants from both.
React Navigation and React Native Paper use the same name for default themes - `DefaultTheme` and `DarkTheme`, so we need to alias them at the imports.
Our goal here is to combine those two themes, so that we could control the theme for the entire application from a single place.
Expand All @@ -83,54 +126,103 @@ To make things easier we can use [deepmerge](https://www.npmjs.com/package/deepm
yarn add deepmerge
```
### Material Design 2
```js
import {
NavigationContainer,
DarkTheme as NavigationDarkTheme,
DefaultTheme as NavigationDefaultTheme,
} from '@react-navigation/native';
import {
DarkTheme as PaperDarkTheme,
DefaultTheme as PaperDefaultTheme,
Provider as PaperProvider,
MD2DarkTheme,
MD2LightTheme,
} from 'react-native-paper';
import merge from 'deepmerge';

const CombinedDefaultTheme = merge(PaperDefaultTheme, NavigationDefaultTheme);
const CombinedDarkTheme = merge(PaperDarkTheme, NavigationDarkTheme);
const CombinedDefaultTheme = merge(MD2DarkTheme, NavigationDefaultTheme);
const CombinedDarkTheme = merge(MD2LightTheme, NavigationDarkTheme);
```
Alternatively, we could merge those themes using vanilla JavaScript
### Material Design 3
```js
import {
NavigationContainer,
DarkTheme as NavigationDarkTheme,
DefaultTheme as NavigationDefaultTheme,
} from '@react-navigation/native';
import {
MD3DarkTheme,
MD3LightTheme,
} from 'react-native-paper';
import merge from 'deepmerge';

const { LightTheme, DarkTheme } = adaptNavigationTheme({
light: NavigationDefaultTheme,
dark: NavigationDarkTheme,
});

const CombinedDefaultTheme = merge(MD2DarkTheme, LightTheme);
const CombinedDarkTheme = merge(MD2LightTheme, DarkTheme);
```
Alternatively, we could merge those themes using vanilla JavaScript:
### Material Design 2
```js
const CombinedDefaultTheme = {
...PaperDefaultTheme,
...MD2LightTheme,
...NavigationDefaultTheme,
colors: {
...PaperDefaultTheme.colors,
...MD2LightTheme.colors,
...NavigationDefaultTheme.colors,
},
};
const CombinedDarkTheme = {
...PaperDarkTheme,
...MD2DarkTheme,
...NavigationDarkTheme,
colors: {
...PaperDarkTheme.colors,
...MD2DarkTheme.colors,
...NavigationDarkTheme.colors,
},
};
```
### Material Design 3
```js
const { LightTheme, DarkTheme } = adaptNavigationTheme({
light: NavigationDefaultTheme,
dark: NavigationDarkTheme,
});

const CombinedDefaultTheme = {
...MD3LightTheme,
...LightTheme,
colors: {
...MD3LightTheme.colors,
...LightTheme.colors,
},
};
const CombinedDarkTheme = {
...MD3DarkTheme,
...DarkTheme,
colors: {
...MD3DarkTheme.colors,
...DarkTheme.colors,
},
};
```
## Passing theme with Providers
After combining the themes, we will be able to control theming in both libraries from a single source, which will come in handy later.
Next, we need to pass merged themes into the Providers. For this part, we use the dark one - `CombinedDarkTheme`.
```js
const CombinedDefaultTheme = merge(PaperDefaultTheme, NavigationDefaultTheme);
const CombinedDarkTheme = merge(PaperDarkTheme, NavigationDarkTheme);

const Stack = createStackNavigator();

export default function App() {
Expand Down Expand Up @@ -184,9 +276,6 @@ import { PreferencesContext } from './PreferencesContext';

const Stack = createStackNavigator();

const CombinedDefaultTheme = merge(PaperDefaultTheme, NavigationDefaultTheme);
const CombinedDarkTheme = merge(PaperDarkTheme, NavigationDarkTheme);

export default function App() {
const [isThemeDark, setIsThemeDark] = React.useState(false);

Expand Down Expand Up @@ -242,7 +331,6 @@ const Header = ({ scene }) => {
<Appbar.Content title={scene.route?.name} />
<TouchableRipple onPress={() => toggleTheme()}>
<Switch
style={[{ backgroundColor: theme.colors.accent }]}
color={'red'}
value={isThemeDark}
/>
Expand Down
Loading

0 comments on commit 57e78bd

Please sign in to comment.