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 ability to default to dark mode when users prefer it #8874

Merged
merged 21 commits into from
May 9, 2023
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
694 changes: 443 additions & 251 deletions docs/Admin.md
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved and rewrote a lot of things in there. Best to read the new version rather than read the diff.

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