Skip to content

Commit

Permalink
Merge pull request #9479 from marmelab/introduce-default-dark-theme-2
Browse files Browse the repository at this point in the history
Introduce default dark theme
  • Loading branch information
djhi committed Dec 4, 2023
2 parents 80b0edd + c65b7c5 commit 47be3ac
Show file tree
Hide file tree
Showing 37 changed files with 666 additions and 631 deletions.
33 changes: 26 additions & 7 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Here are all the props accepted by the component:
| `basename` | Optional | `string` | - | The base path for all URLs |
| `catchAll` | Optional | `Component` | `NotFound` | The fallback component for unknown routes |
| `dashboard` | Optional | `Component` | - | The content of the dashboard page |
| `darkTheme` | Optional | `object` | - | The dark theme configuration |
| `darkTheme` | Optional | `object` | `default DarkTheme` | The dark theme configuration |
| `defaultTheme` | Optional | `boolean` | `false` | Flag to default to the light theme |
| `disableTelemetry` | Optional | `boolean` | `false` | Set to `true` to disable telemetry collection |
| `i18nProvider` | Optional | `I18NProvider` | - | The internationalization provider for translations |
Expand All @@ -156,7 +156,7 @@ Here are all the props accepted by the component:
| `ready` | Optional | `Component` | `Ready` | The content of the ready page |
| `requireAuth` | Optional | `boolean` | `false` | Flag to require authentication for all routes |
| `store` | Optional | `Store` | - | The Store for managing user preferences |
| `theme` | Optional | `object` | - | The main (light) theme configuration |
| `theme` | Optional | `object` | `default LightTheme` | The main (light) theme configuration |
| `title` | Optional | `string` | - | The error page title |


Expand Down Expand Up @@ -433,29 +433,48 @@ const App = () => (

## `darkTheme`

If you want to support both light and dark mode, you can provide a `darkTheme` in addition to the `theme` prop. The app will use the `darkTheme` by default for users who prefer the dark mode at the OS level, and users will be able to switch from light to dark mode using a new app bar button leveraging [the `<ToggleThemeButton>` component](./ToggleThemeButton.md).
React-admin provides a [built-in dark theme](./AppTheme.md#default). The app will use the `darkTheme` by default for users who prefer the dark mode at the OS level, and users will be able to switch from light to dark mode using [the `<ToggleThemeButton>` component](./ToggleThemeButton.md).

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

If you want to override it, you can provide your own `darkTheme` in addition to the `theme` prop:

```tsx
import { Admin } from 'react-admin';
import { dataProvider } from './dataProvider';
import { darkTheme, lightTheme } from './themes';
import { myDarkTheme } from './themes';

const App = () => (
<Admin
dataProvider={dataProvider}
theme={lightTheme}
darkTheme={darkTheme}
darkTheme={myDarkTheme}
>
...
</Admin>
);
```

If you want to remove the user's ability to switch to dark theme, you can set `darkTheme` to `null`, therefore the `<ToggleThemeButton>` component won't be shown:

```tsx
import { Admin } from 'react-admin';
import { dataProvider } from './dataProvider';

const App = () => (
<Admin
dataProvider={dataProvider}
darkTheme={null}
>
...
</Admin>
);
```

If the `theme` prop is provided and the `darkTheme` prop is not, the dark theme is disabled.

**Tip**: To disable OS preference detection and always use one theme by default, see the [`defaultTheme`](#defaulttheme) prop.

## `defaultTheme`
Expand Down Expand Up @@ -843,7 +862,7 @@ const App = () => (

You can also [write your own theme](./AppTheme.md#writing-a-custom-theme) to fit your company branding. For more details on predefined and custom themes, refer to [the Application Theme chapter](./AppTheme.md).

If you want to support both a light and a dark theme, check out [the `<Admin darkTheme>` prop](#darktheme).
React-admin provides a [built-in dark theme by default](./AppTheme.md#default). If you want to override it, check out [the `<Admin darkTheme>` prop](#darktheme).

## `title`

Expand Down
2 changes: 1 addition & 1 deletion docs/AppBar.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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 button to change the theme (unless the [dark theme](./Admin.md#darktheme) is disabled),
- a loading indicator,
- a button to display the user menu.

Expand Down
23 changes: 16 additions & 7 deletions docs/AppTheme.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,17 @@ It's a common practice to support both a light theme and a dark theme in an appl
Your browser does not support the video tag.
</video>

React-admin provides a [built-in dark theme by default](#default), 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.

React-admin's `<Admin>` component accepts a `darkTheme` prop in addition to the `theme` prop.
In addition, users can switch from one theme to the other using [the `<ToggleThemeButton>` component](./ToggleThemeButton.md), that appears in the AppBar as soon as you define a `darkTheme` prop.

```jsx
import { Admin, defaultTheme } from 'react-admin';
You can override the dark theme by setting the `<Admin>`'s `darkTheme` prop with your own theme:

const lightTheme = defaultTheme;
const darkTheme = { ...defaultTheme, palette: { mode: 'dark' } };
```tsx
import { Admin, defaultDarkTheme, defaultLightTheme } from 'react-admin';

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

const App = () => (
<Admin
Expand All @@ -80,9 +83,15 @@ const App = () => (
);
```

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.
**Tip**: If you don't need the default dark theme, you can set the `<Admin>`'s `darkTheme` prop to `null`:

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.
```tsx
const App = () => (
<Admin darkTheme={null}>
// ...
</Admin>
);
```

## Built-In Themes

Expand Down
25 changes: 20 additions & 5 deletions docs/ToggleThemeButton.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ 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).
It is enabled by default in the `<AppBar>` as React-admin provides a [built-in dark theme](./AppTheme.md#default).

## Usage

Expand Down Expand Up @@ -52,10 +52,23 @@ const App = () => (

## Removing The Button From The AppBar

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):
The `<ToggleThemeButton>` appears by default in the `<AppBar>`. If you want to remove it, you have two solutions:

```jsx
// in src/MyAppBar.js
- you can set the `<Admin>` `darkTheme` prop to `null`:

```tsx
// in src/App.tsx
const App = () => (
<Admin darkTheme={null}>
// ...
</Admin>
);
```

- or you can set a custom [`<AppBar toolbar>` prop](./AppBar.md#toolbar):

```tsx
// in src/MyAppBar.tsx
import { AppBar, LocalesMenuButton, RefreshIconButton } from 'react-admin';

export const MyAppBar = () => (
Expand All @@ -71,7 +84,9 @@ export const MyAppBar = () => (

## Creating A Dark Theme

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/).
For this button to work, you must provide a dark theme to the `<Admin>` component. React-admin provides a [built-in dark theme](./AppTheme.md#default), but you can override it according to your needs.

The `darkTheme` should be a JSON object that follows the [Material UI theme specification](https://material-ui.com/customization/theming/).

You can create such a theme from scratch:

Expand Down
17 changes: 15 additions & 2 deletions docs/Upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,22 @@ If you previously relied on the fact that the rows were not clickable by default
</Datagrid>
```

## Dark Theme Is Available By Default

In addition to the light theme, React-admin v5 includes a [dark theme](https://marmelab.com/react-admin/AppTheme.html#light-and-dark-themes), renders a theme switcher in the app bar, and chooses the default theme based on the user OS preferences.

If you don't need the dark mode feature, you'll have to explicitly disable it:

```diff
-<Admin>
+<Admin darkTheme={null}>
...
</Admin>
```

## Links are now underlined by default
In the default theme, links are now underlined by default.

In the default theme, links are now underlined by default.

If you use the `<Link>` component from `react-admin`, and you want to remove the underline, set the `underline` prop to `none`:

Expand Down
35 changes: 18 additions & 17 deletions packages/ra-ui-materialui/src/AdminContext.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import { Typography, ThemeOptions } from '@mui/material';
import { Typography } from '@mui/material';
import expect from 'expect';
import { memoryStore } from 'ra-core';

import { AdminContext } from './AdminContext';
import { ThemeTestWrapper } from './layout/ThemeTestWrapper';

const lightTheme: ThemeOptions = {};
const darkTheme: ThemeOptions = { palette: { mode: 'dark' } };
import { defaultLightTheme } from './theme';

const LIGHT_MODE_TEXT_COLOR = 'rgb(25, 118, 210)'; // text is dark blue in light mode
const DARK_MODE_TEXT_COLOR = 'rgb(144, 202, 249)'; // text is light blue in dark mode

describe('AdminContext', () => {
it('should default to light theme', () => {
render(
<AdminContext theme={lightTheme} darkTheme={darkTheme}>
<AdminContext>
<Typography color="primary">Test</Typography>
</AdminContext>
);
Expand All @@ -26,7 +24,7 @@ describe('AdminContext', () => {
it('should default to dark theme when the browser detects a dark mode preference', () => {
render(
<ThemeTestWrapper mode="dark">
<AdminContext theme={lightTheme} darkTheme={darkTheme}>
<AdminContext>
<Typography color="primary">Test</Typography>
</AdminContext>
</ThemeTestWrapper>
Expand All @@ -37,7 +35,7 @@ describe('AdminContext', () => {
it('should default to light theme when the browser detects a dark mode preference', () => {
render(
<ThemeTestWrapper mode="light">
<AdminContext theme={lightTheme} darkTheme={darkTheme}>
<AdminContext>
<Typography color="primary">Test</Typography>
</AdminContext>
</ThemeTestWrapper>
Expand All @@ -47,11 +45,7 @@ describe('AdminContext', () => {
});
it('should default to dark theme when user preference is dark', () => {
render(
<AdminContext
theme={lightTheme}
darkTheme={darkTheme}
store={memoryStore({ theme: 'dark' })}
>
<AdminContext store={memoryStore({ theme: 'dark' })}>
<Typography color="primary">Test</Typography>
</AdminContext>
);
Expand All @@ -61,11 +55,18 @@ describe('AdminContext', () => {
it('should default to light theme when user preference is light', () => {
render(
<ThemeTestWrapper mode="dark">
<AdminContext
theme={lightTheme}
darkTheme={darkTheme}
store={memoryStore({ theme: 'light' })}
>
<AdminContext store={memoryStore({ theme: 'light' })}>
<Typography color="primary">Test</Typography>
</AdminContext>
</ThemeTestWrapper>
);
const text = screen.getByText('Test');
expect(getComputedStyle(text).color).toBe(LIGHT_MODE_TEXT_COLOR);
});
it('should only use main theme even the browser detects a dark mode preference', () => {
render(
<ThemeTestWrapper mode="dark">
<AdminContext theme={defaultLightTheme}>
<Typography color="primary">Test</Typography>
</AdminContext>
</ThemeTestWrapper>
Expand Down
13 changes: 11 additions & 2 deletions packages/ra-ui-materialui/src/AdminContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ThemesContext,
RaThemeOptions,
defaultLightTheme,
defaultDarkTheme,
} from './theme';

export const AdminContext = (props: AdminContextProps) => {
Expand All @@ -22,7 +23,12 @@ export const AdminContext = (props: AdminContextProps) => {
<ThemesContext.Provider
value={{
lightTheme: theme || lightTheme,
darkTheme,
darkTheme:
theme && !darkTheme
? null
: !darkTheme && darkTheme !== null
? defaultDarkTheme
: darkTheme,
defaultTheme,
}}
>
Expand Down Expand Up @@ -56,13 +62,16 @@ export interface AdminContextProps extends CoreAdminContextProps {

/**
* The material-UI theme to customize the UI. Prefer the theme prop.
* If not provided, the default light theme is used.
*
* @see https://marmelab.com/react-admin/Admin.html#theme
*/
lightTheme?: RaThemeOptions;

/**
* The material-UI theme to apply to the UI when the dark mode is activated.
* If not provided, the default dark theme is used.
* If set to null, the dark mode is disabled.
*
* @see https://marmelab.com/react-admin/Admin.html#darktheme
* @example
Expand All @@ -80,7 +89,7 @@ export interface AdminContextProps extends CoreAdminContextProps {
* </Admin>
* );
*/
darkTheme?: RaThemeOptions;
darkTheme?: RaThemeOptions | null;

/**
* The default theme to use when the user hasn't chosen a theme yet.
Expand Down
10 changes: 7 additions & 3 deletions packages/ra-ui-materialui/src/button/SortButton.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@ const PlayerList = () => (
);

export const Basic = () => (
<AdminContext dataProvider={dataProvider}>
<AdminContext dataProvider={dataProvider} defaultTheme="light">
<Resource name="players" list={PlayerList} />
</AdminContext>
);

export const I18N = () => (
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<AdminContext
dataProvider={dataProvider}
defaultTheme="light"
i18nProvider={i18nProvider}
>
<Resource name="players" list={PlayerList} />
</AdminContext>
);
Expand Down Expand Up @@ -92,7 +96,7 @@ const PlayerListSX = () => (
);

export const SX = () => (
<AdminContext dataProvider={dataProvider}>
<AdminContext dataProvider={dataProvider} defaultTheme="light">
<Resource name="players" list={PlayerListSX} />
</AdminContext>
);
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,7 @@ const BookList = () => (
);

export const Basic = () => (
<Admin
store={memoryStore()}
dataProvider={dataProvider}
history={history}
darkTheme={{ palette: { mode: 'dark' } }}
>
<Admin store={memoryStore()} dataProvider={dataProvider} history={history}>
<Resource name="books" list={BookList} />
</Admin>
);
Expand Down
Loading

0 comments on commit 47be3ac

Please sign in to comment.