Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add <Menu.ResourceItem>component to facilitate menu customization #8392

Merged
merged 2 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 62 additions & 27 deletions docs/Menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,19 @@ This component renders a menu, with one menu item per resource by default. You c

## Usage

You can create a custom menu component using react-admin's `<Menu>` and `<Menu.Item>` components:
You can create a custom menu component using react-admin's `<Menu>`, `<Menu.ResourceItem>`, and `<Menu.Item>` components:

```jsx
// in src/MyMenu.js
import { Menu } from 'react-admin';

import BookIcon from '@mui/icons-material/Book';
import ChatBubbleIcon from '@mui/icons-material/ChatBubble';
import PeopleIcon from '@mui/icons-material/People';
import LabelIcon from '@mui/icons-material/Label';

export const MyMenu = () => (
<Menu>
<Menu.DashboardItem />
<Menu.Item to="/posts" primaryText="Posts" leftIcon={<BookIcon />}/>
<Menu.Item to="/comments" primaryText="Comments" leftIcon={<ChatBubbleIcon />}/>
<Menu.Item to="/users" primaryText="Users" leftIcon={<PeopleIcon />}/>
<Menu.ResourceItem name="posts" />
<Menu.ResourceItem name="comments" >
<Menu.ResourceItem name="users" />
<Menu.Item to="/custom-route" primaryText="Miscellaneous" leftIcon={<LabelIcon />}/>
</Menu>
);
Expand Down Expand Up @@ -95,23 +91,19 @@ const App = () => (

![standard menu with dashboard](./img/menu-with-dashboard.webp)

If you pass children to `<Menu>`, they will override the default menu items. Use `<Menu.Item>` to add custom menu items, and `<Menu.DashboardItem>` to add a menu item for the dashboard.
If you pass children to `<Menu>`, they will override the default menu items. Use `<Menu.DashboardItem>` to add a menu item for the dashboard, `<Menu.ResourceItem>` to add menu items for a resource list, and `<Menu.Item>` to add custom menu items.

```jsx
// in src/MyMenu.js
import { Menu } from 'react-admin';

import BookIcon from '@mui/icons-material/Book';
import ChatBubbleIcon from '@mui/icons-material/ChatBubble';
import PeopleIcon from '@mui/icons-material/People';
import LabelIcon from '@mui/icons-material/Label';

export const MyMenu = () => (
<Menu>
<Menu.DashboardItem />
<Menu.Item to="/posts" primaryText="Posts" leftIcon={<BookIcon />}/>
<Menu.Item to="/comments" primaryText="Comments" leftIcon={<ChatBubbleIcon />}/>
<Menu.Item to="/users" primaryText="Users" leftIcon={<PeopleIcon />}/>
<Menu.ResourceItem name="posts" />
<Menu.ResourceItem name="comments" >
<Menu.ResourceItem name="users" />
<Menu.Item to="/custom-route" primaryText="Miscellaneous" leftIcon={<LabelIcon />}/>
</Menu>
);
Expand Down Expand Up @@ -217,31 +209,74 @@ export const MyMenu = () => (

Clicking on the dashboard menu item leads to the `/` route and renders the component defined in [the `<Admin dashboard>` prop](./Admin.md#dashboard).

## `<Menu.ResourceItem>`

The `<Menu.ResourceItem>` component displays a menu item for the list page of a resource, based on the resource name.

```jsx
import { Menu } from 'react-admin';

export const MyMenu = () => (
<Menu>
<Menu.ResourceItem name="posts" />
<Menu.ResourceItem name="comments" />
<Menu.ResourceItem name="tags" />
<Menu.ResourceItem name="users" />
</Menu>
);
```

`<Menu.ResourceItem>` renders a menu item for a resource based on its name, using the resource label and icon defined in the corresponding `<Resource>` component.

So the following code:

```jsx
<Menu.ResourceItem name="posts" />
```

uses the following resource definition:

```jsx
<Resource name="posts" list={PostList} icon={BookIcon} />
```

and translates to:

```jsx
<Menu.Item to="/posts" primaryText="Posts" leftIcon={<BookIcon />}/>
```

## Creating Menu Items For Resources

Developers often want to *add* custom menu items in addition to the default ones. But passing `children` to `<Menu>` actually *replaces* the default menu items.
If you want to reorder the default menu, create a new Menu and use `<Menu.ResourceItem>` components as children.

```jsx
// in src/MyMenu.js
import { Menu } from 'react-admin';

export const MyMenu = () => (
<Menu>
<Menu.ResourceItem name="posts" />
<Menu.ResourceItem name="comments" />
<Menu.ResourceItem name="tags" />
<Menu.ResourceItem name="users" />
</Menu>
);
```

If you want to render a custom menu item and the default resource menu items, use the `useResourceDefinitions` hook to retrieve the list of resources and create one menu item per resource.
Passing `children` to `<Menu>` actually *replaces* the default menu items. If you want to render a custom menu item **in addition to** the default resource menu items, use the `useResourceDefinitions` hook to retrieve the list of resources, and the `<Menu.ResourceItem>` component to create one menu item per resource.

```jsx
// in src/MyMenu.js
import * as React from 'react';
import { createElement } from 'react';
import { Menu, useResourceDefinitions } from 'react-admin';
import LabelIcon from '@mui/icons-material/Label';

export const MyMenu = () => {
const resources = useResourceDefinitions();

return (
<Menu>
{Object.keys(resources).map(name => (
<Menu.Item
key={name}
to={`/${name}`}
primaryText={resources[name].options && resources[name].options.label || name}
leftIcon={createElement(resources[name].icon)}
/>
<Menu.ResourceItem key={name} name={name} />
))}
<Menu.Item to="/custom-route" primaryText="Miscellaneous" leftIcon={<LabelIcon />} />
</Menu>
Expand Down
32 changes: 5 additions & 27 deletions packages/ra-ui-materialui/src/layout/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import * as React from 'react';
import { ReactNode, createElement } from 'react';
import { ReactNode } from 'react';
import { MenuList } from '@mui/material';
import { styled } from '@mui/material/styles';
import DefaultIcon from '@mui/icons-material/ViewList';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import clsx from 'clsx';
import {
useResourceDefinitions,
useGetResourceLabel,
useCreatePath,
} from 'ra-core';
import { useResourceDefinitions } from 'ra-core';

import { DRAWER_WIDTH, CLOSED_DRAWER_WIDTH } from './Sidebar';
import { useSidebarState } from './useSidebarState';
import { DashboardMenuItem } from './DashboardMenuItem';
import { MenuItemLink } from './MenuItemLink';
import { ResourceMenuItem } from './ResourceMenuItem';

/**
* Renders a menu with one menu item per resource by default. You can also set menu items by hand.
Expand All @@ -41,8 +37,6 @@ import { MenuItemLink } from './MenuItemLink';
*/
export const Menu = (props: MenuProps) => {
const resources = useResourceDefinitions();
const getResourceLabel = useGetResourceLabel();
const createPath = useCreatePath();
const {
hasDashboard,
children = [
Expand All @@ -51,24 +45,7 @@ export const Menu = (props: MenuProps) => {
) : null,
...Object.keys(resources)
.filter(name => resources[name].hasList)
.map(name => (
<MenuItemLink
key={name}
to={createPath({
resource: name,
type: 'list',
})}
state={{ _scrollToTop: true }}
primaryText={getResourceLabel(name, 2)}
leftIcon={
resources[name].icon ? (
createElement(resources[name].icon)
) : (
<DefaultIcon />
)
}
/>
)),
.map(name => <ResourceMenuItem key={name} name={name} />),
],
className,
...rest
Expand Down Expand Up @@ -110,6 +87,7 @@ Menu.propTypes = {
// re-export MenuItem commponents for convenience
Menu.Item = MenuItemLink;
Menu.DashboardItem = DashboardMenuItem;
Menu.ResourceItem = ResourceMenuItem;

const PREFIX = 'RaMenu';

Expand Down
34 changes: 34 additions & 0 deletions packages/ra-ui-materialui/src/layout/ResourceMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { createElement } from 'react';
import DefaultIcon from '@mui/icons-material/ViewList';

import {
useResourceDefinitions,
useGetResourceLabel,
useCreatePath,
} from 'ra-core';

import { MenuItemLink } from './MenuItemLink';

export const ResourceMenuItem = ({ name }: { name: string }) => {
const resources = useResourceDefinitions();
const getResourceLabel = useGetResourceLabel();
const createPath = useCreatePath();
return (
<MenuItemLink
to={createPath({
resource: name,
type: 'list',
})}
state={{ _scrollToTop: true }}
primaryText={getResourceLabel(name, 2)}
leftIcon={
resources[name].icon ? (
createElement(resources[name].icon)
) : (
<DefaultIcon />
)
}
/>
);
};
1 change: 1 addition & 0 deletions packages/ra-ui-materialui/src/layout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './MenuItemLink';
export * from './NotFound';
export * from './Notification';
export * from './PageTitleConfigurable';
export * from './ResourceMenuItem';
export * from './Sidebar';
export * from './SidebarToggleButton';
export * from './Theme';
Expand Down