Skip to content

Commit 906af62

Browse files
authored
Merge pull request #8874 from marmelab/theme-dark-mode
Add ability to default to dark mode when users prefer it
2 parents 14e9c3d + 1c26c54 commit 906af62

35 files changed

Lines changed: 1230 additions & 680 deletions

docs/Admin.md

Lines changed: 443 additions & 251 deletions
Large diffs are not rendered by default.

docs/AppBar.md

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ By default, the `<AppBar>` component displays:
1717
- a hamburger icon to toggle the sidebar width,
1818
- the page title,
1919
- a button to change locales (if the application uses [i18n](./Translation.md)),
20+
- a button to change the theme (if the application uses a [dark theme](./Admin.md#darktheme)),
2021
- a loading indicator,
2122
- a button to display the user menu.
2223

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

7576
## `children`
7677

77-
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.
78+
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.
7879

7980
```jsx
8081
// in src/MyAppBar.js
81-
import {
82-
AppBar,
83-
TitlePortal,
84-
ToggleThemeButton,
85-
defaultTheme,
86-
} from 'react-admin';
82+
import { AppBar, TitlePortal } from 'react-admin';
83+
import SettingsIcon from '@mui/icons-material/Settings';
84+
import { IconButton } from '@mui/material';
8785

88-
const darkTheme = { palette: { mode: 'dark' } };
86+
const SettingsButton = () => (
87+
<IconButton color="inherit">
88+
<SettingsIcon />
89+
</IconButton>
90+
);
8991

9092
export const MyAppBar = () => (
9193
<AppBar>
9294
<TitlePortal />
93-
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
95+
<SettingsButton />
9496
</AppBar>
9597
);
9698
```
9799

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

100102
**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.
101103

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

167169
## `toolbar`
168170

169-
By default, the `<AppBar>` renders two buttons in addition to the user menu: the language menu and the refresh button.
171+
By default, the `<AppBar>` renders three buttons in addition to the user menu:
172+
173+
- the [language menu button](./LocalesMenuButton.md),
174+
- the [theme toggle button](./ToggleThemeButton.md),
175+
- and [the refresh button](./Buttons.md#refreshbutton).
170176

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

@@ -177,36 +183,37 @@ import {
177183
LocalesMenuButton,
178184
RefreshIconButton,
179185
ToggleThemeButton,
180-
defaultTheme,
181186
} from 'react-admin';
182187

183-
const darkTheme = {
184-
palette: { mode: 'dark' },
185-
};
186-
187188
export const MyAppBar = () => (
188189
<AppBar toolbar={
189190
<>
190191
<LocalesMenuButton />
191-
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
192+
<ToggleThemeButton />
192193
<RefreshIconButton />
193194
</>
194195
} />
195196
);
196197
```
197198

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

200201
```jsx
201202
// in src/MyAppBar.js
202-
import { AppBar, TitlePortal, ToggleThemeButton, defaultTheme } from 'react-admin';
203+
import { AppBar, TitlePortal } from 'react-admin';
204+
import SettingsIcon from '@mui/icons-material/Settings';
205+
import { IconButton } from '@mui/material';
203206

204-
const darkTheme = { palette: { mode: 'dark' } };
207+
const SettingsButton = () => (
208+
<IconButton color="inherit">
209+
<SettingsIcon />
210+
</IconButton>
211+
);
205212

206213
export const MyAppBar = () => (
207214
<AppBar>
208215
<TitlePortal />
209-
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
216+
<SettingsButton />
210217
</AppBar>
211218
);
212219
```
@@ -346,23 +353,24 @@ export const i18nProvider = {
346353

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

349-
For instance, to add `<ToggleThemeButton>`:
356+
For instance, to add a settings button:
350357

351358
```jsx
352359
// in src/MyAppBar.js
353-
import {
354-
AppBar,
355-
TitlePortal,
356-
ToggleThemeButton,
357-
defaultTheme,
358-
} from 'react-admin';
360+
import { AppBar, TitlePortal } from 'react-admin';
361+
import SettingsIcon from '@mui/icons-material/Settings';
362+
import { IconButton } from '@mui/material';
359363

360-
const darkTheme = { palette: { mode: 'dark' } };
364+
const SettingsButton = () => (
365+
<IconButton color="inherit">
366+
<SettingsIcon />
367+
</IconButton>
368+
);
361369

362370
export const MyAppBar = () => (
363371
<AppBar>
364372
<TitlePortal />
365-
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
373+
<SettingsButton />
366374
</AppBar>
367375
);
368376
```

docs/AuthProviderList.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ It's very common that your auth logic is so specific that you'll need to write y
1212
- **[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)
1313
- **[Directus](https://directus.io/)**: [marmelab/ra-directus](https://github.com/marmelab/ra-directus)
1414
- **[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)
15-
- **[Postgrest](https://postgrest.org/): [raphiniert-com/ra-data-postgrest](https://github.com/raphiniert-com/ra-data-postgrest)
15+
- **[Postgrest](https://postgrest.org/)**: [raphiniert-com/ra-data-postgrest](https://github.com/raphiniert-com/ra-data-postgrest)
1616
- **[Supabase](https://supabase.io/)**: [marmelab/ra-supabase](https://github.com/marmelab/ra-supabase)
1717
- **[Keycloak](https://www.keycloak.org/)**: [marmelab/ra-keycloak](https://github.com/marmelab/ra-keycloak)
1818
- **[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)

docs/Theming.md

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ Note that you don't need to call `createTheme` yourself. React-admin will do it
243243

244244
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)).
245245

246-
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:
246+
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:
247247

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

368368
## Letting Users Choose The Theme
369369

370-
The `<ToggleThemeButton>` component lets users switch from light to dark mode, and persists that choice by leveraging the [store](./Store.md).
370+
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.
371371

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

377377

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

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

385-
const darkTheme = createTheme({
386-
palette: { mode: 'dark' },
387-
});
383+
const lightTheme = defaultTheme;
384+
const darkTheme = { ...defaultTheme, palette: { mode: 'dark' } };
388385

389-
const MyAppBar = () => (
390-
<AppBar>
391-
<TitlePortal />
392-
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
393-
</AppBar>
386+
const App = () => (
387+
<Admin
388+
dataProvider={...}
389+
theme={lightTheme}
390+
darkTheme={darkTheme}
391+
>
392+
// ...
393+
</Admin>
394394
);
395-
396-
const MyLayout = props => <Layout {...props} appBar={MyAppBar} />;
397395
```
398396

397+
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.
398+
399+
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.
400+
399401
## Changing the Theme Programmatically
400402

401-
React-admin provides the `useTheme` hook to read and update the theme programmatically. It uses the same syntax as `useState`.
402-
Its used internally by `ToggleThemeButton` component.
403+
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).
403404

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

408-
const lightTheme = defaultTheme;
409-
const darkTheme = {
410-
...defaultTheme,
411-
palette: {
412-
mode: 'dark',
413-
},
414-
};
415-
416409
const ThemeToggler = () => {
417410
const [theme, setTheme] = useTheme();
418411

419412
return (
420-
<Button onClick={() => setTheme(theme.palette.mode === 'dark' ? lightTheme : darkTheme)}>
421-
{theme.palette.mode === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
413+
<Button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
414+
{theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
422415
</Button>
423416
);
424417
}

docs/ToggleThemeButton.md

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,24 @@ The `<ToggleThemeButton>` component lets users switch from light to dark mode, a
1212
Your browser does not support the video tag.
1313
</video>
1414

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

1617
## Usage
1718

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

2021
```jsx
21-
import { AppBar, TitlePortal, ToggleThemeButton, defaultTheme } from 'react-admin';
22-
23-
const darkTheme = {
24-
palette: { mode: 'dark' },
25-
};
22+
// in src/MyAppBar.js
23+
import { AppBar, ToggleThemeButton } from 'react-admin';
2624

2725
export const MyAppBar = () => (
28-
<AppBar>
29-
<TitlePortal />
30-
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
31-
</AppBar>>
26+
<AppBar toolbar={<ToggleThemeButton />} />
3227
);
3328
```
3429

35-
Then, pass the custom App Bar in a custom `<Layout>`, and the `<Layout>` to your `<Admin>`:
30+
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:
3631

32+
{% raw %}
3733
```jsx
3834
import { Admin, Layout } from 'react-admin';
3935

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

4440
const App = () => (
45-
<Admin dataProvider={dataProvider} layout={MyLayout}>
41+
<Admin
42+
dataProvider={dataProvider}
43+
layout={MyLayout}
44+
darkTheme={{ palette: { mode: 'dark' } }}
45+
>
4646
...
4747
</Admin>
4848
);
4949
```
50+
{% endraw %}
5051

51-
## `darkTheme`
52+
## Removing The Button From The AppBar
5253

53-
Required: A theme object to use when the user chooses the dark mode. It must be serializable to JSON.
54+
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):
5455

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

60-
<ToggleThemeButton darkTheme={darkTheme} />
60+
export const MyAppBar = () => (
61+
<AppBar toolbar={
62+
<>
63+
<LocalesMenuButton />
64+
{/* no ToggleThemeButton here */}
65+
<RefreshIconButton />
66+
</>
67+
} />
68+
);
6169
```
6270

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

65-
## `lightTheme`
73+
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/).
6674

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

6977
```jsx
7078
const darkTheme = {
7179
palette: { mode: 'dark' },
7280
};
73-
const lightTheme = {
81+
```
82+
83+
Of you can override react-admin's default dark theme:
84+
85+
```jsx
86+
import { defaultDarkTheme } from 'react-admin';
87+
88+
const darkTheme = {
89+
...defaultDarkTheme,
7490
palette: {
75-
type: 'light',
91+
...defaultDarkTheme.palette,
7692
primary: {
77-
main: '#3f51b5',
78-
},
79-
secondary: {
80-
main: '#f50057',
93+
main: '#90caf9',
8194
},
8295
},
8396
};
84-
85-
<ToggleThemeButton lightTheme={lightTheme} darkTheme={darkTheme} />
8697
```
8798

88-
React-admin uses the `<Admin theme>` prop as default theme.
89-
90-
**Tip**: React-admin calls Material UI's `createTheme()` on this object.
91-
92-
## API
93-
94-
* [`ToggleThemeButton`]
95-
96-
[`ToggleThemeButton`]: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/button/ToggleThemeButton.jsx
99+
**Tip**: React-admin calls Material UI's `createTheme()` on the `<Admin darkTheme>` prop - don't call it yourself.
97100

docs/img/AppBar-children.png

12.1 KB
Loading

docs/img/dark-theme.png

191 KB
Loading

docs/img/not-found.png

11 KB
Loading

0 commit comments

Comments
 (0)