From 6160f3328f4547b7a246f021d8422e09842d456b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Thu, 18 Jan 2024 00:02:35 +0100 Subject: [PATCH 1/9] Update Layout to require only children prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The Layout component is the last one requiring props cloning in user land: ```jsx // in src/MyLayout.js import { Layout } from 'react-admin'; import { MyAppBar } from './MyAppBar'; export const MyLayout = props => ( ); ``` Developers wonder: what is props? Why do I have to pass it down? It’s not so usual in modern React. ## Solution Use a context instead, and just pass the children down (which requires no explaining). ```jsx // in src/MyLayout.js import { Layout } from 'react-admin'; import { MyAppBar } from './MyAppBar'; export const MyLayout = ({ children }) => ( {children} ); ``` --- docs/Admin.md | 6 +- docs/AppBar.md | 14 ++- docs/AppTheme.md | 6 +- docs/Architecture.md | 6 +- docs/AuthRBAC.md | 6 +- docs/Authentication.md | 6 +- docs/Buttons.md | 6 +- docs/CheckForApplicationUpdate.md | 34 +++--- docs/CreateReactApp.md | 7 +- docs/CustomRoutes.md | 6 +- docs/DataProviders.md | 8 +- docs/Features.md | 6 +- docs/IconMenu.md | 6 +- docs/Layout.md | 87 ++++++++------- docs/LocalesMenuButton.md | 6 +- docs/Menu.md | 24 ++-- docs/MenuLive.md | 9 +- docs/MultiLevelMenu.md | 8 +- docs/Realtime.md | 6 +- docs/Routing.md | 4 +- docs/SX.md | 5 +- docs/Search.md | 18 ++- docs/SearchWithResult.md | 8 +- docs/SolarLayout.md | 103 ++++++++++-------- docs/ToggleThemeButton.md | 7 +- docs/Vite.md | 7 +- docs/canAccess.md | 6 +- docs/useLogout.md | 6 +- examples/crm/src/Layout.tsx | 10 +- examples/demo/src/layout/Layout.tsx | 8 +- examples/simple/src/Layout.tsx | 4 +- examples/simple/src/posts/PostList.tsx | 22 ++-- .../templates/common/src/Layout.tsx | 6 +- packages/ra-core/src/auth/usePermissions.ts | 6 +- .../src/controller/create/CreateBase.tsx | 4 +- packages/ra-core/src/core/CoreAdmin.tsx | 2 - packages/ra-core/src/core/CoreAdminRoutes.tsx | 20 ++-- packages/ra-core/src/core/CoreAdminUI.tsx | 80 +++++++------- .../ra-core/src/core/DefaultTitleContext.ts | 8 ++ .../ra-core/src/core/HasDashboardContext.ts | 7 ++ packages/ra-core/src/core/index.ts | 2 + .../ra-core/src/form/FormDataConsumer.tsx | 8 +- .../src/form/FormGroupContextProvider.tsx | 4 +- packages/ra-core/src/form/useFormGroup.ts | 4 +- packages/ra-core/src/types.ts | 13 +-- packages/ra-no-code/src/ui/Layout.tsx | 10 +- .../src/button/LocalesMenuButton.stories.tsx | 4 +- .../src/button/ToggleThemeButton.stories.tsx | 4 +- .../src/button/ToggleThemeButton.tsx | 6 +- .../src/button/ToggleThemeLegacyButton.tsx | 6 +- .../ra-ui-materialui/src/detail/Create.tsx | 4 +- packages/ra-ui-materialui/src/detail/Edit.tsx | 4 +- packages/ra-ui-materialui/src/detail/Tab.tsx | 4 +- .../ra-ui-materialui/src/form/SimpleForm.tsx | 4 +- .../ra-ui-materialui/src/form/TabbedForm.tsx | 4 +- .../ra-ui-materialui/src/input/DateInput.tsx | 4 +- .../src/input/ReferenceArrayInput.tsx | 4 +- .../src/input/ReferenceInput.tsx | 8 +- .../ra-ui-materialui/src/input/TimeInput.tsx | 4 +- .../ra-ui-materialui/src/layout/AppBar.tsx | 16 +-- .../src/layout/CheckForApplicationUpdate.tsx | 8 +- .../ra-ui-materialui/src/layout/Error.tsx | 10 +- .../src/layout/Layout.stories.tsx | 14 ++- .../ra-ui-materialui/src/layout/Layout.tsx | 23 +--- .../src/layout/Menu.stories.tsx | 16 ++- packages/ra-ui-materialui/src/layout/Menu.tsx | 23 +--- .../src/layout/MenuItemLink.tsx | 6 +- .../ra-ui-materialui/src/layout/NotFound.tsx | 5 +- .../ra-ui-materialui/src/layout/Title.tsx | 4 +- .../src/list/InfiniteList.stories.tsx | 4 +- 70 files changed, 461 insertions(+), 367 deletions(-) create mode 100644 packages/ra-core/src/core/DefaultTitleContext.ts create mode 100644 packages/ra-core/src/core/HasDashboardContext.ts diff --git a/docs/Admin.md b/docs/Admin.md index 03917b22afa..be3011e7892 100644 --- a/docs/Admin.md +++ b/docs/Admin.md @@ -598,7 +598,11 @@ Layout components can be customized via props. For instance, you can pass a cust import { Layout } from 'react-admin'; import MyMenu from './MyMenu'; -export const MyLayout = (props) => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` Then, pass it to the `` component as the `layout` prop: diff --git a/docs/AppBar.md b/docs/AppBar.md index 6d34cdbabeb..33e88bcde9b 100644 --- a/docs/AppBar.md +++ b/docs/AppBar.md @@ -43,10 +43,13 @@ Then, create a custom layout based on react-admin's ``: ```jsx // in src/MyLayout.js import { Layout } from 'react-admin'; - import { MyAppBar } from './MyAppBar'; -export const MyLayout = props => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` Then pass this custom layout to the `` component: @@ -463,11 +466,12 @@ Then, use your custom app bar in a custom `` component: ```jsx // in src/MyLayout.js import { Layout } from 'react-admin'; - import { MyAppBar } from './MyAppBar'; -export const MyLayout = (props) => ( - +export const MyLayout = ({ children }) => ( + + {children} + ); ``` diff --git a/docs/AppTheme.md b/docs/AppTheme.md index cb3ef3ec006..4a34ea4ab63 100644 --- a/docs/AppTheme.md +++ b/docs/AppTheme.md @@ -352,7 +352,11 @@ const MySidebar = (props) => ( /> ); -const MyLayout = props => +const MyLayout = ({ children }) => ( + + {children} + +); ``` {% endraw %} diff --git a/docs/Architecture.md b/docs/Architecture.md index b174a44478c..11f46e9fefb 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -200,7 +200,11 @@ The trade-off with this approach is that sometimes react-admin may require you t import { Layout } from 'react-admin'; import { Menu } from './Menu'; -export const Layout = (props) => ; +export const Layout = ({ children }) => ( + + {children} + +); // in src/App.js import { Layout } from './Layout'; diff --git a/docs/AuthRBAC.md b/docs/AuthRBAC.md index 6f37cd27ead..eb7f7433d0b 100644 --- a/docs/AuthRBAC.md +++ b/docs/AuthRBAC.md @@ -640,7 +640,11 @@ const authProvider= { }), }; -const CustomLayout = props => ; +const CustomLayout = ({ children }) => ( + + {children} + +); const App = () => ( diff --git a/docs/Authentication.md b/docs/Authentication.md index b3f003c6f13..95d184bfa96 100644 --- a/docs/Authentication.md +++ b/docs/Authentication.md @@ -396,7 +396,11 @@ const MyUserMenu = () => ; const MyAppBar = () => } />; -const MyLayout = (props) => ; +const MyLayout = ({ children }) => ( + + {children} + +); const App = () => ( diff --git a/docs/Buttons.md b/docs/Buttons.md index f7169dae28a..b60f5b844f3 100644 --- a/docs/Buttons.md +++ b/docs/Buttons.md @@ -800,7 +800,11 @@ To use this custom menu component, pass it to a custom Layout: import { Layout } from 'react-admin'; import { Menu } from './Menu'; -export const Layout = (props) => ; +export const Layout = ({ children }) => ( + + {children} + +); ``` Then, use this layout in the `` `layout` prop: diff --git a/docs/CheckForApplicationUpdate.md b/docs/CheckForApplicationUpdate.md index e78b1647dfa..7ac7660792b 100644 --- a/docs/CheckForApplicationUpdate.md +++ b/docs/CheckForApplicationUpdate.md @@ -17,10 +17,11 @@ Include this component in a custom layout: ```tsx // in src/MyLayout.tsx -import { CheckForApplicationUpdate, Layout, LayoutProps } from 'react-admin'; +import type { ReactNode } from 'react'; +import { CheckForApplicationUpdate, Layout } from 'react-admin'; -export const MyLayout = ({ children, ...props }: LayoutProps) => ( - +export const MyLayout = ({ children }: { children: ReactNode}) => ( + {children} @@ -56,12 +57,13 @@ You can customize the interval between each check by providing the `interval` pr ```tsx // in src/MyLayout.tsx -import { CheckForApplicationUpdate, Layout, LayoutProps } from 'react-admin'; +import type { ReactNode } from 'react'; +import { CheckForApplicationUpdate, Layout } from 'react-admin'; const HALF_HOUR = 30 * 60 * 1000; -export const MyLayout = ({ children, ...props }: LayoutProps) => ( - +export const MyLayout = ({ children }: { children: ReactNode}) => ( + {children} @@ -74,10 +76,11 @@ You can dynamically disable the automatic application update detection by provid ```tsx // in src/MyLayout.tsx -import { CheckForApplicationUpdate, Layout, LayoutProps } from 'react-admin'; +import type { ReactNode } from 'react'; +import { CheckForApplicationUpdate, Layout } from 'react-admin'; -export const MyLayout = ({ children, ...props }: LayoutProps) => ( - +export const MyLayout = ({ children }: { children: ReactNode}) => ( + {children} @@ -91,7 +94,7 @@ Note that you must wrap your component with `forwardRef`. ```tsx // in src/MyLayout.tsx -import { forwardRef } from 'react'; +import { forwardRef, ReactNode } from 'react'; import { Layout, CheckForApplicationUpdate } from 'react-admin'; const CustomAppUpdatedNotification = forwardRef((props, ref) => ( @@ -112,8 +115,8 @@ const CustomAppUpdatedNotification = forwardRef((props, ref) => ( )); -const MyLayout = ({ children, ...props }) => ( - +const MyLayout = ({ children }: { children: ReactNode}) => ( + {children} }/> @@ -163,12 +166,13 @@ You can customize the URL fetched to detect updates by providing the `url` prop. ```tsx // in src/MyLayout.tsx -import { CheckForApplicationUpdate, Layout, LayoutProps } from 'react-admin'; +import type { ReactNode } from 'react'; +import { CheckForApplicationUpdate, Layout } from 'react-admin'; const MY_APP_ROOT_URL = 'https://admin.mycompany.com'; -export const MyLayout = ({ children, ...props }: LayoutProps) => ( - +export const MyLayout = ({ children }: { children: ReactNode}) => ( + {children} diff --git a/docs/CreateReactApp.md b/docs/CreateReactApp.md index 5f42f94f857..dde3499e189 100644 --- a/docs/CreateReactApp.md +++ b/docs/CreateReactApp.md @@ -77,10 +77,11 @@ To enable it, start by creating a custom layout: ```tsx // in src/admin/MyLayout.tsx -import { CheckForApplicationUpdate, Layout, LayoutProps } from 'react-admin'; +import type { ReactNode } from 'react'; +import { CheckForApplicationUpdate, Layout } from 'react-admin'; -export const MyLayout = ({ children, ...props }: LayoutProps) => ( - +export const MyLayout = ({ children }: { children: ReactNode}) => ( + {children} diff --git a/docs/CustomRoutes.md b/docs/CustomRoutes.md index 645093dfed5..e8eb0929bb7 100644 --- a/docs/CustomRoutes.md +++ b/docs/CustomRoutes.md @@ -158,7 +158,11 @@ Next, pass the custom menu to a custom `` component: import { Layout } from 'react-admin'; import { MyMenu } from './MyMenu'; -export const MyLayout = (props) => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` Finally, pass the custom `` component to ``: diff --git a/docs/DataProviders.md b/docs/DataProviders.md index 5c7f10b8d1d..644cf9a3ac3 100644 --- a/docs/DataProviders.md +++ b/docs/DataProviders.md @@ -124,11 +124,11 @@ To enable these devtools, add the `` component to a custom L import { Layout } from 'react-admin'; import { ReactQueryDevtools } from 'react-query/devtools'; -export const MyLayout = props => ( - <> - +export const MyLayout = ({ children }) => ( + + {children} - + ); ``` diff --git a/docs/Features.md b/docs/Features.md index ebb0f9c49bb..c22131a5055 100644 --- a/docs/Features.md +++ b/docs/Features.md @@ -1191,8 +1191,10 @@ import { MenuLive } from '@react-admin/ra-realtime'; import { PostList, PostShow, PostEdit, realTimeDataProvider } from '.'; -const CustomLayout = (props) => ( - +const CustomLayout = ({ children }) => ( + + {children} + ); const MyReactAdmin = () => ( diff --git a/docs/IconMenu.md b/docs/IconMenu.md index b93f7b3a71d..b48e76cf18a 100644 --- a/docs/IconMenu.md +++ b/docs/IconMenu.md @@ -45,9 +45,11 @@ import { AppLocationContext } from '@react-admin/ra-navigation'; import { MyMenu } from './MyMenu'; -export const MyLayout = (props) => ( +export const MyLayout = ({ children }) => ( - + + {children} + ); ``` diff --git a/docs/Layout.md b/docs/Layout.md index d4f43aa49ec..ceb770be7be 100644 --- a/docs/Layout.md +++ b/docs/Layout.md @@ -13,7 +13,6 @@ The default react-admin layout renders a horizontal app bar at the top, a naviga Your browser does not support the video tag. - In addition, the layout renders the menu as a dropdown on mobile. - React-admin lets you override the app layout using [the `` prop](./Admin.md#layout). You can use any component you want as layout ; but if you just need to tweak the default layout, you can use the `` component. ## Usage -Create a custom layout overriding some of the components of the default layout: +Create a custom layout overriding some of the props of the default layout. Remember to pass down the `children` prop: ```jsx // in src/MyLayout.js @@ -35,7 +33,11 @@ import { Layout } from 'react-admin'; import { MyAppBar } from './MyAppBar'; -export const MyLayout = props => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` Then pass this custom layout to the `` component: @@ -57,6 +59,7 @@ const App = () => ( | Prop | Required | Type | Default | Description | | ---------------- | -------- | ----------- | -------- | ----------------------------------------------------------------------- | +| `children` | Required | `Element` | - | The content of the layout | | `appBar` | Optional | `Component` | - | A React component rendered at the top of the layout | | `appBarAlwaysOn` | Optional | `boolean` | - | When true, the app bar is always visible | | `className` | Optional | `string` | - | Passed to the root `
` component | @@ -65,23 +68,6 @@ const App = () => ( | `sidebar` | Optional | `Component` | - | A React component responsible for rendering the menu (e.g. in a drawer) | | `sx` | Optional | `SxProps` | - | Style overrides, powered by MUI System | -React-admin injects more props at runtime based on the `` props: - -* `dashboard`: The dashboard component. Used to enable the dahboard link in the menu -* `title`: The default page tile, rendered in the AppBar for error pages -* `children`: The main content of the page - -Any value set for these props in a custom layout will be ignored. That's why you're supposed to pass down the props when creating a layout based on ``: - -```jsx -// in src/MyLayout.js -import { Layout } from 'react-admin'; - -import { MyAppBar } from './MyAppBar'; - -export const MyLayout = props => ; -``` - ## `appBar` Lets you override the top App Bar. @@ -93,7 +79,11 @@ import { Layout } from 'react-admin'; import { MyAppBar } from './MyAppBar'; -export const MyLayout = (props) => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` You can use [react-admin's `` component](./AppBar.md) as a base for your custom app bar, or the component of your choice. @@ -135,7 +125,11 @@ By default, the app bar is hidden when the user scrolls down the page. This is u import * as React from 'react'; import { Layout } from 'react-admin'; -export const MyLayout = (props) => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` ## `className` @@ -153,10 +147,13 @@ If you want to customize this page, or log the error to a third-party service, c ```jsx // in src/MyLayout.js import { Layout } from 'react-admin'; - import { MyError } from './MyError'; -export const MyLayout = (props) => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` The following snippet is a simplified version of the react-admin `Error` component, that you can use as a base for your own: @@ -220,10 +217,13 @@ Lets you override the menu. ```jsx // in src/Layout.js import { Layout } from 'react-admin'; - import { MyMenu } from './MyMenu'; -export const Layout = (props) => ; +export const Layout = ({ children }) => ( + + {children} + +); ``` You can create a custom menu component using [react-admin's `` component](./Menu.md): @@ -276,8 +276,11 @@ import { Layout } from 'react-admin'; import { MySidebar } from './MySidebar'; -export const Layout = (props) => ; - +export const Layout = ({ children }) => ( + + {children} + +); // in src/MySidebar.js import * as React from 'react'; @@ -340,7 +343,11 @@ const MySidebar = (props) => ( /> ); -const MyLayout = props => +const MyLayout = ({ children }) => ( + + {children} + +); ``` {% endraw %} @@ -350,8 +357,10 @@ Pass an `sx` prop to customize the style of the main component and the underlyin {% raw %} ```jsx -export const MyLayout = (props) => ( - +export const MyLayout = ({ children }) => ( + + {children} + ); ``` {% endraw %} @@ -385,9 +394,11 @@ const getCookie = (name) => document.cookie .find(row => row.startsWith(`${name}=`)) ?.split('=')[1]; -export const MyLayout = (props) => ( +export const MyLayout = ({ children }) => ( - + + {children} + ); ``` @@ -401,11 +412,11 @@ A custom layout is also the ideal place to add debug tools, e.g. [react-query de import { Layout } from 'react-admin'; import { ReactQueryDevtools } from 'react-query/devtools' -export const MyLayout = (props) => ( - <> - +export const MyLayout = ({ children }) => ( + + {children} - + ); ``` diff --git a/docs/LocalesMenuButton.md b/docs/LocalesMenuButton.md index b4d5dd9495e..c8035c2c4a9 100644 --- a/docs/LocalesMenuButton.md +++ b/docs/LocalesMenuButton.md @@ -46,7 +46,11 @@ import { Admin, Resource, Layout } from 'react-admin'; import { MyAppBar } from './MyAppBar'; -const MyLayout = (props) => ; +const MyLayout = ({ children }) => ( + + {children} + +); const i18nProvider = polyglotI18nProvider( locale => (locale === 'fr' ? frenchMessages : englishMessages), diff --git a/docs/Menu.md b/docs/Menu.md index 154df47384e..20aa37cc3a0 100644 --- a/docs/Menu.md +++ b/docs/Menu.md @@ -34,10 +34,13 @@ Then, create a custom layout using [the `` component](./Layout.md) and p ```jsx // in src/MyLayout.js import { Layout } from 'react-admin'; - import { MyMenu } from './MyMenu'; -export const MyLayout = props => ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` Finally, pass this custom layout to the `` component: @@ -79,7 +82,11 @@ import PeopleIcon from '@mui/icons-material/People'; import { dataProvider } from './dataProvider'; const MyMenu = () => ; -const MyLayout = (props) => +const MyLayout = ({ children }) => ( + + {children} + +); const App = () => ( @@ -91,7 +98,7 @@ const App = () => ( ); ``` -Renders the following menu: +Renders the following menu: ![standard menu with dashboard](./img/menu-with-dashboard.webp) @@ -416,12 +423,15 @@ If you need to display a menu item with a submenu, you should use [the `` component](./MenuLive.md) instead of `` to enable this feature. ```tsx -import { Admin, Layout, LayoutProps, Resource } from 'react-admin'; +import type { ReactNode } from 'react'; +import { Admin, Layout, Resource } from 'react-admin'; import { MenuLive } from '@react-admin/ra-realtime'; import { PostList, PostShow, PostEdit, realTimeDataProvider } from '.'; -const CustomLayout = (props: LayoutProps) => ( - +const CustomLayout = ({ children}: { children: ReactNode }) => ( + + {children} + ); const MyReactAdmin = () => ( diff --git a/docs/MenuLive.md b/docs/MenuLive.md index 624e17cc495..c868bef127a 100644 --- a/docs/MenuLive.md +++ b/docs/MenuLive.md @@ -14,12 +14,15 @@ title: "The MenuLive Component" Use `` instead of `` in a custom layout: ```tsx -import { Admin, Layout, LayoutProps, Resource } from 'react-admin'; +import type { ReactNode } from 'react'; +import { Admin, Layout, Resource } from 'react-admin'; import { MenuLive } from '@react-admin/ra-realtime'; import { PostList, PostShow, PostEdit, realTimeDataProvider } from '.'; -const CustomLayout = (props: LayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); const MyReactAdmin = () => ( diff --git a/docs/MultiLevelMenu.md b/docs/MultiLevelMenu.md index 5014e3c72ee..410836feb63 100644 --- a/docs/MultiLevelMenu.md +++ b/docs/MultiLevelMenu.md @@ -50,7 +50,7 @@ const MyMenu = () => ( Note that each `` requires a unique `name` attribute. -Then, create a custom layout using [the `` component](./Layout.md) and pass your custom menu component to it. Make sure you wrap the layout with the `` component. +Then, create a custom layout using [the `` component](./Layout.md) and pass your custom menu component to it. Make sure you wrap the layout with the `` component. ```jsx // in src/MyLayout.js @@ -59,9 +59,11 @@ import { AppLocationContext } from '@react-admin/ra-navigation'; import { MyMenu } from './MyMenu'; -export const MyLayout = (props) => ( +export const MyLayout = ({ children }) => ( - + + {children} + ); ``` diff --git a/docs/Realtime.md b/docs/Realtime.md index fe770fe6c4f..6d94b131f96 100644 --- a/docs/Realtime.md +++ b/docs/Realtime.md @@ -218,8 +218,10 @@ import { MenuLive } from '@react-admin/ra-realtime'; import { PostList, PostShow, PostEdit, realTimeDataProvider } from '.'; -const CustomLayout = (props) => ( - +const CustomLayout = ({ children }) => ( + + {children} + ); const MyReactAdmin = () => ( diff --git a/docs/Routing.md b/docs/Routing.md index 2c66a38d0b9..35d69de6eea 100644 --- a/docs/Routing.md +++ b/docs/Routing.md @@ -83,9 +83,9 @@ import { Layout } from 'react-admin'; import { usePageTracking } from './usePageTracking'; -export const MyLayout = (props) => { +export const MyLayout = ({ children }) => { usePageTracking(); - return ; + return {children}; } ``` diff --git a/docs/SX.md b/docs/SX.md index fee4065e2f6..d06acedfa14 100644 --- a/docs/SX.md +++ b/docs/SX.md @@ -366,10 +366,11 @@ Sometimes you want the format to depend on the value. Use `useRecordContext` to The following example shows how to create a new `` component, which renders with red text when its value is less than 0. {% raw %} -```jsx +```tsx import { useRecordContext, NumberField, List, Datagrid, TextField, EditButton } from 'react-admin'; +import type { NumberFieldProps } from 'react-admin'; -const ColoredNumberField = (props) => { +const ColoredNumberField = (props: NumberFieldProps) => { const record = useRecordContext(); return ( ; +export const MyLayout = ({ children }) => ( + + {children} + +); ``` Finally, include that custom layout in the ``. @@ -133,8 +137,10 @@ If you're using [the `` component](./ContainerLayout.md), you c import { ContainerLayout } from "@react-admin/ra-navigation"; import { Search } from "@react-admin/ra-search"; -const MyLayout = (props: any) => ( - } /> +const MyLayout = ({ children }) => ( + }> + {children} + ); ``` @@ -200,7 +206,11 @@ const MyAppBar = () => ( ); -const MyLayout = props => ; +const MyLayout = ({ children }) => ( + + {children} + +); export const App = () => ( diff --git a/docs/SearchWithResult.md b/docs/SearchWithResult.md index f2bf8ea520a..47d8b64938c 100644 --- a/docs/SearchWithResult.md +++ b/docs/SearchWithResult.md @@ -358,6 +358,7 @@ Here is an implementation example: {% raw %} ```tsx +import type { ReactNode } from 'react'; import { Admin } from 'react-admin'; import { Box } from '@mui/material'; import SearchIcon from '@mui/icons-material/Search'; @@ -365,15 +366,16 @@ import AlbumIcon from '@mui/icons-material/Album'; import Groups3Icon from '@mui/icons-material/Groups3'; import { SolarLayout, - SolarLayoutProps, SolarMenu, useSolarSidebarActiveMenu, } from '@react-admin/ra-navigation'; import { SearchWithResult } from '@react-admin/ra-search'; import { searchDataProvider } from './searchDataProvider'; -const MySolarLayout = (props: SolarLayoutProps) => ( - +const MySolarLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); const MySolarMenu = () => ( diff --git a/docs/SolarLayout.md b/docs/SolarLayout.md index 7b7972cc419..6f5c4e3cd72 100644 --- a/docs/SolarLayout.md +++ b/docs/SolarLayout.md @@ -71,10 +71,10 @@ You can customize the AppBar that appears on Mobile by setting the `appBar` prop {% raw %} ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; import { SolarAppBar, - SolarLayoutProps, SolarLayout, } from '@react-admin/ra-navigation'; @@ -96,8 +96,10 @@ const CustomAppBar = () => ( /> ); -const CustomLayout = (props: SolarLayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); export const App = () => ( @@ -121,10 +123,15 @@ If you want to customize this page, or log the error to a third-party service, c ```tsx // in src/MyLayout.tsx +import type { ReactNode } from 'react'; import { Layout } from 'react-admin'; import { MyError } from './MyError'; -export const MyLayout = props => ; +export const MyLayout = ({ children }: { children: ReactNode }) => ( + + {children} + +); ``` The following snippet is a simplified version of the react-admin `Error` component, that you can use as a base for your own: @@ -193,13 +200,16 @@ export const MyError = ({ You can customize the icon of the dashboard menu item of the default menu by setting the `logo` prop: ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; -import { SolarLayoutProps, SolarLayout } from '@react-admin/ra-navigation'; +import { SolarLayout } from '@react-admin/ra-navigation'; import { Dashboard } from './Dashboard'; import { Logo } from './Logo'; -const CustomLayout = (props: SolarLayoutProps) => ( - } /> +const CustomLayout = ({ children }: { children: ReactNode }) => ( + }> + {children} + ); export const WithDashboardAndCustomLogo = () => ( @@ -215,12 +225,9 @@ export const WithDashboardAndCustomLogo = () => ( If you need a customized menu, pass it to the `menu` prop. It's recommended to pass a customized [``](#solarmenu) to leverage this layout. This is useful to organize many resources into categories or to provide shortcuts to filtered lists: ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; -import { - SolarLayoutProps, - SolarLayout, - SolarMenu, -} from '@react-admin/ra-navigation'; +import { SolarLayout, SolarMenu } from '@react-admin/ra-navigation'; export const App = () => ( ( ); -const CustomLayout = ({ children, ...props }: SolarLayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + {children} ); @@ -325,11 +332,14 @@ The `sx` prop allows you to customize the layout styles using a MUI [SX](./SX.md {% raw %} ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; -import { SolarLayoutProps, SolarLayout } from '@react-admin/ra-navigation'; +import { SolarLayout } from '@react-admin/ra-navigation'; -const CustomLayout = (props: SolarLayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); export const App = () => ( @@ -475,19 +485,18 @@ const CustomMenu = () => ( Set the `dense` prop to `true` to reduce the vertical space between items: ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; -import { - SolarLayoutProps, - SolarLayout, - SolarMenu, -} from '@react-admin/ra-navigation'; +import { SolarLayout, SolarMenu } from '@react-admin/ra-navigation'; import { ListItemButton } from '@mui/material'; import { dataProvider } from './dataProvider'; const CustomMenu = () => ; -const CustomLayout = (props: SolarLayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); export const App = () => ( @@ -518,12 +527,9 @@ You can customize it by passing your own content to the `userMenu` prop. For instance, here's how to only show a logout button: ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; -import { - SolarLayoutProps, - SolarLayout, - SolarMenu, -} from '@react-admin/ra-navigation'; +import { SolarLayout, SolarMenu } from '@react-admin/ra-navigation'; import { ListItemButton } from '@mui/material'; import { dataProvider } from './dataProvider'; @@ -539,8 +545,8 @@ const CustomUserMenu = () => { const CustomMenu = () => } />; -const CustomLayout = ({ children, ...props }: SolarLayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + {children} ); @@ -566,12 +572,9 @@ You can customize it by passing your own content to the `bottomToolbar` prop. For instance, here's how to show a settings menu item in addition to the existing bottom menu items: ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; -import { - SolarLayoutProps, - SolarLayout, - SolarMenu, -} from '@react-admin/ra-navigation'; +import { SolarLayout, SolarMenu } from '@react-admin/ra-navigation'; import { ListItemButton } from '@mui/material'; import { dataProvider } from './dataProvider'; @@ -590,8 +593,10 @@ const CustomBottomToolbar = () => ( const CustomMenu = () => } />; -const CustomLayout = (props: SolarLayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); export const App = () => ( @@ -612,8 +617,9 @@ For instance, here is how to change the background color of the menu: {% raw %} ```tsx +import type { ReactNode } from 'react'; import { Admin, Resource, ListGuesser } from 'react-admin'; -import { SolarLayoutProps, SolarLayout, SolarMenu, SolarMenuProps } from '@react-admin/ra-navigation'; +import { SolarLayout, SolarMenu, SolarMenuProps } from '@react-admin/ra-navigation'; const CustomMenu = (props: SolarMenuProps) => ( ( /> ); -const CustomLayout = (props: SolarLayoutProps) => ( - +const CustomLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); export const App = () => ( @@ -824,12 +832,9 @@ An AppBar alternative for the SolarLayout that is only shown on small devices. I You can customize it by passing children: ```tsx +import type { ReactNode } from 'react'; import { Admin, AppBarProps, Resource, LoadingIndicator } from 'react-admin'; -import { - SolarAppBar, - SolarLayout, - SolarLayoutProps, -} from '@react-admin/ra-navigation'; +import { SolarAppBar, SolarLayout } from '@react-admin/ra-navigation'; import { Search } from '@react-admin/ra-search'; const CustomAppBar = () => ( @@ -839,8 +844,10 @@ const CustomAppBar = () => ( ); -export const CustomLayout = (props: SolarLayoutProps) => ( - +export const CustomLayout = ({ children }: { children: ReactNode }) => ( + + {children} + ); export const App = () => ( diff --git a/docs/ToggleThemeButton.md b/docs/ToggleThemeButton.md index b906247fe62..c05e78577d3 100644 --- a/docs/ToggleThemeButton.md +++ b/docs/ToggleThemeButton.md @@ -33,10 +33,13 @@ Then, pass the custom App Bar in a custom ``, and the `` to your {% raw %} ```jsx import { Admin, Layout } from 'react-admin'; - import { MyAppBar } from './MyAppBar'; -const MyLayout = (props) => ; +const MyLayout = ({ children }) => ( + + {children} + +); const App = () => ( ( - +export const MyLayout = ({ children }: { children: ReactNode }) => ( + {children} diff --git a/docs/canAccess.md b/docs/canAccess.md index b6de7fd5822..0325443165e 100644 --- a/docs/canAccess.md +++ b/docs/canAccess.md @@ -349,7 +349,11 @@ const MyMenu = () => { ); }; -const MyLayout = props => ; +const MyLayout = ({ children }) => ( + + {children} + +); export const App = () => ( ( const MyAppBar = () => } />; -const MyLayout = (props) => ( - +const MyLayout = ({ children }) => ( + + {children} + ); export default MyLayout; diff --git a/examples/crm/src/Layout.tsx b/examples/crm/src/Layout.tsx index da32c47b145..440cdbe4102 100644 --- a/examples/crm/src/Layout.tsx +++ b/examples/crm/src/Layout.tsx @@ -1,12 +1,12 @@ -import React, { Suspense, HtmlHTMLAttributes } from 'react'; +import React, { Suspense, ReactNode } from 'react'; import { CssBaseline, Container } from '@mui/material'; -import { CoreLayoutProps, CheckForApplicationUpdate } from 'react-admin'; +import { CheckForApplicationUpdate } from 'react-admin'; import { ErrorBoundary } from 'react-error-boundary'; import { Error, Loading } from 'react-admin'; import Header from './Header'; -const Layout = ({ children }: LayoutProps) => ( +const Layout = ({ children }: { children: ReactNode }) => ( <>
@@ -22,8 +22,4 @@ const Layout = ({ children }: LayoutProps) => ( ); -export interface LayoutProps - extends CoreLayoutProps, - Omit, 'title'> {} - export default Layout; diff --git a/examples/demo/src/layout/Layout.tsx b/examples/demo/src/layout/Layout.tsx index 3de84c0c74a..bc6c818fa84 100644 --- a/examples/demo/src/layout/Layout.tsx +++ b/examples/demo/src/layout/Layout.tsx @@ -1,8 +1,10 @@ import * as React from 'react'; -import { Layout, LayoutProps } from 'react-admin'; +import { Layout } from 'react-admin'; import AppBar from './AppBar'; import Menu from './Menu'; -export default (props: LayoutProps) => ( - +export default ({ children }: { children: React.ReactNode }) => ( + + {children} + ); diff --git a/examples/simple/src/Layout.tsx b/examples/simple/src/Layout.tsx index 9a840ca66b4..f17e8f98f70 100644 --- a/examples/simple/src/Layout.tsx +++ b/examples/simple/src/Layout.tsx @@ -10,9 +10,9 @@ const MyAppBar = () => ( ); -export default props => ( +export default ({ children }) => ( <> - + {children} ({ '& .publishedAt': { fontStyle: 'italic' }, })); -const PostListBulkActions = memo( - ({ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - children, - ...props - }: { - children?: React.ReactNode; - }) => ( - - - - - - ) -); +const PostListBulkActions = memo(() => ( + + + + + +)); const PostListActions = () => ( diff --git a/packages/create-react-admin/templates/common/src/Layout.tsx b/packages/create-react-admin/templates/common/src/Layout.tsx index b6a3be25da0..ab303b7e8c4 100644 --- a/packages/create-react-admin/templates/common/src/Layout.tsx +++ b/packages/create-react-admin/templates/common/src/Layout.tsx @@ -1,11 +1,11 @@ +import type { ReactNode } from 'react'; import { Layout as RALayout, - LayoutProps, CheckForApplicationUpdate, } from "react-admin"; -export const Layout = ({ children, ...props }: LayoutProps) => ( - +export const Layout = ({ children }: { children: ReactNode }) => ( + {children} diff --git a/packages/ra-core/src/auth/usePermissions.ts b/packages/ra-core/src/auth/usePermissions.ts index 2661bd86bd7..4f707e44050 100644 --- a/packages/ra-core/src/auth/usePermissions.ts +++ b/packages/ra-core/src/auth/usePermissions.ts @@ -31,12 +31,12 @@ const emptyParams = {}; * @example * import { usePermissions } from 'react-admin'; * - * const PostDetail = props => { + * const PostDetail = () => { * const { isPending, permissions } = usePermissions(); * if (!isPending && permissions == 'editor') { - * return + * return * } else { - * return + * return * } * }; */ diff --git a/packages/ra-core/src/controller/create/CreateBase.tsx b/packages/ra-core/src/controller/create/CreateBase.tsx index 54ed9fa90e6..8c66a551c71 100644 --- a/packages/ra-core/src/controller/create/CreateBase.tsx +++ b/packages/ra-core/src/controller/create/CreateBase.tsx @@ -19,8 +19,8 @@ import { ResourceContextProvider } from '../../core'; * * @example // Custom edit layout * - * const PostCreate = props => ( - * + * const PostCreate = () => ( + * * * * diff --git a/packages/ra-core/src/core/CoreAdmin.tsx b/packages/ra-core/src/core/CoreAdmin.tsx index 85719837906..48e848899fe 100644 --- a/packages/ra-core/src/core/CoreAdmin.tsx +++ b/packages/ra-core/src/core/CoreAdmin.tsx @@ -97,7 +97,6 @@ export const CoreAdmin = (props: CoreAdminProps) => { layout, loading, loginPage, - menu, // deprecated, use a custom layout instead ready, requireAuth, store, @@ -117,7 +116,6 @@ export const CoreAdmin = (props: CoreAdminProps) => { layout={layout} dashboard={dashboard} disableTelemetry={disableTelemetry} - menu={menu} catchAll={catchAll} title={title} loading={loading} diff --git a/packages/ra-core/src/core/CoreAdminRoutes.tsx b/packages/ra-core/src/core/CoreAdminRoutes.tsx index 6c91e4df3b1..c2bd3b923eb 100644 --- a/packages/ra-core/src/core/CoreAdminRoutes.tsx +++ b/packages/ra-core/src/core/CoreAdminRoutes.tsx @@ -7,11 +7,12 @@ import { useScrollToTop, useCreatePath } from '../routing'; import { AdminChildren, CatchAllComponent, + DashboardComponent, LayoutComponent, LoadingComponent, - CoreLayoutProps, } from '../types'; import { useConfigureAdminRouterFromChildren } from './useConfigureAdminRouterFromChildren'; +import { HasDashboardContextProvider } from './HasDashboardContext'; export const CoreAdminRoutes = (props: CoreAdminRoutesProps) => { useScrollToTop(); @@ -29,10 +30,8 @@ export const CoreAdminRoutes = (props: CoreAdminRoutesProps) => { catchAll: CatchAll, dashboard, loading: LoadingPage, - menu, requireAuth, ready: Ready, - title, } = props; const [canRender, setCanRender] = useState(!requireAuth); @@ -77,8 +76,8 @@ export const CoreAdminRoutes = (props: CoreAdminRoutesProps) => { - + + {customRoutesWithLayout} {Children.map(resources, resource => ( @@ -107,21 +106,18 @@ export const CoreAdminRoutes = (props: CoreAdminRoutesProps) => { ) : null } /> - } - /> + } /> -
+ } /> ); }; -export interface CoreAdminRoutesProps - extends Omit { +export interface CoreAdminRoutesProps { + dashboard?: DashboardComponent; layout: LayoutComponent; catchAll: CatchAllComponent; children?: AdminChildren; diff --git a/packages/ra-core/src/core/CoreAdminUI.tsx b/packages/ra-core/src/core/CoreAdminUI.tsx index 89020f4d3d0..5310d02b391 100644 --- a/packages/ra-core/src/core/CoreAdminUI.tsx +++ b/packages/ra-core/src/core/CoreAdminUI.tsx @@ -4,11 +4,11 @@ import { Routes, Route } from 'react-router-dom'; import { CoreAdminRoutes } from './CoreAdminRoutes'; import { Ready } from '../util'; -import { +import { DefaultTitleContextProvider } from './DefaultTitleContext'; +import type { TitleComponent, LoginComponent, LayoutComponent, - CoreLayoutProps, AdminChildren, CatchAllComponent, DashboardComponent, @@ -17,7 +17,9 @@ import { export type ChildrenFunction = () => ComponentType[]; -const DefaultLayout = ({ children }: CoreLayoutProps) => <>{children}; +const DefaultLayout = ({ children }: { children: React.ReactNode }) => ( + <>{children} +); export interface CoreAdminUIProps { /** @@ -94,7 +96,11 @@ export interface CoreAdminUIProps { * @example * import { Admin, Layout } from 'react-admin'; * - * const MyLayout = props => ; + * const MyLayout = ({ children }) => ( + * + * {children} + * + * ); * * export const App = () => ( * @@ -152,12 +158,6 @@ export interface CoreAdminUIProps { */ loginPage?: LoginComponent | boolean; - /** - * @deprecated use a custom layout instead - * @see https://marmelab.com/react-admin/Admin.html#layout - */ - menu?: ComponentType; - /** * Flag to require authentication for all routes. Defaults to false. * @@ -227,9 +227,8 @@ export const CoreAdminUI = (props: CoreAdminUIProps) => { loading = Noop, loginPage: LoginPage = false, authCallbackPage: LoginCallbackPage = false, - menu, // deprecated, use a custom layout instead ready = Ready, - title = 'React Admin', + title, requireAuth = false, } = props; @@ -248,36 +247,39 @@ export const CoreAdminUI = (props: CoreAdminUIProps) => { }, [disableTelemetry]); return ( - - {LoginPage !== false && LoginPage !== true ? ( - - ) : null} + + + {LoginPage !== false && LoginPage !== true ? ( + + ) : null} + + {LoginCallbackPage !== false && LoginCallbackPage !== true ? ( + + ) : null} - {LoginCallbackPage !== false && LoginCallbackPage !== true ? ( + {children} + + } /> - ) : null} - - - {children} - - } - /> - + + ); }; diff --git a/packages/ra-core/src/core/DefaultTitleContext.ts b/packages/ra-core/src/core/DefaultTitleContext.ts new file mode 100644 index 00000000000..10a82b0815d --- /dev/null +++ b/packages/ra-core/src/core/DefaultTitleContext.ts @@ -0,0 +1,8 @@ +import { createContext, useContext } from 'react'; +import type { TitleComponent } from '../types'; + +export const DefaultTitleContext = createContext('React Admin'); + +export const DefaultTitleContextProvider = DefaultTitleContext.Provider; + +export const useDefaultTitle = () => useContext(DefaultTitleContext); diff --git a/packages/ra-core/src/core/HasDashboardContext.ts b/packages/ra-core/src/core/HasDashboardContext.ts new file mode 100644 index 00000000000..153c2c3fc22 --- /dev/null +++ b/packages/ra-core/src/core/HasDashboardContext.ts @@ -0,0 +1,7 @@ +import { createContext, useContext } from 'react'; + +export const HasDashboardContext = createContext(undefined); + +export const HasDashboardContextProvider = HasDashboardContext.Provider; + +export const useHasDashboard = () => useContext(HasDashboardContext); diff --git a/packages/ra-core/src/core/index.ts b/packages/ra-core/src/core/index.ts index 4409feaf86f..5d1221dd656 100644 --- a/packages/ra-core/src/core/index.ts +++ b/packages/ra-core/src/core/index.ts @@ -3,6 +3,8 @@ export * from './CoreAdminContext'; export * from './CoreAdminRoutes'; export * from './CoreAdminUI'; export * from './CustomRoutes'; +export * from './DefaultTitleContext'; +export * from './HasDashboardContext'; export * from './Resource'; export * from './ResourceContext'; export * from './ResourceContextProvider'; diff --git a/packages/ra-core/src/form/FormDataConsumer.tsx b/packages/ra-core/src/form/FormDataConsumer.tsx index f478e41a116..4a2b6126f29 100644 --- a/packages/ra-core/src/form/FormDataConsumer.tsx +++ b/packages/ra-core/src/form/FormDataConsumer.tsx @@ -11,8 +11,8 @@ import { useWrappedSource } from '../core'; * * @example * - * const PostEdit = (props) => ( - * + * const PostEdit = () => ( + * * > * * @@ -26,8 +26,8 @@ import { useWrappedSource } from '../core'; * * @example * - * const OrderEdit = (props) => ( - * + * const OrderEdit = () => ( + * * * * > diff --git a/packages/ra-core/src/form/FormGroupContextProvider.tsx b/packages/ra-core/src/form/FormGroupContextProvider.tsx index a2454e368d3..1eb1433f9e8 100644 --- a/packages/ra-core/src/form/FormGroupContextProvider.tsx +++ b/packages/ra-core/src/form/FormGroupContextProvider.tsx @@ -12,8 +12,8 @@ import { useFormGroups } from './useFormGroups'; * import { Edit, SimpleForm, TextInput, FormGroupContextProvider, useFormGroup } from 'react-admin'; * import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material'; * - * const PostEdit = (props) => ( - * + * const PostEdit = () => ( + * * * * diff --git a/packages/ra-core/src/form/useFormGroup.ts b/packages/ra-core/src/form/useFormGroup.ts index afee8d7cdac..d0de0bbcb27 100644 --- a/packages/ra-core/src/form/useFormGroup.ts +++ b/packages/ra-core/src/form/useFormGroup.ts @@ -28,8 +28,8 @@ type FormGroupState = { * import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material'; * import ExpandMoreIcon from '@mui/icons-material/ExpandMoreIcon'; * - * const PostEdit = (props) => ( - * + * const PostEdit = () => ( + * * * * diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index d06e31f996f..ed754b19b83 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -318,16 +318,9 @@ export type CatchAllComponent = ComponentType<{ title?: TitleComponent }>; export type LoginComponent = ComponentType<{}> | ReactElement; export type DashboardComponent = ComponentType; -export interface CoreLayoutProps { - children?: ReactNode; - dashboard?: DashboardComponent; - menu?: ComponentType<{ - hasDashboard?: boolean; - }>; - title?: TitleComponent; -} - -export type LayoutComponent = ComponentType; +export type LayoutComponent = ComponentType<{ + children: ReactNode; +}>; export type LoadingComponent = ComponentType<{ loadingPrimary?: string; loadingSecondary?: string; diff --git a/packages/ra-no-code/src/ui/Layout.tsx b/packages/ra-no-code/src/ui/Layout.tsx index 6bf29220606..af33c0e3e84 100644 --- a/packages/ra-no-code/src/ui/Layout.tsx +++ b/packages/ra-no-code/src/ui/Layout.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { Layout as RaLayout, LayoutProps } from 'react-admin'; +import { Layout as RaLayout } from 'react-admin'; import { useResourcesConfiguration } from '../ResourceConfiguration'; import { Menu } from './Menu'; import { AppBar } from './Appbar'; import { Ready } from './Ready'; -export const Layout = (props: LayoutProps) => { +export const Layout = ({ children }: { children: React.ReactNode }) => { const [resources] = useResourcesConfiguration(); const hasResources = !!resources && Object.keys(resources).length > 0; @@ -13,5 +13,9 @@ export const Layout = (props: LayoutProps) => { return ; } - return ; + return ( + + {children} + + ); }; diff --git a/packages/ra-ui-materialui/src/button/LocalesMenuButton.stories.tsx b/packages/ra-ui-materialui/src/button/LocalesMenuButton.stories.tsx index c03c0ff0227..40f1baecf3b 100644 --- a/packages/ra-ui-materialui/src/button/LocalesMenuButton.stories.tsx +++ b/packages/ra-ui-materialui/src/button/LocalesMenuButton.stories.tsx @@ -167,7 +167,9 @@ const MyAppBar = () => ( />
); -const MyLayout = props => ; +const MyLayout = ({ children }) => ( + {children} +); export const FullApp = () => ( ( ); -const MyLayout = props => ; +const MyLayout = ({ children }) => ( + {children} +); export const Legacy = () => ( } /> * ); * - * const MyLayout = props => ; + * const MyLayout = ({ children }) => ( + * + * {children} + * + * ); */ export const ToggleThemeButton = (props: ToggleThemeButtonProps) => { const translate = useTranslate(); diff --git a/packages/ra-ui-materialui/src/button/ToggleThemeLegacyButton.tsx b/packages/ra-ui-materialui/src/button/ToggleThemeLegacyButton.tsx index 6c83ee825fd..d71a45e7453 100644 --- a/packages/ra-ui-materialui/src/button/ToggleThemeLegacyButton.tsx +++ b/packages/ra-ui-materialui/src/button/ToggleThemeLegacyButton.tsx @@ -21,7 +21,11 @@ import { useTheme, RaThemeOptions } from '../theme'; * * ); * - * const MyLayout = props => ; + * const MyLayout = ({ children }) => ( + * + * {children} + * + * ); */ export const ToggleThemeLegacyButton = ( props: ToggleThemeLegacyButtonProps diff --git a/packages/ra-ui-materialui/src/detail/Create.tsx b/packages/ra-ui-materialui/src/detail/Create.tsx index 45d3481cdeb..a5e6618d5d2 100644 --- a/packages/ra-ui-materialui/src/detail/Create.tsx +++ b/packages/ra-ui-materialui/src/detail/Create.tsx @@ -29,8 +29,8 @@ import { CreateBase } from 'ra-core'; * import * as React from "react"; * import { Create, SimpleForm, TextInput } from 'react-admin'; * - * export const PostCreate = (props) => ( - * + * export const PostCreate = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/detail/Edit.tsx b/packages/ra-ui-materialui/src/detail/Edit.tsx index 8164782ff4f..24038cad311 100644 --- a/packages/ra-ui-materialui/src/detail/Edit.tsx +++ b/packages/ra-ui-materialui/src/detail/Edit.tsx @@ -30,8 +30,8 @@ import { EditBase } from 'ra-core'; * import * as React from "react"; * import { Edit, SimpleForm, TextInput } from 'react-admin'; * - * export const PostEdit = (props) => ( - * + * export const PostEdit = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/detail/Tab.tsx b/packages/ra-ui-materialui/src/detail/Tab.tsx index 2a50a5202c6..3fb93b6a63f 100644 --- a/packages/ra-ui-materialui/src/detail/Tab.tsx +++ b/packages/ra-ui-materialui/src/detail/Tab.tsx @@ -32,8 +32,8 @@ import { Labeled } from '../Labeled'; * import PersonPinIcon from '@mui/icons-material/PersonPin'; * import { Show, TabbedShowLayout, TextField } from 'react-admin'; * - * export const PostShow = (props) => ( - * + * export const PostShow = () => ( + * * * }> * diff --git a/packages/ra-ui-materialui/src/form/SimpleForm.tsx b/packages/ra-ui-materialui/src/form/SimpleForm.tsx index 20110acc8c5..bfb30e43a90 100644 --- a/packages/ra-ui-materialui/src/form/SimpleForm.tsx +++ b/packages/ra-ui-materialui/src/form/SimpleForm.tsx @@ -18,8 +18,8 @@ import { Toolbar } from './Toolbar'; * import { Create, Edit, SimpleForm, TextInput, DateInput, ReferenceManyField, Datagrid, TextField, DateField, EditButton } from 'react-admin'; * import RichTextInput from 'ra-input-rich-text'; * - * export const PostCreate = (props) => ( - * + * export const PostCreate = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.tsx b/packages/ra-ui-materialui/src/form/TabbedForm.tsx index a9cb3d63757..4da9c09b8b1 100644 --- a/packages/ra-ui-materialui/src/form/TabbedForm.tsx +++ b/packages/ra-ui-materialui/src/form/TabbedForm.tsx @@ -36,8 +36,8 @@ import { FormTab } from './FormTab'; * EditButton * } from 'react-admin'; * - * export const PostEdit = (props) => ( - * + * export const PostEdit = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/input/DateInput.tsx b/packages/ra-ui-materialui/src/input/DateInput.tsx index 6f249875e85..1dd3e738ffa 100644 --- a/packages/ra-ui-materialui/src/input/DateInput.tsx +++ b/packages/ra-ui-materialui/src/input/DateInput.tsx @@ -16,8 +16,8 @@ import { InputHelperText } from './InputHelperText'; * @example * import { Edit, SimpleForm, DateInput } from 'react-admin'; * - * const PostEdit = (props) => ( - * + * const PostEdit = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx index 18450fecd0e..3e41c5276e5 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx @@ -31,8 +31,8 @@ import { AutocompleteArrayInput } from './AutocompleteArrayInput'; * or . * * @example - * export const PostEdit = (props) => ( - * + * export const PostEdit = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.tsx b/packages/ra-ui-materialui/src/input/ReferenceInput.tsx index ac90f900279..d5c6279bdd9 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceInput.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceInput.tsx @@ -22,8 +22,8 @@ import { AutocompleteInput } from './AutocompleteInput'; * instead of ``). * * @example // default selector: AutocompleteInput - * export const CommentEdit = (props) => ( - * + * export const CommentEdit = () => ( + * * * * @@ -31,8 +31,8 @@ import { AutocompleteInput } from './AutocompleteInput'; * ); * * @example // using a SelectInput as selector - * export const CommentEdit = (props) => ( - * + * export const CommentEdit = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/input/TimeInput.tsx b/packages/ra-ui-materialui/src/input/TimeInput.tsx index ff2c49cd5b1..d9aa4bbb85f 100644 --- a/packages/ra-ui-materialui/src/input/TimeInput.tsx +++ b/packages/ra-ui-materialui/src/input/TimeInput.tsx @@ -36,8 +36,8 @@ const parseTime = (value: string) => { * @example * import { Edit, SimpleForm, TimeInput } from 'react-admin'; * - * const PostEdit = (props) => ( - * + * const PostEdit = () => ( + * * * * diff --git a/packages/ra-ui-materialui/src/layout/AppBar.tsx b/packages/ra-ui-materialui/src/layout/AppBar.tsx index 4e788fc807c..c5a993aafe5 100644 --- a/packages/ra-ui-materialui/src/layout/AppBar.tsx +++ b/packages/ra-ui-materialui/src/layout/AppBar.tsx @@ -49,8 +49,6 @@ export const AppBar: FC = memo(props => { children, className, color = 'secondary', - open, - title, toolbar = defaultToolbarElement, userMenu = DefaultUserMenu, container: Container = alwaysOn ? 'div' : HideOnScroll, @@ -119,17 +117,13 @@ AppBar.propTypes = { 'transparent', ]), container: ComponentPropType, - /** - * @deprecated - */ - open: PropTypes.bool, toolbar: PropTypes.element, userMenu: PropTypes.oneOfType([PropTypes.element, PropTypes.bool]), }; const DefaultUserMenu = ; -export interface AppBarProps extends Omit { +export interface AppBarProps extends MuiAppBarProps { /** * This prop is injected by Layout. You should not use it directly unless * you are using a custom layout. @@ -137,14 +131,6 @@ export interface AppBarProps extends Omit { */ alwaysOn?: boolean; container?: React.ElementType; - /** - * @deprecated injected by Layout but not used by this AppBar - */ - open?: boolean; - /** - * @deprecated injected by Layout but not used by this AppBar - */ - title?: string | JSX.Element; toolbar?: JSX.Element; userMenu?: JSX.Element | boolean; } diff --git a/packages/ra-ui-materialui/src/layout/CheckForApplicationUpdate.tsx b/packages/ra-ui-materialui/src/layout/CheckForApplicationUpdate.tsx index 0bcf10ad55b..600f209ee5d 100644 --- a/packages/ra-ui-materialui/src/layout/CheckForApplicationUpdate.tsx +++ b/packages/ra-ui-materialui/src/layout/CheckForApplicationUpdate.tsx @@ -20,8 +20,8 @@ import { ApplicationUpdatedNotification } from './ApplicationUpdatedNotification * @example Basic usage * import { Admin, Resource, Layout, CheckForApplicationUpdate, ListGuesser } from 'react-admin'; * - * const MyLayout = ({ children, ...props }) => ( - * + * const MyLayout = ({ children }) => ( + * * {children} * * @@ -55,8 +55,8 @@ import { ApplicationUpdatedNotification } from './ApplicationUpdatedNotification * * )); * - * const MyLayout = ({ children, ...props }) => ( - * + * const MyLayout = ({ children }) => ( + * * {children} * } /> * diff --git a/packages/ra-ui-materialui/src/layout/Error.tsx b/packages/ra-ui-materialui/src/layout/Error.tsx index 3dd7134a48d..d9a8a489136 100644 --- a/packages/ra-ui-materialui/src/layout/Error.tsx +++ b/packages/ra-ui-materialui/src/layout/Error.tsx @@ -13,7 +13,9 @@ import { import ErrorIcon from '@mui/icons-material/Report'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import History from '@mui/icons-material/History'; -import { TitleComponent, useTranslate } from 'ra-core'; +import { useTranslate, useDefaultTitle } from 'ra-core'; +import type { TitleComponent } from 'ra-core'; + import { Title, TitlePropType } from './Title'; import { useResetErrorBoundaryOnLocationChange } from './useResetErrorBoundaryOnLocationChange'; @@ -28,11 +30,11 @@ export const Error = ( errorInfo, resetErrorBoundary, className, - title, ...rest } = props; const translate = useTranslate(); + const title = useDefaultTitle(); useResetErrorBoundaryOnLocationChange(resetErrorBoundary); if (ErrorComponent) { @@ -129,9 +131,9 @@ Error.propTypes = { interface InternalErrorProps extends Omit, 'title'>, - FallbackProps, - ErrorProps { + FallbackProps { className?: string; + errorInfo?: ErrorInfo; } export interface ErrorProps extends Pick { diff --git a/packages/ra-ui-materialui/src/layout/Layout.stories.tsx b/packages/ra-ui-materialui/src/layout/Layout.stories.tsx index 530349be3ce..fa516b0047f 100644 --- a/packages/ra-ui-materialui/src/layout/Layout.stories.tsx +++ b/packages/ra-ui-materialui/src/layout/Layout.stories.tsx @@ -50,7 +50,7 @@ const Content = () => ( const Wrapper = ({ children = , theme = createTheme(defaultTheme), - layout: LayoutProp = Layout, + layout: LayoutComponent = Layout, }) => ( @@ -58,10 +58,10 @@ const Wrapper = ({ - + {children} - </LayoutProp> + </LayoutComponent> </AuthContext.Provider> </PreferencesEditorContextProvider> </StoreContextProvider> @@ -93,8 +93,12 @@ const Menu = () => ( </MenuList> ); -const BasicLayout = props => <Layout menu={Menu} {...props} />; +const BasicLayout = ({ children }) => <Layout menu={Menu}>{children}</Layout>; export const Basic = () => <Wrapper layout={BasicLayout} />; -const AppBarAlwaysOnLayout = props => <BasicLayout appBarAlwaysOn {...props} />; +const AppBarAlwaysOnLayout = ({ children }) => ( + <Layout appBarAlwaysOn menu={Menu}> + {children} + </Layout> +); export const AppBarAlwaysOn = () => <Wrapper layout={AppBarAlwaysOnLayout} />; diff --git a/packages/ra-ui-materialui/src/layout/Layout.tsx b/packages/ra-ui-materialui/src/layout/Layout.tsx index b61f78ce019..1477a217d57 100644 --- a/packages/ra-ui-materialui/src/layout/Layout.tsx +++ b/packages/ra-ui-materialui/src/layout/Layout.tsx @@ -1,21 +1,13 @@ -import React, { - ComponentType, - ErrorInfo, - HtmlHTMLAttributes, - Suspense, - useState, -} from 'react'; +import React, { ComponentType, ErrorInfo, Suspense, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import clsx from 'clsx'; import { styled, SxProps } from '@mui/material/styles'; -import { CoreLayoutProps } from 'ra-core'; import { AppBar as DefaultAppBar, AppBarProps } from './AppBar'; import { Sidebar as DefaultSidebar, SidebarProps } from './Sidebar'; import { Menu as DefaultMenu, MenuProps } from './Menu'; import { Error, ErrorProps } from './Error'; import { SkipNavigationButton } from '../button'; -import { useSidebarState } from './useSidebarState'; import { Inspector } from '../preferences'; import { Loading } from './Loading'; @@ -25,15 +17,12 @@ export const Layout = (props: LayoutProps) => { appBarAlwaysOn, children, className, - dashboard, error: errorComponent, menu: Menu = DefaultMenu, sidebar: Sidebar = DefaultSidebar, - title, ...rest } = props; - const [open] = useSidebarState(); const [errorInfo, setErrorInfo] = useState<ErrorInfo>(null); const handleError = (error: Error, info: ErrorInfo) => { @@ -44,10 +33,10 @@ export const Layout = (props: LayoutProps) => { <Core className={clsx('layout', className)} {...rest}> <SkipNavigationButton /> <div className={LayoutClasses.appFrame}> - <AppBar open={open} title={title} alwaysOn={appBarAlwaysOn} /> + <AppBar alwaysOn={appBarAlwaysOn} /> <main className={LayoutClasses.contentWithSidebar}> <Sidebar appBarAlwaysOn={appBarAlwaysOn}> - <Menu hasDashboard={!!dashboard} /> + <Menu /> </Sidebar> <div id="main-content" className={LayoutClasses.content}> <ErrorBoundary @@ -58,7 +47,6 @@ export const Layout = (props: LayoutProps) => { errorComponent={errorComponent} errorInfo={errorInfo} resetErrorBoundary={resetErrorBoundary} - title={title} /> )} > @@ -74,12 +62,11 @@ export const Layout = (props: LayoutProps) => { ); }; -export interface LayoutProps - extends CoreLayoutProps, - Omit<HtmlHTMLAttributes<HTMLDivElement>, 'title'> { +export interface LayoutProps { appBar?: ComponentType<AppBarProps>; appBarAlwaysOn?: boolean; className?: string; + children: React.ReactNode; error?: ComponentType<ErrorProps>; menu?: ComponentType<MenuProps>; sidebar?: ComponentType<SidebarProps>; diff --git a/packages/ra-ui-materialui/src/layout/Menu.stories.tsx b/packages/ra-ui-materialui/src/layout/Menu.stories.tsx index 225e681491a..5210b2b7707 100644 --- a/packages/ra-ui-materialui/src/layout/Menu.stories.tsx +++ b/packages/ra-ui-materialui/src/layout/Menu.stories.tsx @@ -38,7 +38,9 @@ const DemoList = ({ name }) => ( export const Default = () => { const MenuDefault = () => <Menu hasDashboard={true} dense={false} />; - const DefaultLayout = props => <Layout {...props} menu={MenuDefault} />; + const DefaultLayout = ({ children }) => ( + <Layout menu={MenuDefault}>{children}</Layout> + ); return ( <Admin @@ -59,7 +61,9 @@ export const Default = () => { export const Dense = () => { const MenuDense = () => <Menu hasDashboard={true} dense={true} />; - const LayoutDense = props => <Layout {...props} menu={MenuDense} />; + const LayoutDense = ({ children }) => ( + <Layout menu={MenuDense}>{children}</Layout> + ); return ( <Admin @@ -103,7 +107,9 @@ export const Custom = () => { /> </Menu> ); - const CustomLayout = props => <Layout {...props} menu={CustomMenu} />; + const CustomLayout = ({ children }) => ( + <Layout menu={CustomMenu}>{children}</Layout> + ); return ( <MemoryRouter initialEntries={['/']}> @@ -192,7 +198,9 @@ export const MenuItemChild = () => { </Menu> ); }; - const CustomLayout = props => <Layout {...props} menu={CustomMenu} />; + const CustomLayout = ({ children }) => ( + <Layout menu={CustomMenu}>{children}</Layout> + ); return ( <MemoryRouter initialEntries={['/']}> diff --git a/packages/ra-ui-materialui/src/layout/Menu.tsx b/packages/ra-ui-materialui/src/layout/Menu.tsx index ef71c122403..682e4bc0e1c 100644 --- a/packages/ra-ui-materialui/src/layout/Menu.tsx +++ b/packages/ra-ui-materialui/src/layout/Menu.tsx @@ -12,6 +12,7 @@ import { DashboardMenuItem } from './DashboardMenuItem'; import { MenuItemLink } from './MenuItemLink'; import { ResourceMenuItem } from './ResourceMenuItem'; import { ResourceMenuItems } from './ResourceMenuItems'; +import { useHasDashboard } from 'ra-core'; /** * Renders a menu with one menu item per resource by default. You can also set menu items by hand. @@ -36,21 +37,8 @@ import { ResourceMenuItems } from './ResourceMenuItems'; * ); */ export const Menu = (props: MenuProps) => { - const { - hasDashboard, - children = hasDashboard ? ( - [ - <DashboardMenuItem key="default-dashboard-menu-item" />, - <ResourceMenuItems key="default-resource-menu-items" />, - ] - ) : ( - <ResourceMenuItems /> - ), - - className, - ...rest - } = props; - + const { children, className, ...rest } = props; + const hasDashboard = useHasDashboard(); const [open] = useSidebarState(); return ( @@ -64,7 +52,8 @@ export const Menu = (props: MenuProps) => { )} {...rest} > - {children} + {hasDashboard && !children && <DashboardMenuItem />} + {children ?? <ResourceMenuItems />} </Root> ); }; @@ -74,14 +63,12 @@ export interface MenuProps { children?: ReactNode; className?: string; dense?: boolean; - hasDashboard?: boolean; [key: string]: any; } Menu.propTypes = { className: PropTypes.string, dense: PropTypes.bool, - hasDashboard: PropTypes.bool, }; // re-export MenuItem components for convenience diff --git a/packages/ra-ui-materialui/src/layout/MenuItemLink.tsx b/packages/ra-ui-materialui/src/layout/MenuItemLink.tsx index 0898bb6a101..20c4b7f479d 100644 --- a/packages/ra-ui-materialui/src/layout/MenuItemLink.tsx +++ b/packages/ra-ui-materialui/src/layout/MenuItemLink.tsx @@ -53,7 +53,11 @@ import { useTranslate, useBasename } from 'ra-core'; * import { Layout } from 'react-admin'; * import { Menu } from './Menu'; * - * export const Layout = (props) => <Layout {...props} menu={Menu} />; + * export const Layout = ({ children }) => ( + * <Layout menu={Menu}> + * {children} + * </Layout> + * ); * * // then, use this layout in the <Admin layout> prop: * // in src/App.js diff --git a/packages/ra-ui-materialui/src/layout/NotFound.tsx b/packages/ra-ui-materialui/src/layout/NotFound.tsx index 2e777e5aad4..34c6da1e40f 100644 --- a/packages/ra-ui-materialui/src/layout/NotFound.tsx +++ b/packages/ra-ui-materialui/src/layout/NotFound.tsx @@ -5,14 +5,15 @@ import Button from '@mui/material/Button'; import HotTub from '@mui/icons-material/HotTub'; import History from '@mui/icons-material/History'; -import { useAuthenticated, useTranslate } from 'ra-core'; +import { useAuthenticated, useDefaultTitle, useTranslate } from 'ra-core'; import { Title } from './Title'; export const NotFound = props => { - const { className, title, ...rest } = props; + const { className, ...rest } = props; const translate = useTranslate(); useAuthenticated(); + const title = useDefaultTitle(); return ( <Root className={className} {...sanitizeRestProps(rest)}> <Title defaultTitle={title} /> diff --git a/packages/ra-ui-materialui/src/layout/Title.tsx b/packages/ra-ui-materialui/src/layout/Title.tsx index caa4673301a..5366c8042dc 100644 --- a/packages/ra-ui-materialui/src/layout/Title.tsx +++ b/packages/ra-ui-materialui/src/layout/Title.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import { ReactElement } from 'react'; import { createPortal } from 'react-dom'; import PropTypes from 'prop-types'; -import { RaRecord, warning } from 'ra-core'; +import { RaRecord, TitleComponent, warning } from 'ra-core'; import { PageTitleConfigurable } from './PageTitleConfigurable'; @@ -57,7 +57,7 @@ Title.propTypes = { export interface TitleProps { className?: string; - defaultTitle?: string; + defaultTitle?: TitleComponent; record?: Partial<RaRecord>; title?: string | ReactElement; preferenceKey?: string; diff --git a/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx b/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx index dfe7a41f520..4e90537de4c 100644 --- a/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx +++ b/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx @@ -338,9 +338,9 @@ export const Title = () => ( </Admin> ); -const LayoutWithFooter = props => ( +const LayoutWithFooter = ({ children }) => ( <> - <Layout {...props} /> + <Layout>{children}</Layout> <div style={{ height: '100px', backgroundColor: 'red' }}>Footer</div> </> ); From 0d919067af810cf569b323af568248c52961358e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= <fzaninotto@gmail.com> Date: Thu, 18 Jan 2024 00:21:08 +0100 Subject: [PATCH 2/9] Fix build --- packages/react-admin/src/Admin.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-admin/src/Admin.tsx b/packages/react-admin/src/Admin.tsx index a40cddb86f7..49e454978c4 100644 --- a/packages/react-admin/src/Admin.tsx +++ b/packages/react-admin/src/Admin.tsx @@ -108,7 +108,6 @@ export const Admin = (props: AdminProps) => { loading, loginPage, authCallbackPage, - menu, // deprecated, use a custom layout instead notification, queryClient, requireAuth, @@ -145,7 +144,6 @@ export const Admin = (props: AdminProps) => { layout={layout} dashboard={dashboard} disableTelemetry={disableTelemetry} - menu={menu} catchAll={catchAll} title={title} loading={loading} From d1c1ea7654973bafe84bcb85c5fdbdc0f7d4f42d Mon Sep 17 00:00:00 2001 From: fzaninotto <fzaninotto@gmail.com> Date: Thu, 18 Jan 2024 09:26:58 +0100 Subject: [PATCH 3/9] Revert unrelated change to fix e2e tests --- examples/simple/src/posts/PostList.tsx | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/examples/simple/src/posts/PostList.tsx b/examples/simple/src/posts/PostList.tsx index 006cb72a89a..8a976290f53 100644 --- a/examples/simple/src/posts/PostList.tsx +++ b/examples/simple/src/posts/PostList.tsx @@ -110,13 +110,21 @@ const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ '& .publishedAt': { fontStyle: 'italic' }, })); -const PostListBulkActions = memo(() => ( - <Fragment> - <ResetViewsButton /> - <BulkDeleteButton /> - <BulkExportButton /> - </Fragment> -)); +const PostListBulkActions = memo( + ({ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + children, + ...props + }: { + children?: React.ReactNode; + }) => ( + <Fragment> + <ResetViewsButton {...props} /> + <BulkDeleteButton {...props} /> + <BulkExportButton {...props} /> + </Fragment> + ) +); const PostListActions = () => ( <TopToolbar> From 26a1b88ce5eada0f64ffac935dcdf3a468eb6990 Mon Sep 17 00:00:00 2001 From: fzaninotto <fzaninotto@gmail.com> Date: Mon, 22 Jan 2024 17:23:31 +0100 Subject: [PATCH 4/9] Add upgrade guide --- docs/Upgrade.md | 140 +++++++++++++++++- .../ra-core/src/core/DefaultTitleContext.ts | 12 ++ .../ra-core/src/core/HasDashboardContext.ts | 18 +++ packages/ra-core/src/types.ts | 6 +- 4 files changed, 173 insertions(+), 3 deletions(-) diff --git a/docs/Upgrade.md b/docs/Upgrade.md index d1470ddcb36..21aff445fc5 100644 --- a/docs/Upgrade.md +++ b/docs/Upgrade.md @@ -23,7 +23,7 @@ The React team has published a [migration guide](https://react.dev/blog/2022/03/ React 18 adds out-of-the-box performance improvements by doing more batching by default. -## Drop support for IE11 +## IE11 Is No Longer Supported React-admin v5 uses React 18, which dropped support for Internet Explorer. If you need to support IE11, you'll have to stay on react-admin v4. @@ -133,6 +133,144 @@ Here are the available codemods you may need to run on your codebase: Check out React Query [codemod documentation](https://tanstack.com/query/latest/docs/react/guides/migrating-to-v5#codemod) for more information. +## `<Admin menu>` Is No Longer Supported + +The `<Admin menu>` prop was deprecated since 4.0. It's no longer supported. If you want to customize the application menu, you'll have to do it un a custom Layout instead: + +```diff +-import { Admin } from 'react-admin'; ++import { Admin, Layout } from 'react-admin'; +import { MyMenu } from './MyMenu'; + ++const MyLayout = ({ children }) => ( ++ <Layout menu={MyMenu}>{children}</Layout> ++); + +const App = () => ( +- <Admin menu={MyMenu} dataProvider={dataProvider}> ++ <Admin layout={MyLayout} dataProvider={dataProvider}> + ... + </Admin> +); +``` + +## Custom Layout No Longer Receives Props + +React-admin used to inject 4 props to [custom layouts](https://marmelab.com/react-admin/Admin.html#layout): `children`, `dashboard`, `menu`, and `title`. In react-admin v5, only the `children` prop is injected. + +This means that you'll need to use hooks to get the other props: + +```diff ++import { useHasDashboard, useDefaultTitle } from 'react-admin'; + +-const MyLayout = ({ children, dashboard, title }) => ( ++const MyLayout = ({ children }) => { +- const hasDashboard = !!dashboard; ++ const hasDashboard = useHasDashboard(); ++ const title = useDefaultTitle(); + // ... +} + +const App = () => ( + <Admin layout={MyLayout} dataProvider={dataProvider}> + ... + </Admin> +); +``` + +As for the `menu` prop, it's no longer injected by react-admin because the `<Admin menu>` prop is no longer supported. But you can still customize the menu of the default Layout as before: + +```tsx +import { Layout } from 'react-admin'; +import { MyMenu } from './MyMenu'; + +const MyLayout = ({ children }) => ( + <Layout menu={MyMenu}>{children}</Layout> +); + +const App = () => ( + <Admin layout={MyLayout} dataProvider={dataProvider}> + ... + </Admin> +); +``` + +## Custom App Bars No Longer Receive Props + +React-admin used to inject 2 props to [custom app bars](https://marmelab.com/react-admin/Layout.html#appbar): `open`, and `title`. These deprecated props are no longer injected in v5. If you need them, you'll have to use hooks: + +```diff ++import { useSidebarState, useDefaultTitle } from 'react-admin'; + +-const MyAppBar = ({ open, title }) => ( ++const MyAppBar = () => { ++ const [open] = useSidebarState(); ++ const title = useDefaultTitle(); + // ... +} + +const MyLayout = ({ children }) => ( + <Layout appBar={MyAppBar}>{children}</Layout> +); +``` + +## Custom Menu No Longer Receive Props + +React-admin used to inject one prop to [custom menus](https://marmelab.com/react-admin/Layout.html#menu): `hasDashboard`. This deprecated prop is no longer injected in v5. If you need it, you'll have to use the `useHasDashboard` hook instead: + +```diff ++import { useHasDashboard } from 'react-admin'; + +-const MyMenu = ({ hasDashboard }) => ( ++const MyMenu = () => { ++ const hasDashboard = useHasDashboard(); + // ... +} + +const MyLayout = ({ children }) => ( + <Layout menu={MyMenu}>{children}</Layout> +); +``` + +## Custom Error Page No Longer Receives Title + +React-admin injects several props to [custom error pages](https://marmelab.com/react-admin/Layout.html#error), including the default app `title`. This prop is no longer injected in v5. If you need it, you'll have to use the `useDefaultTitle` hook instead: + +```diff ++import { useDefaultTitle } from 'react-admin'; + +-const MyError = ({ error, errorInfo, title }) => ( ++const MyError = ({ error, errorInfo }) => { ++ const title = useDefaultTitle(); + // ... +} + +const MyLayout = ({ children }) => ( + <Layout error={MyError}>{children}</Layout> +); +``` + +## Custom Catch All No Longer Receives Title + +React-admin used to inject the default app `title` to [custom catch all pages](https://marmelab.com/react-admin/Admin.html#catchall). This prop is no longer injected in v5. If you need it, you'll have to use the `useDefaultTitle` hook instead: + +```diff ++import { useDefaultTitle } from 'react-admin'; + +-const MyCatchAll = ({ title }) => ( ++const MyCatchAll = () => { ++ const title = useDefaultTitle(); + // ... +} + +const App = () => ( + <Admin catchAll={MyCatchAll} dataProvider={dataProvider}> + ... + </Admin> +); +``` +``` + ## Removed deprecated hooks The following deprecated hooks have been removed diff --git a/packages/ra-core/src/core/DefaultTitleContext.ts b/packages/ra-core/src/core/DefaultTitleContext.ts index 10a82b0815d..f471ade06b3 100644 --- a/packages/ra-core/src/core/DefaultTitleContext.ts +++ b/packages/ra-core/src/core/DefaultTitleContext.ts @@ -5,4 +5,16 @@ export const DefaultTitleContext = createContext<TitleComponent>('React Admin'); export const DefaultTitleContextProvider = DefaultTitleContext.Provider; +/** + * Get the application title defined at the `<Admin>` level + * + * @private + * @example + * import { useDefaultTitle } from 'react-admin'; + * + * const AppBar = () => { + * const defaultTitle = useDefaultTitle(); + * return <span>{defaultTitle}</span>; + * } + */ export const useDefaultTitle = () => useContext(DefaultTitleContext); diff --git a/packages/ra-core/src/core/HasDashboardContext.ts b/packages/ra-core/src/core/HasDashboardContext.ts index 153c2c3fc22..c47449cfd87 100644 --- a/packages/ra-core/src/core/HasDashboardContext.ts +++ b/packages/ra-core/src/core/HasDashboardContext.ts @@ -4,4 +4,22 @@ export const HasDashboardContext = createContext<boolean>(undefined); export const HasDashboardContextProvider = HasDashboardContext.Provider; +/** + * Returns true if the app has a dashboard defined at the <Admin> level. + * + * @private + * @example + * import { useHasDashboard } from 'react-admin'; + * + * const MyMenu = () => { + * const hasDashboard = useHasDashboard(); + * return ( + * <Menu> + * {hasDashboard && <DashboardMenuItem />} + * <MenuItemLink to="/posts" /> + * <MenuItemLink to="/comments" /> + * </Menu> + * ); + * } + */ export const useHasDashboard = () => useContext(HasDashboardContext); diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index ed754b19b83..9aa4972364f 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -318,9 +318,11 @@ export type CatchAllComponent = ComponentType<{ title?: TitleComponent }>; export type LoginComponent = ComponentType<{}> | ReactElement<any>; export type DashboardComponent = ComponentType<WithPermissionsChildrenParams>; -export type LayoutComponent = ComponentType<{ +export interface CoreLayoutProps { children: ReactNode; -}>; +} + +export type LayoutComponent = ComponentType<CoreLayoutProps>; export type LoadingComponent = ComponentType<{ loadingPrimary?: string; loadingSecondary?: string; From f2875af8d079323bfd042a1413b518c875877b60 Mon Sep 17 00:00:00 2001 From: fzaninotto <fzaninotto@gmail.com> Date: Mon, 22 Jan 2024 17:25:27 +0100 Subject: [PATCH 5/9] Document useDefaultTitle hook --- docs/Admin.md | 11 +++++++++++ packages/ra-core/src/core/DefaultTitleContext.ts | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/Admin.md b/docs/Admin.md index be3011e7892..11fd5bce23e 100644 --- a/docs/Admin.md +++ b/docs/Admin.md @@ -882,6 +882,17 @@ const App = () => ( ); ``` +If you need to display this application title somewhere in your app, use the `useDefaultTitle` hook: + +```tsx +import { useDefaultTitle } from 'react-admin'; + +const MyTitle = () => { + const defaultTitle = useDefaultTitle(); + return <span>{defaultTitle}</span>; // My Custom Admin +}; +``` + ## Adding Custom Pages The [`children`](#children) prop of the `<Admin>` component define the routes of the application. diff --git a/packages/ra-core/src/core/DefaultTitleContext.ts b/packages/ra-core/src/core/DefaultTitleContext.ts index f471ade06b3..40abfc055d3 100644 --- a/packages/ra-core/src/core/DefaultTitleContext.ts +++ b/packages/ra-core/src/core/DefaultTitleContext.ts @@ -8,7 +8,6 @@ export const DefaultTitleContextProvider = DefaultTitleContext.Provider; /** * Get the application title defined at the `<Admin>` level * - * @private * @example * import { useDefaultTitle } from 'react-admin'; * From 674d871d9a8c7f937b075882d9011d1f5485758a Mon Sep 17 00:00:00 2001 From: fzaninotto <fzaninotto@gmail.com> Date: Mon, 22 Jan 2024 17:28:51 +0100 Subject: [PATCH 6/9] Document default title in custom Error page --- docs/Layout.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Layout.md b/docs/Layout.md index ceb770be7be..f446e9c66ff 100644 --- a/docs/Layout.md +++ b/docs/Layout.md @@ -164,7 +164,7 @@ import * as React from 'react'; import Button from '@mui/material/Button'; import ErrorIcon from '@mui/icons-material/Report'; import History from '@mui/icons-material/History'; -import { Title, useTranslate } from 'react-admin'; +import { Title, useTranslate, useDefaultTitle } from 'react-admin'; import { useLocation } from 'react-router-dom'; export const MyError = ({ @@ -183,9 +183,10 @@ export const MyError = ({ }, [pathname, resetErrorBoundary]); const translate = useTranslate(); + const defaultTitle = useDefaultTitle(); return ( <div> - <Title title="Error" /> + <Title title={`${defaultTitle}: Error`} /> <h1><ErrorIcon /> Something Went Wrong </h1> <div>A client error occurred and your request couldn't be completed.</div> {process.env.NODE_ENV !== 'production' && ( From a6e4272e1e27b37963040afc4e45e30a2da8980f Mon Sep 17 00:00:00 2001 From: fzaninotto <fzaninotto@gmail.com> Date: Mon, 22 Jan 2024 17:36:48 +0100 Subject: [PATCH 7/9] Improve doc for custom layout --- docs/Admin.md | 25 ++++++++++++++++++++++++- docs/Layout.md | 20 ++++++++++++++++---- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/docs/Admin.md b/docs/Admin.md index 11fd5bce23e..2c3f15ac6a0 100644 --- a/docs/Admin.md +++ b/docs/Admin.md @@ -621,7 +621,30 @@ const App = () => ( Refer to each layout component documentation to understand the props it accepts. -Finally, you can also pass a custom component as the `layout` prop. It must contain a `{children}` placeholder, where react-admin will render the page content. Check [the custom layout documentation](./Layout.md#writing-a-layout-from-scratch) for examples, and use the [default `<Layout>`](https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/layout/Layout.tsx) as a starting point. +Finally, you can also pass a custom component as the `layout` prop. Your custom layout will receive the page content as `children`, so it should render it somewhere. + +```tsx +// in src/MyLayout.js +export const MyLayout = ({ children }) => ( + <div> + <h1>My App</h1> + <main>{children}</main> + </div> +); + +// in src/App.js +import { Admin } from 'react-admin'; +import { dataProvider } from './dataProvider'; +import { MyLayout } from './MyLayout'; + +const App = () => ( + <Admin dataProvider={dataProvider} layout={MyLayout}> + // ... + </Admin> +); +``` + +Check [the custom layout documentation](./Layout.md#writing-a-layout-from-scratch) for examples, and use the [default `<Layout>`](https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/layout/Layout.tsx) as a starting point. ## `loginPage` diff --git a/docs/Layout.md b/docs/Layout.md index f446e9c66ff..59b524ca659 100644 --- a/docs/Layout.md +++ b/docs/Layout.md @@ -455,16 +455,28 @@ You can also write your own layout component from scratch (see below). ## Writing A Layout From Scratch -For more custom layouts, write a component from scratch. It must contain a `{children}` placeholder, where react-admin will render the resources. Use the [default layout](https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/layout/Layout.tsx) as a starting point. Here is a simplified version (with no responsive support): +For more custom layouts, write a component from scratch. Your custom layout will receive the page content as `children`, so it should render it somewhere. + +In its simplest form, a custom layout is just a component that renders its children: + +```tsx +const MyLayout = ({ children }) => ( + <div> + <h1>My App</h1> + <main>{children}</main> + </div> +); +``` + +You can use the [default layout](https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/layout/Layout.tsx) as a starting point for your custom layout. Here is a simplified version (with no responsive support): {% raw %} ```jsx // in src/MyLayout.js -import * as React from 'react'; import { Box } from '@mui/material'; import { AppBar, Menu, Sidebar } from 'react-admin'; -const MyLayout = ({ children, dashboard }) => ( +const MyLayout = ({ children }) => ( <Box display="flex" flexDirection="column" @@ -481,7 +493,7 @@ const MyLayout = ({ children, dashboard }) => ( <AppBar /> <Box display="flex" flexGrow={1}> <Sidebar> - <Menu hasDashboard={!!dashboard} /> + <Menu /> </Sidebar> <Box display="flex" From 8813a1deb8453f94b483e2ae4e2d2aa50d41cb4f Mon Sep 17 00:00:00 2001 From: Francois Zaninotto <francois@marmelab.com> Date: Tue, 23 Jan 2024 15:12:50 +0100 Subject: [PATCH 8/9] Update docs/Upgrade.md Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com> --- docs/Upgrade.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Upgrade.md b/docs/Upgrade.md index 21aff445fc5..6fc1d15e2aa 100644 --- a/docs/Upgrade.md +++ b/docs/Upgrade.md @@ -135,7 +135,7 @@ Check out React Query [codemod documentation](https://tanstack.com/query/latest/ ## `<Admin menu>` Is No Longer Supported -The `<Admin menu>` prop was deprecated since 4.0. It's no longer supported. If you want to customize the application menu, you'll have to do it un a custom Layout instead: +The `<Admin menu>` prop was deprecated since 4.0. It's no longer supported. If you want to customize the application menu, you'll have to do it in a custom Layout instead: ```diff -import { Admin } from 'react-admin'; From 9c5fb1d26e8202e239331a0aeeb1f5742915e1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= <fzaninotto@gmail.com> Date: Tue, 23 Jan 2024 15:16:51 +0100 Subject: [PATCH 9/9] Add types to `<Admin layout>` example --- docs/Admin.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Admin.md b/docs/Admin.md index 2c3f15ac6a0..3c67090b564 100644 --- a/docs/Admin.md +++ b/docs/Admin.md @@ -595,10 +595,11 @@ Layout components can be customized via props. For instance, you can pass a cust ```tsx // in src/MyLayout.js +import type { ReactNode } from 'react'; import { Layout } from 'react-admin'; import MyMenu from './MyMenu'; -export const MyLayout = ({ children }) => ( +export const MyLayout = ({ children }: { children: ReactNode }) => ( <Layout menu={MyMenu}> {children} </Layout> @@ -625,7 +626,8 @@ Finally, you can also pass a custom component as the `layout` prop. Your custom ```tsx // in src/MyLayout.js -export const MyLayout = ({ children }) => ( +import type { ReactNode } from 'react'; +export const MyLayout = ({ children }: { children: ReactNode }) => ( <div> <h1>My App</h1> <main>{children}</main>