Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add adaptNavigationTheme utility #3399

Merged
merged 1 commit into from
Oct 18, 2022
Merged
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
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