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

Add color fidelity option #17

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion .github/actions/setup-node/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ inputs:

pnpm-version:
description: Version of pnpm to use
default: 7.x
default: 9.x

install:
description: Run pnpm install
Expand Down
12 changes: 2 additions & 10 deletions .github/workflows/build-example-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,8 @@ jobs:
steps:
- uses: actions/checkout@v3

- name: 🏗 Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 7.x

- name: 🏗 Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: pnpm
- name: 🏗 Setup node
uses: ./.github/actions/setup-node

- name: 🏗 Setup expo
uses: expo/expo-github-action@v7
Expand Down
187 changes: 103 additions & 84 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ For devices not compatible (iOS or older Android versions) a fallback theme is r

> This library works with Expo Go, but you won't be able to retrieve the system theme (you'll get a fallback theme) because it requires custom native code and Expo Go [doesn't support it](https://docs.expo.dev/workflow/customizing/)


```
npx expo install @pchmn/expo-material3-theme
```
Expand Down Expand Up @@ -111,6 +110,24 @@ function App() {
}
```

With color fidelity (https://m3.material.io/styles/color/advanced/adjust-existing-colors#cb49eeb4-3bbd-4521-9612-0856c27f91ef):

```ts
import { useMaterial3Theme } from '@pchmn/expo-material3-theme';
import { useColorScheme, View, Button } from 'react-native';

function App() {
const colorScheme = useColorScheme();
const { theme } = useMaterial3Theme({ sourceColor: '#3E8260', colorFidelity: true });

return (
<View style={{ backgroundColor: theme[colorScheme].background }}>
<Button color={theme[colorScheme].primary}>Themed button</Button>
</View>
);
}
```

### Change theme

You may also want to update the theme by generating a new one, or go back to the default theme (to let users personalize your app for example). You can do it with `useMaterial3Theme` hook:
Expand Down Expand Up @@ -139,6 +156,7 @@ function App() {
### Usage with `react-native-paper`

#### Basic example

`@pchmn/expo-material3-theme` provides a theme compatible with `react-native-paper`, so you can combine both libraries easily:

```tsx
Expand Down Expand Up @@ -171,106 +189,107 @@ function App() {
<summary>Override <code>react-native-paper</code> theme (Typescript)</summary>
<br>

Some [colors](https://github.com/pchmn/expo-material3-theme/blob/main/src/ExpoMaterial3Theme.types.ts#L54-L61) present in `Material3Theme` from this library are not present in `MD3Theme` of `react-native-paper`. You can create a typed `useAppTheme()` hook and use it instead of `useTheme()` hook to fix this :
Some [colors](https://github.com/pchmn/expo-material3-theme/blob/main/src/ExpoMaterial3Theme.types.ts#L54-L61) present in `Material3Theme` from this library are not present in `MD3Theme` of `react-native-paper`. You can create a typed `useAppTheme()` hook and use it instead of `useTheme()` hook to fix this :

```ts
import { Material3Scheme } from '@pchmn/expo-material3-theme';
import { MD3Theme, useTheme } from 'react-native-paper';
```ts
import { Material3Scheme } from '@pchmn/expo-material3-theme';
import { MD3Theme, useTheme } from 'react-native-paper';

export const useAppTheme = useTheme<MD3Theme & { colors: Material3Scheme }>;
export const useAppTheme = useTheme<MD3Theme & { colors: Material3Scheme }>;

// Now use useAppTheme() instead of useTheme()
```

// Now use useAppTheme() instead of useTheme()
```
</details>

<details>
<summary>Create a <code>Material3ThemeProvider</code> that includes <code>PaperProvider</code></summary>

```tsx
// Material3ThemeProvider.tsx
import { Material3Scheme, Material3Theme, useMaterial3Theme } from '@pchmn/expo-material3-theme';
import { createContext, useContext } from 'react';
import { useColorScheme } from 'react-native';
import {
MD3DarkTheme,
MD3LightTheme,
MD3Theme,
Provider as PaperProvider,
ProviderProps,
useTheme,
} from 'react-native-paper';

type Material3ThemeProviderProps = {
theme: Material3Theme;
updateTheme: (sourceColor: string) => void;
resetTheme: () => void;
};

const Material3ThemeProviderContext = createContext<Material3ThemeProviderProps>({} as Material3ThemeProviderProps);

export function Material3ThemeProvider({
children,
```tsx
// Material3ThemeProvider.tsx
import { Material3Scheme, Material3Theme, useMaterial3Theme } from '@pchmn/expo-material3-theme';
import { createContext, useContext } from 'react';
import { useColorScheme } from 'react-native';
import {
MD3DarkTheme,
MD3LightTheme,
MD3Theme,
Provider as PaperProvider,
ProviderProps,
useTheme,
} from 'react-native-paper';

type Material3ThemeProviderProps = {
theme: Material3Theme;
updateTheme: (sourceColor: string) => void;
resetTheme: () => void;
};

const Material3ThemeProviderContext = createContext<Material3ThemeProviderProps>({} as Material3ThemeProviderProps);

export function Material3ThemeProvider({
children,
sourceColor,
fallbackSourceColor,
...otherProps
}: ProviderProps & { sourceColor?: string; fallbackSourceColor?: string }) {
const colorScheme = useColorScheme();

const { theme, updateTheme, resetTheme } = useMaterial3Theme({
sourceColor,
fallbackSourceColor,
...otherProps
}: ProviderProps & { sourceColor?: string; fallbackSourceColor?: string }) {
const colorScheme = useColorScheme();

const { theme, updateTheme, resetTheme } = useMaterial3Theme({
sourceColor,
fallbackSourceColor,
});

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

return (
<Material3ThemeProviderContext.Provider value={{ theme, updateTheme, resetTheme }}>
<PaperProvider theme={paperTheme} {...otherProps}>
{children}
</PaperProvider>
</Material3ThemeProviderContext.Provider>
);
}
});

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

return (
<Material3ThemeProviderContext.Provider value={{ theme, updateTheme, resetTheme }}>
<PaperProvider theme={paperTheme} {...otherProps}>
{children}
</PaperProvider>
</Material3ThemeProviderContext.Provider>
);
}

export function useMaterial3ThemeContext() {
const ctx = useContext(Material3ThemeProviderContext);
if (!ctx) {
throw new Error('useMaterial3ThemeContext must be used inside Material3ThemeProvider');
}
return ctx;
export function useMaterial3ThemeContext() {
const ctx = useContext(Material3ThemeProviderContext);
if (!ctx) {
throw new Error('useMaterial3ThemeContext must be used inside Material3ThemeProvider');
}
return ctx;
}

export const useAppTheme = useTheme<MD3Theme & { colors: Material3Scheme }>;
export const useAppTheme = useTheme<MD3Theme & { colors: Material3Scheme }>;

// App.tsx
import { Material3ThemeProvider, useAppTheme, useMaterial3ThemeContext } from '../Material3ThemeProvider';
import { View, Button } from 'react-native';

// App.tsx
import { Material3ThemeProvider, useAppTheme, useMaterial3ThemeContext } from '../Material3ThemeProvider';
import { View, Button } from 'react-native';
function App() {
return (
<Material3ThemeProvider>
<AppContent />
</Material3ThemeProvider>
);
}

function App() {
return (
<Material3ThemeProvider>
<AppContent />
</Material3ThemeProvider>
)
}
function AppContent() {
const { updateTheme, resetTheme } = useMaterial3ThemeContext();
// react-native-paper theme is always in sync
const theme = useAppTheme();

return (
<View style={{ backgroundColor: theme.colors.background }}>
{/* Update theme by generating a new one based on #3E8260 color */}
<Button onPress={() => updateTheme('#3E8260')}>Update theme</Button>
{/* Reset theme to default (system or fallback) */}
<Button onPress={() => resetTheme()}>Reset theme</Button>
</View>
);
}
```

function AppContent() {
const { updateTheme, resetTheme } = useMaterial3ThemeContext();
// react-native-paper theme is always in sync
const theme = useAppTheme();

return (
<View style={{ backgroundColor: theme.colors.background }}>
{/* Update theme by generating a new one based on #3E8260 color */}
<Button onPress={() => updateTheme('#3E8260')}>Update theme</Button>
{/* Reset theme to default (system or fallback) */}
<Button onPress={() => resetTheme()}>Reset theme</Button>
</View>
);
}
```
</details>

## Example
Expand Down
20 changes: 12 additions & 8 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# API Reference

## <code>useMaterial3Theme(params?: Params): Object</code>

## <code>useMaterial3Theme(options?: UseMaterial3ThemeOptions): Object</code>

Hook that lets you manage Material 3 theme in your app. It will return the theme retrieved from user device (or a fallback theme if device is not supported) or a theme based on a source color, a function to update the theme and a function to reset the theme.

Expand All @@ -24,14 +23,16 @@ const { theme, updateTheme, resetTheme } = useMaterial3Theme({ sourceColor: '#3E
{
fallbackSourceColor?: string;
sourceColor?: string;
colorFidelity?: boolean;
}
```

</details>

- `params`:
- `options: UseMaterial3ThemeOptions`:
- `fallbackSourceColor` (optional, default to `#6750A4`): Source color for the fallback theme
- `sourceColor` (optional): Source color for the theme (overwrite system theme)
- `colorFidelity` (optional): Apply color fidelity to make scheme colors better match `sourceColor` (or `fallbackSourceColor`)

#### Returns

Expand All @@ -48,9 +49,8 @@ const { theme, updateTheme, resetTheme } = useMaterial3Theme({ sourceColor: '#3E

</details>


- `Object`:
- `theme`:
- `theme`:
- theme retrieved from user device (or fallback theme) if `sourceColor` not provided
- theme based on `sourceColor` if provided
- `updateTheme(sourceColor)`: Function to update theme by generating a new one based on a source color
Expand All @@ -60,7 +60,7 @@ const { theme, updateTheme, resetTheme } = useMaterial3Theme({ sourceColor: '#3E

<br>

## <code>getMaterial3Theme(fallbackSourceColor?: string): <a href="../src/ExpoMaterial3Theme.types.ts#L59-L62">Material3Theme</a></code>
## <code>getMaterial3Theme(fallbackSourceColor?: string, options?: Material3ThemeOptions): <a href="../src/ExpoMaterial3Theme.types.ts#L59-L62">Material3Theme</a></code>

Function that will return the theme retrieved from user device (or a fallback theme if device is not supported).

Expand All @@ -75,14 +75,16 @@ const theme = getMaterial3Theme('#6750A4');
#### Parameters

- `fallbackSourceColor` (optional, default to `#6750A4`): Source color for the fallback theme
- `options?: Material3ThemeOptions` (optional)
- `colorFidelity` (optional): Apply color fidelity to make scheme colors better match `fallbackSourceColor` if used

#### Returns

- [`Material3Theme`](../src/ExpoMaterial3Theme.types.ts#L59-L62): theme retrieved from user device (or fallback theme)

<br>

## <code>createMaterial3Theme(sourceColor: string): <a href="../src/ExpoMaterial3Theme.types.ts#L59-L62">Material3Theme</a></code>
## <code>createMaterial3Theme(sourceColor: string, options?: Material3ThemeOptions): <a href="../src/ExpoMaterial3Theme.types.ts#L59-L62">Material3Theme</a></code>

Function that will create a Material 3 theme based on a source color (using [`@material/material-color-utilities`](https://github.com/material-foundation/material-color-utilities/tree/main/typescript)).

Expand All @@ -93,6 +95,8 @@ const theme = createMaterial3Theme('#6750A4');
#### Parameters

- `sourceColor` (required): Source color for the theme
- `options?: Material3ThemeOptions` (optional)
- `colorFidelity` (optional): Apply color fidelity to make scheme colors better match `sourceColor`

#### Returns

Expand All @@ -102,4 +106,4 @@ const theme = createMaterial3Theme('#6750A4');

## `isDynamicThemeSupported: boolean`

Constant that returns if device supports [Material 3 dynamic theme](https://developer.android.com/develop/ui/views/theming/dynamic-colors) from Android 12+ devices.
Constant that returns if device supports [Material 3 dynamic theme](https://developer.android.com/develop/ui/views/theming/dynamic-colors) from Android 12+ devices.
Loading
Loading