Skip to content

Commit

Permalink
Merge pull request #8874 from marmelab/theme-dark-mode
Browse files Browse the repository at this point in the history
Add ability to default to dark mode when users prefer it
  • Loading branch information
djhi authored May 9, 2023
2 parents 14e9c3d + 1c26c54 commit 906af62
Show file tree
Hide file tree
Showing 35 changed files with 1,230 additions and 680 deletions.
694 changes: 443 additions & 251 deletions docs/Admin.md

Large diffs are not rendered by default.

68 changes: 38 additions & 30 deletions docs/AppBar.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ By default, the `<AppBar>` component displays:
- a hamburger icon to toggle the sidebar width,
- the page title,
- a button to change locales (if the application uses [i18n](./Translation.md)),
- a button to change the theme (if the application uses a [dark theme](./Admin.md#darktheme)),
- a loading indicator,
- a button to display the user menu.

Expand Down Expand Up @@ -74,28 +75,29 @@ Additional props are passed to [the underlying Material UI `<AppBar>` element](h

## `children`

The `<AppBar>` component accepts a `children` prop, which is displayed in the central part of the app bar. This is useful to add buttons to the app bar, for instance, a light/dark theme switcher.
The `<AppBar>` component accepts a `children` prop, which is displayed in the central part of the app bar. This is useful to add buttons to the app bar, for instance, a settings button.

```jsx
// in src/MyAppBar.js
import {
AppBar,
TitlePortal,
ToggleThemeButton,
defaultTheme,
} from 'react-admin';
import { AppBar, TitlePortal } from 'react-admin';
import SettingsIcon from '@mui/icons-material/Settings';
import { IconButton } from '@mui/material';

const darkTheme = { palette: { mode: 'dark' } };
const SettingsButton = () => (
<IconButton color="inherit">
<SettingsIcon />
</IconButton>
);

export const MyAppBar = () => (
<AppBar>
<TitlePortal />
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
<SettingsButton />
</AppBar>
);
```

![App bar with a toggle theme button](./img/AppBar-children.png)
![App bar with a settings button](./img/AppBar-children.png)

**Tip**: Whats the `<TitlePortal>`? It's a placeholder for the page title, that components in the page can fill using [the `<Title>` component](./Title.md). `<Title>` uses a [React Portal](https://reactjs.org/docs/portals.html) under the hood. `<TitlePortal>` takes all the available space in the app bar, so it "pushes" the following children to the right.

Expand Down Expand Up @@ -166,7 +168,11 @@ To override the style of `<AppBar>` using the [Material UI style overrides](http

## `toolbar`

By default, the `<AppBar>` renders two buttons in addition to the user menu: the language menu and the refresh button.
By default, the `<AppBar>` renders three buttons in addition to the user menu:

- the [language menu button](./LocalesMenuButton.md),
- the [theme toggle button](./ToggleThemeButton.md),
- and [the refresh button](./Buttons.md#refreshbutton).

If you want to reorder or remove these buttons, you can customize the toolbar by passing a `toolbar` prop.

Expand All @@ -177,36 +183,37 @@ import {
LocalesMenuButton,
RefreshIconButton,
ToggleThemeButton,
defaultTheme,
} from 'react-admin';

const darkTheme = {
palette: { mode: 'dark' },
};

export const MyAppBar = () => (
<AppBar toolbar={
<>
<LocalesMenuButton />
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
<ToggleThemeButton />
<RefreshIconButton />
</>
} />
);
```

**Tip**: If you only need to *add* buttons to the toolbar, you can pass them as children instead of overriding the entire toolbar.
**Tip**: If you only need to *add* buttons to the toolbar, you can pass them as [children](#children) instead of overriding the entire toolbar.

```jsx
// in src/MyAppBar.js
import { AppBar, TitlePortal, ToggleThemeButton, defaultTheme } from 'react-admin';
import { AppBar, TitlePortal } from 'react-admin';
import SettingsIcon from '@mui/icons-material/Settings';
import { IconButton } from '@mui/material';

const darkTheme = { palette: { mode: 'dark' } };
const SettingsButton = () => (
<IconButton color="inherit">
<SettingsIcon />
</IconButton>
);

export const MyAppBar = () => (
<AppBar>
<TitlePortal />
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
<SettingsButton />
</AppBar>
);
```
Expand Down Expand Up @@ -346,23 +353,24 @@ export const i18nProvider = {

To add buttons to the app bar, you can use the `<AppBar>` [`children` prop](#children).

For instance, to add `<ToggleThemeButton>`:
For instance, to add a settings button:

```jsx
// in src/MyAppBar.js
import {
AppBar,
TitlePortal,
ToggleThemeButton,
defaultTheme,
} from 'react-admin';
import { AppBar, TitlePortal } from 'react-admin';
import SettingsIcon from '@mui/icons-material/Settings';
import { IconButton } from '@mui/material';

const darkTheme = { palette: { mode: 'dark' } };
const SettingsButton = () => (
<IconButton color="inherit">
<SettingsIcon />
</IconButton>
);

export const MyAppBar = () => (
<AppBar>
<TitlePortal />
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
<SettingsButton />
</AppBar>
);
```
Expand Down
2 changes: 1 addition & 1 deletion docs/AuthProviderList.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ It's very common that your auth logic is so specific that you'll need to write y
- **[AWS Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/setting-up-the-javascript-sdk.html)**: [ra-auth-cognito](https://github.com/marmelab/ra-auth-cognito)
- **[Directus](https://directus.io/)**: [marmelab/ra-directus](https://github.com/marmelab/ra-directus)
- **[Firebase Auth (Google, Facebook, GitHub, etc.)](https://firebase.google.com/docs/auth/web/firebaseui)**: [benwinding/react-admin-firebase](https://github.com/benwinding/react-admin-firebase#auth-provider)
- **[Postgrest](https://postgrest.org/): [raphiniert-com/ra-data-postgrest](https://github.com/raphiniert-com/ra-data-postgrest)
- **[Postgrest](https://postgrest.org/)**: [raphiniert-com/ra-data-postgrest](https://github.com/raphiniert-com/ra-data-postgrest)
- **[Supabase](https://supabase.io/)**: [marmelab/ra-supabase](https://github.com/marmelab/ra-supabase)
- **[Keycloak](https://www.keycloak.org/)**: [marmelab/ra-keycloak](https://github.com/marmelab/ra-keycloak)
- **[Azure Active Directory (using MSAL)](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser)**: [marmelab/ra-auth-msal](https://github.com/marmelab/ra-auth-msal)
Expand Down
49 changes: 21 additions & 28 deletions docs/Theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ Note that you don't need to call `createTheme` yourself. React-admin will do it

Again, to guess the name of the subclass to use (like `.RaDatagrid-headerCell` above) for customizing a component, you can use the developer tools of your browser, or check the react-admin documentation for individual components (e.g. the [Datagrid CSS documentation](./Datagrid.md#sx-css-api)).

You can use this technique to override not only styles, but also default for components. That's how react-admin applies the `filled` variant to all `TextField` components. So for instance, to change the variant to `outlined`, create a custom theme as follows:
You can use this technique to override not only styles, but also defaults for components. That's how react-admin applies the `filled` variant to all `TextField` components. So for instance, to change the variant to `outlined`, create a custom theme as follows:

```jsx
import { defaultTheme } from 'react-admin';
Expand Down Expand Up @@ -367,58 +367,51 @@ const App = () => (

## Letting Users Choose The Theme

The `<ToggleThemeButton>` component lets users switch from light to dark mode, and persists that choice by leveraging the [store](./Store.md).
It's a common practice to support both a light theme and a dark theme in an application, and let users choose which one they prefer.

<video controls autoplay muted loop>
<source src="./img/ToggleThemeButton.webm" type="video/webm"/>
Your browser does not support the video tag.
</video>


You can add the `<ToggleThemeButton>` to a custom App Bar:
React-admin's `<Admin>` component accepts a `darkTheme` prop in addition to the `theme` prop.

```jsx
import * as React from 'react';
import { defaultTheme, Layout, AppBar, ToggleThemeButton, TitlePortal } from 'react-admin';
import { createTheme, Box, Typography } from '@mui/material';
import { Admin, defaultTheme } from 'react-admin';

const darkTheme = createTheme({
palette: { mode: 'dark' },
});
const lightTheme = defaultTheme;
const darkTheme = { ...defaultTheme, palette: { mode: 'dark' } };

const MyAppBar = () => (
<AppBar>
<TitlePortal />
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
</AppBar>
const App = () => (
<Admin
dataProvider={...}
theme={lightTheme}
darkTheme={darkTheme}
>
// ...
</Admin>
);

const MyLayout = props => <Layout {...props} appBar={MyAppBar} />;
```

With this setup, the default application theme depends on the user's system settings. If the user has chosen a dark mode in their OS, react-admin will use the dark theme. Otherwise, it will use the light theme.

In addition, users can switch from one theme to the other using [the `<ToggleThemeButton>` component](./ToggleThemeButton.md), which appears in the AppBar as soon as you define a `darkTheme` prop.

## Changing the Theme Programmatically

React-admin provides the `useTheme` hook to read and update the theme programmatically. It uses the same syntax as `useState`.
Its used internally by `ToggleThemeButton` component.
React-admin provides the `useTheme` hook to read and update the theme programmatically. It uses the same syntax as `useState`. Its used internally by [the `<ToggleThemeButton>` component](./ToggleThemeButton.md).

```jsx
import { defaultTheme, useTheme } from 'react-admin';
import { Button } from '@mui/material';

const lightTheme = defaultTheme;
const darkTheme = {
...defaultTheme,
palette: {
mode: 'dark',
},
};

const ThemeToggler = () => {
const [theme, setTheme] = useTheme();

return (
<Button onClick={() => setTheme(theme.palette.mode === 'dark' ? lightTheme : darkTheme)}>
{theme.palette.mode === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
<Button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
{theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
</Button>
);
}
Expand Down
79 changes: 41 additions & 38 deletions docs/ToggleThemeButton.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,24 @@ The `<ToggleThemeButton>` component lets users switch from light to dark mode, a
Your browser does not support the video tag.
</video>

It is enabled by default in the `<AppBar>` as soon as you define a dark theme via [the `<Admin darkTheme>` prop](./Admin.md#darktheme).

## Usage

You can add the `<ToggleThemeButton>` to a custom App Bar:
You can add the `<ToggleThemeButton>` to a custom [`<AppBar toolbar>`](./AppBar.md#toolbar):

```jsx
import { AppBar, TitlePortal, ToggleThemeButton, defaultTheme } from 'react-admin';

const darkTheme = {
palette: { mode: 'dark' },
};
// in src/MyAppBar.js
import { AppBar, ToggleThemeButton } from 'react-admin';

export const MyAppBar = () => (
<AppBar>
<TitlePortal />
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
</AppBar>>
<AppBar toolbar={<ToggleThemeButton />} />
);
```

Then, pass the custom App Bar in a custom `<Layout>`, and the `<Layout>` to your `<Admin>`:
Then, pass the custom App Bar in a custom `<Layout>`, and the `<Layout>` to your `<Admin>`. The `<Admin>` must define a `darkTheme` prop for the button to work:

{% raw %}
```jsx
import { Admin, Layout } from 'react-admin';

Expand All @@ -42,56 +38,63 @@ import { MyAppBar } from './MyAppBar';
const MyLayout = (props) => <Layout {...props} appBar={MyAppBar} />;

const App = () => (
<Admin dataProvider={dataProvider} layout={MyLayout}>
<Admin
dataProvider={dataProvider}
layout={MyLayout}
darkTheme={{ palette: { mode: 'dark' } }}
>
...
</Admin>
);
```
{% endraw %}

## `darkTheme`
## Removing The Button From The AppBar

Required: A theme object to use when the user chooses the dark mode. It must be serializable to JSON.
The `<ToggleThemeButton>` appears by default in the `<AppBar>` if the `<Admin darkTheme>` prop is defined. If you want to remove it, you need to set a custom [`<AppBar toolbar>` prop](./AppBar.md#toolbar):

```jsx
const darkTheme = {
palette: { mode: 'dark' },
};
// in src/MyAppBar.js
import { AppBar, LocalesMenuButton, RefreshIconButton } from 'react-admin';

<ToggleThemeButton darkTheme={darkTheme} />
export const MyAppBar = () => (
<AppBar toolbar={
<>
<LocalesMenuButton />
{/* no ToggleThemeButton here */}
<RefreshIconButton />
</>
} />
);
```

**Tip**: React-admin calls Material UI's `createTheme()` on this object.
## Creating A Dark Theme

## `lightTheme`
For this button to work, you must provide a dark theme to the `<Admin>` component. The `darkTheme` should be a JSON object that follows the [Material UI theme specification](https://material-ui.com/customization/theming/).

A theme object to use when the user chooses the light mode. It must be serializable to JSON.
You can create such a theme from scratch:

```jsx
const darkTheme = {
palette: { mode: 'dark' },
};
const lightTheme = {
```

Of you can override react-admin's default dark theme:

```jsx
import { defaultDarkTheme } from 'react-admin';

const darkTheme = {
...defaultDarkTheme,
palette: {
type: 'light',
...defaultDarkTheme.palette,
primary: {
main: '#3f51b5',
},
secondary: {
main: '#f50057',
main: '#90caf9',
},
},
};

<ToggleThemeButton lightTheme={lightTheme} darkTheme={darkTheme} />
```

React-admin uses the `<Admin theme>` prop as default theme.

**Tip**: React-admin calls Material UI's `createTheme()` on this object.

## API

* [`ToggleThemeButton`]

[`ToggleThemeButton`]: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/button/ToggleThemeButton.jsx
**Tip**: React-admin calls Material UI's `createTheme()` on the `<Admin darkTheme>` prop - don't call it yourself.

Binary file modified docs/img/AppBar-children.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/dark-theme.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/not-found.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 906af62

Please sign in to comment.