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

Introduce default dark theme #9479

Merged
merged 24 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b03ba63
doc: introduce documentation (wip)
adguernier Nov 28, 2023
360bba8
add the default darktheme in <Admin>
adguernier Nov 28, 2023
a62a03f
add a story to demonstrate a custome light and dark theme
adguernier Nov 29, 2023
7c8ef33
add JSdoc to explain priority
adguernier Nov 29, 2023
735395f
JSdoc precisions about theme priorities
adguernier Nov 29, 2023
5dbd8eb
doc: enhance documentation about darktheme
adguernier Nov 29, 2023
ad6f74d
fix a typo
adguernier Nov 29, 2023
d1441cf
doc: add a section to upgrade guide about default dark theme
adguernier Nov 29, 2023
a535fd3
apply reviews
adguernier Nov 29, 2023
690448c
Fix props table
fzaninotto Nov 29, 2023
73f31b5
remove confusing lightTheme documentation
adguernier Dec 4, 2023
5b6a0fd
doc: default value for the theme to defaultLightTheme
adguernier Dec 4, 2023
3a7b3cc
doc: correct sentence
adguernier Dec 4, 2023
f713d26
improve upgrade guide for default dark theme
adguernier Dec 4, 2023
fb00163
remove JSDoc
adguernier Dec 4, 2023
6de9973
doc: remove them priorities
adguernier Dec 4, 2023
67a6ed6
remove useless darkTheme usage in stories
adguernier Dec 4, 2023
017ea11
update AdminContext spec
adguernier Dec 4, 2023
7873995
add a spec to test main theme priority
adguernier Dec 4, 2023
b19144b
Merge branch 'introduce-default-dark-theme-2' of github.com:marmelab/…
adguernier Dec 4, 2023
457eefc
Merge branch 'next' into introduce-default-dark-theme-2
adguernier Dec 4, 2023
ab9b129
Small edit
fzaninotto Dec 4, 2023
e44f99e
Default stories to light theme
fzaninotto Dec 4, 2023
c65b7c5
[no ci] Simplify upgrade guide
fzaninotto Dec 4, 2023
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
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 default built-in dark theme) | 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` | `defaultLightTheme` (the default built-in light theme) | 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
22 changes: 22 additions & 0 deletions docs/Upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ 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>
```

adguernier marked this conversation as resolved.
Show resolved Hide resolved
If the `theme` prop is defined but not the `darkTheme` prop, theme switcher button in the app bar isn't displayed:
adguernier marked this conversation as resolved.
Show resolved Hide resolved

```diff
-<Admin>
+<Admin theme={myMainTheme}>
...
</Admin>
```

## Upgrading to v4

If you are on react-admin v3, follow the [Upgrading to v4](https://marmelab.com/react-admin/doc/4.16/Upgrade.html) guide before upgrading to v5.
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
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
7 changes: 1 addition & 6 deletions packages/ra-ui-materialui/src/layout/Menu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as React from 'react';
import { Resource, CustomRoutes, testDataProvider, memoryStore } from 'ra-core';
import { defaultTheme, Admin, useSidebarState } from 'react-admin';
import { Admin, useSidebarState } from 'react-admin';
import {
Typography,
Skeleton,
ThemeOptions,
MenuItem,
ListItemText,
ListItemIcon,
Expand All @@ -30,8 +29,6 @@ export default { title: 'ra-ui-materialui/layout/Menu' };

const resources = ['Posts', 'Comments', 'Tags', 'Users', 'Orders', 'Reviews'];

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

const DemoList = ({ name }) => (
<>
<Title title={name} />
Expand All @@ -48,7 +45,6 @@ export const Default = () => {
store={memoryStore()}
dataProvider={testDataProvider()}
layout={DefaultLayout}
darkTheme={darkTheme}
>
{resources.map((resource, index) => (
<Resource
Expand All @@ -70,7 +66,6 @@ export const Dense = () => {
store={memoryStore()}
dataProvider={testDataProvider()}
layout={LayoutDense}
darkTheme={darkTheme}
>
{resources.map((resource, index) => (
<Resource
Expand Down
Loading
Loading