ra-navigation

Complex admins with many resources need to organize their pages in a tree structure, and provide navigation widgets to help their users find their way in that structure. ra-navigation offers specialized React components to implement a *breadcrumb, menu hierarchies, and sliding menu panels, together with hooks to handle the user location.

Test it live in the Enterprise Edition Storybook and in the e-commerce demo.

Installation

npm install --save @react-admin/ra-navigation
# or
yarn add @react-admin/ra-navigation

Tip: ra-navigation is part of the React-Admin Enterprise Edition, and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package.

Concepts

ra-navigation introduces the concept of application location (AppLocation) which is distinct from the browser location. This distinction is important as it allows to structure the navigation UI independently of the routes (having multiple resources under the same "category" for example, like Catalog / Products and Catalog / Categories).

Each page in a react-admin application can define its app location as a string, stored in a shared context. UI components use that context to display navigation informations and controls (menu, breadcrumb, or your custom components) in a consistent way. Pages can also provide related data with the location, such as the current record for example.

By default, standard react-admin views such as List, Edit, Create and Show define their app location:

  • List: [resource].list
  • Create: [resource].create
  • Edit: [resource].edit. It will also provide the current record
  • Show: [resource].show. It will also provide the current record

A Categories List page can define its location as 'catalog.categories.list' (instead of the default 'categories.list'), i.e. as a child of the Catalog location in the app tree structure. That way, when users visit the Categories List Page, they see a <Breadcrumb> looking like the following:

Dashboard > Catalog > Categories

And they should see a <MultiLevelMenu> looking like the following:

> Dashboard
> Catalog
   > Categories
   > Products

Usage

The first thing to do is to make the AppLocation available everywhere by wrapping the <Layout> component inside an <AppLocationContext>:

import { AppLocationContext } from '@react-admin/ra-navigation';
import { Admin, Resource, Layout } from 'react-admin';

const MyLayout = props => {
    const classes = useStyles();

    return (
        <AppLocationContext>
            <Layout {...props} />
        </AppLocationContext>
    );
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
    </Admin>
);
import { AppLocationContext } from '@react-admin/ra-navigation';
import { Admin, Resource, Layout } from 'react-admin';

const MyLayout = props => {
    const classes = useStyles();

    return (
        <AppLocationContext>
            <Layout {...props} />
        </AppLocationContext>
    );
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
    </Admin>
);

You can now either leverage the provided components such as the <Breadcrumb> or <MultiLevelMenu>, or build your own.

Adding a Breadcrumb Path to Every Page

A breadcrumb

The <Breadcrumb /> component allows to include a breadcrumb inside our application. The layout of the app must be inside a AppLocationContext.

By default, the <Breadcrumb /> item will not render anything. To turn on the breadcrumb resolving from your current react-admin resources, you'll need to provide a <ResourceBreacrumbItems /> component as <Breadcrumb /> child.

import React from 'react';

import {
    AppLocationContext,
    Breadcrumb,
    ResourceBreadcrumbItems,
} from '@react-admin/ra-navigation';

import { Admin, Resource, Layout } from 'react-admin';

import PostList from './PostList';
import PostEdit from './PostEdit';
import PostShow from './PostShow';
import PostCreate from './PostCreate';

const MyLayout = props => (
    <AppLocationContext hasDashboard={props.hasDashboard}>
        <Layout {...props} />
    </AppLocationContext>
);

// Include this component in your views. See below
const MyBreadcrumb = props => (
    <Breadcrumb {...props}>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource
            name="posts"
            list={PostList}
            edit={PostEdit}
            show={PostShow}
            create={PostCreate}
        />
    </Admin>
);
import React from 'react';

import {
    AppLocationContext,
    Breadcrumb,
    ResourceBreadcrumbItems,
} from '@react-admin/ra-navigation';

import { Admin, Resource, Layout } from 'react-admin';

import PostList from './PostList';
import PostEdit from './PostEdit';
import PostShow from './PostShow';
import PostCreate from './PostCreate';

const MyLayout = props => (
    <AppLocationContext hasDashboard={props.hasDashboard}>
        <Layout {...props} />
    </AppLocationContext>
);

// Include this component in your views. See below
const MyBreadcrumb = props => (
    <Breadcrumb {...props}>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource
            name="posts"
            list={PostList}
            edit={PostEdit}
            show={PostShow}
            create={PostCreate}
        />
    </Admin>
);

It'll display respectively:

  • "Posts" on Post List
  • "Posts / Show #1" on Post Show with id = 1
  • "Posts / Edit #1" on Post Edit with id = 1
  • "Posts / Create" on Post Create

We don't recommend adding the Breadcrumb inside your layout as it would add unecessary space above the current view. Instead, you should add it directly on the views. For react-admin views such as Create, Show and Edit, you can set the variant prop to actions to apply custom styles which make it fit inside a <TopToolbar> used in actions:

import * as React from 'react';
import { TopToolbar, ShowButton } from 'react-admin';
import { Breadcrumb } from '@react-admin/ra-navigation';

const PostEditActions = ({ basePath, data, resource }) => (
    <TopToolbar>
        <Breadcrumb variant="actions" />
        <ShowButton basePath={basePath} record={data} />
    </TopToolbar>
);

export const PostEdit = props => (
    <Edit actions={<PostEditActions />} {...props}>
        ...
    </Edit>
);
import * as React from 'react';
import { TopToolbar, ShowButton } from 'react-admin';
import { Breadcrumb } from '@react-admin/ra-navigation';

const PostEditActions = ({ basePath, data, resource }) => (
    <TopToolbar>
        <Breadcrumb variant="actions" />
        <ShowButton basePath={basePath} record={data} />
    </TopToolbar>
);

export const PostEdit = props => (
    <Edit actions={<PostEditActions />} {...props}>
        ...
    </Edit>
);

The ra-enterprise package includes alternative version of all react-admin views with the breadcrumb already included.

Using the Dashboard Page as the Root Item

If the app have a dashboard page, you can automatically set the root of the Breadcrumb to this page in three possible ways:

  1. By passing the hasDashboard prop to the <AppLocationContext>
const MyLayout = props => (
    <AppLocationContext hasDashboard={!!props.dashboard}>
        <Layout {...props} />
    </AppLocationContext>
);
const MyLayout = props => (
    <AppLocationContext hasDashboard={!!props.dashboard}>
        <Layout {...props} />
    </AppLocationContext>
);
  1. By passing the dashboard prop to the Component
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
    <Breadcrumb dashboard={dashboard}>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
    <Breadcrumb dashboard={dashboard}>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);
  1. By passing a hasDashboard prop to the Component
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
    <Breadcrumb hasDashboard>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
    <Breadcrumb hasDashboard>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);

By doing this, the breadcrumb will now show respectively:

  • "Dashboard / Posts" on Post List
  • "Dashboard / Posts / Show #1" on Post Show with id = 1
  • "Dashboard / Posts / Edit #1" on Post Edit with id = 1
  • "Dashboard / Posts / Create" on Post Create

Adding Custom Breadcrumb Items with the <BreadcrumbItem> Component

It's also possible to define a custom breadcrumb tree inside <Breadcrumb /> using a <BreadcrumbItem /> tree in it.

This way, custom routes can also be displayed inside the breadcrumb.

import React from 'react';
import {
    AppLocationContext,
    Breadcrumb,
    ResourceBreadcrumbItems,
    useDefineAppLocation,
} from '@react-admin/ra-navigation';
import { Admin, Resource, Layout, List } from 'react-admin';
import { Route } from 'react-router-dom';

const UserPreferences = () => {
    useDefineAppLocation('myhome.user.preferences');
    return <span>My Preferences</span>;
};

const routes = [
    <Route exact path="/user/preferences" component={UserPreferences} />,
];

const MyLayout = props => (
    <AppLocationContext hasDashboard={!!props.dashboard}>
        <Layout {...props} />
    </AppLocationContext>
);

// Use it in your views
const MyBreadcrumb = () => (
    <Breadcrumb>
        <ResourceBreadcrumbItems />
        <BreadcrumbItem name="myhome" label="Home">
            <BreadcrumbItem name="user" label="User">
                <BreadcrumbItem name="preferences" label="Preferences" />
                // It also supports translation keys
                <BreadcrumbItem
                    name="preferences"
                    label="myapp.breadcrumb.preferences"
                />
            </BreadcrumbItem>
        </BreadcrumbItem>
    </Breadcrumb>
);

const PostList = props => (
    <>
        <MyBreadcrumb />
        <List {...props}>{/* ... */}</List>
    </>
);

const App = () => (
    <Admin dataProvider={dataProvider} customRoutes={routes} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
    </Admin>
);
import React from 'react';
import {
    AppLocationContext,
    Breadcrumb,
    ResourceBreadcrumbItems,
    useDefineAppLocation,
} from '@react-admin/ra-navigation';
import { Admin, Resource, Layout, List } from 'react-admin';
import { Route } from 'react-router-dom';

const UserPreferences = () => {
    useDefineAppLocation('myhome.user.preferences');
    return <span>My Preferences</span>;
};

const routes = [
    <Route exact path="/user/preferences" component={UserPreferences} />,
];

const MyLayout = props => (
    <AppLocationContext hasDashboard={!!props.dashboard}>
        <Layout {...props} />
    </AppLocationContext>
);

// Use it in your views
const MyBreadcrumb = () => (
    <Breadcrumb>
        <ResourceBreadcrumbItems />
        <BreadcrumbItem name="myhome" label="Home">
            <BreadcrumbItem name="user" label="User">
                <BreadcrumbItem name="preferences" label="Preferences" />
                // It also supports translation keys
                <BreadcrumbItem
                    name="preferences"
                    label="myapp.breadcrumb.preferences"
                />
            </BreadcrumbItem>
        </BreadcrumbItem>
    </Breadcrumb>
);

const PostList = props => (
    <>
        <MyBreadcrumb />
        <List {...props}>{/* ... */}</List>
    </>
);

const App = () => (
    <Admin dataProvider={dataProvider} customRoutes={routes} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
    </Admin>
);

The displayed path will be "Home / User / Preferences" on "/user/preferences".

Warning:

We used myhome in this exemple and not home because it is a reserved word used for the Dashboard page when it exists.

Overriding the Resource Breadcrumb Items

In some cases, it's useful to override the default resource breadcrumb path. eg: to add custom label instead of "Show #1", "Edit #1", ...

This can be done by disabling concerned resources (enabling only ones we don't customize) in the <ResourceBreadcrumbItems /> "resources" prop and declare them manually.

import React from 'react';
import {
    AppLocationContext,
    Breadcrumb,
    ResourceBreadcrumbItems,
} from '@react-admin/ra-navigation';
import { Admin, Resource, Layout, linkToRecord, List } from 'react-admin';

const MyBreadcrumb = ({ children, ...props }) => (
    <Breadcrumb>
        <ResourceBreadcrumbItems resources={['otherResources']} />
        <BreadcrumbItem name="posts" label="Posts">
            <BreadcrumbItem
                name="edit"
                label={({ record }) => `Edit "${record.title}"`}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/edit`
                }
            />
            <BreadcrumbItem
                name="show"
                label={({ record }) => record.title}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/show`
                }
            />
            <BreadcrumbItem name="list" label="My Post List" />
            <BreadcrumbItem name="create" label="Let's write a Post!" />
        </BreadcrumbItem>
    </Breadcrumb>
);

const PostList = props => (
    <>
        <MyBreadcrumb />
        <List {...props}>{/* ... */}</List>
    </>
);

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
        <Resource name="otherResource" />
    </Admin>
);
import React from 'react';
import {
    AppLocationContext,
    Breadcrumb,
    ResourceBreadcrumbItems,
} from '@react-admin/ra-navigation';
import { Admin, Resource, Layout, linkToRecord, List } from 'react-admin';

const MyBreadcrumb = ({ children, ...props }) => (
    <Breadcrumb>
        <ResourceBreadcrumbItems resources={['otherResources']} />
        <BreadcrumbItem name="posts" label="Posts">
            <BreadcrumbItem
                name="edit"
                label={({ record }) => `Edit "${record.title}"`}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/edit`
                }
            />
            <BreadcrumbItem
                name="show"
                label={({ record }) => record.title}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/show`
                }
            />
            <BreadcrumbItem name="list" label="My Post List" />
            <BreadcrumbItem name="create" label="Let's write a Post!" />
        </BreadcrumbItem>
    </Breadcrumb>
);

const PostList = props => (
    <>
        <MyBreadcrumb />
        <List {...props}>{/* ... */}</List>
    </>
);

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
        <Resource name="otherResource" />
    </Admin>
);

Grouping Menus Into Submenus

MultiLevelMenu

When a React-admin application grows significantly, the default menu might not be the best solution. The <MultiLevelMenu> can help organize navigation.

It offers menu items with support for an infinite numbers and levels of sub menus, and category items which display their children in a sliding panel, keeping things out of the way and providing a cleaner navigation.

In order to use it, the layout of the app must be inside a <AppLocationContext>.

The <MultiLevelMenu> accepts <MenuItem> components as its children. They are very similar to the default <MenuItemLink> from React-Admin, except that they accepts other <MenuItem> as their children.

Those children will be rendered inside a collapsible panel.

The <MenuItem> component accepts a name, a label and an optional icon prop.

import { Admin, Layout, Resource } from 'react-admin';
import {
    AppLocationContext,
    MenuItem,
    MultiLevelMenu,
} from '@react-admin/ra-navigation';
import { Dashboard } from './Dashboard';
import { dataProvider } from './dataProvider';
import { SongList } from './songs';
import { ArtistList } from './artists';

const MyMenu = () => (
    <MultiLevelMenu>
        <MenuItem name="dashboard" to="/" exact label="Dashboard" />
        <MenuItem name="songs" to="/songs" label="Songs" />
        {/* The empty filter is required to avoid falling back to the previously set filter */}
        <MenuItem name="artists" to={'/artists?filter={}'} label="Artists">
            <MenuItem
                name="artists.rock"
                to={'/artists?filter={"type":"Rock"}'}
                label="Rock"
            >
                <MenuItem
                    name="artists.rock.pop"
                    to={'/artists?filter={"type":"Pop Rock"}'}
                    label="Pop Rock"
                />
                <MenuItem
                    name="artists.rock.folk"
                    to={'/artists?filter={"type":"Folk Rock"}'}
                    label="Folk Rock"
                />
            </MenuItem>
            <MenuItem
                name="artists.jazz"
                to={'/artists?filter={"type":"Jazz"}'}
                label="Jazz"
            >
                <MenuItem
                    name="artists.jazz.rb"
                    to={'/artists?filter={"type":"RB"}'}
                    label="R&B"
                />
            </MenuItem>
        </MenuItem>
    </MultiLevelMenu>
);

const MyLayout = props => {
    return (
        <AppLocationContext>
            <Layout {...props} menu={MyMenu} />
        </AppLocationContext>
    );
};

export const Basic = () => (
    <Admin dashboard={Dashboard} dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="songs" list={SongList} />
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
import { Admin, Layout, Resource } from 'react-admin';
import {
    AppLocationContext,
    MenuItem,
    MultiLevelMenu,
} from '@react-admin/ra-navigation';
import { Dashboard } from './Dashboard';
import { dataProvider } from './dataProvider';
import { SongList } from './songs';
import { ArtistList } from './artists';

const MyMenu = () => (
    <MultiLevelMenu>
        <MenuItem name="dashboard" to="/" exact label="Dashboard" />
        <MenuItem name="songs" to="/songs" label="Songs" />
        {/* The empty filter is required to avoid falling back to the previously set filter */}
        <MenuItem name="artists" to={'/artists?filter={}'} label="Artists">
            <MenuItem
                name="artists.rock"
                to={'/artists?filter={"type":"Rock"}'}
                label="Rock"
            >
                <MenuItem
                    name="artists.rock.pop"
                    to={'/artists?filter={"type":"Pop Rock"}'}
                    label="Pop Rock"
                />
                <MenuItem
                    name="artists.rock.folk"
                    to={'/artists?filter={"type":"Folk Rock"}'}
                    label="Folk Rock"
                />
            </MenuItem>
            <MenuItem
                name="artists.jazz"
                to={'/artists?filter={"type":"Jazz"}'}
                label="Jazz"
            >
                <MenuItem
                    name="artists.jazz.rb"
                    to={'/artists?filter={"type":"RB"}'}
                    label="R&B"
                />
            </MenuItem>
        </MenuItem>
    </MultiLevelMenu>
);

const MyLayout = props => {
    return (
        <AppLocationContext>
            <Layout {...props} menu={MyMenu} />
        </AppLocationContext>
    );
};

export const Basic = () => (
    <Admin dashboard={Dashboard} dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="songs" list={SongList} />
        <Resource name="artists" list={ArtistList} />
    </Admin>
);

Using a Sliding Menu Panel

Sometimes, even menu with sub menus are not enough to organize the navigation. <MenuItemCategory> components can be used as children of a <MultiLevelMenu> to display a vertical bar with smaller items. Clicking on any of those items will slide in a panel with the <MenuItemCategory> children which can be any component.

You must set the variant prop to categories on the <MultiLevelMenu> component to ensure it get properly styled.

You must add only others <MenuItemCategory> components as children of the <MultiLevelMenu> if you already added one.

In case you use <MenuItem /> nested in a <MenuItemCategory />, labels may disappear when the sidebar is in reduced mode because of the internal workings of react-admin. That's why we recommend to implement your own <AppBar /> and hide the Hamburger Button since the "categories" variant of the <MultiLevelMenu /> is thin enough not to interfere with the navigation.

Besides, in order to adjust the size of the React-Admin <Sidebar> component according to the categories, you should either apply the theme provided by the @react-admin/ra-navigation package, or merge it in your own custom theme.

import { Admin, Layout, Resource } from 'react-admin';
import {
    AppLocationContext,
    Menu,
    MenuItem,
    MenuItemCategory,
    MultiLevelMenu,
    theme,
} from '@react-admin/ra-navigation';
import { makeStyles } from '@material-ui/core/styles';

import { Dashboard } from './Dashboard';
import { dataProvider } from './dataProvider';
import { SongList } from './songs';
import { ArtistList } from './artists';

const useStyles = makeStyles({
    // Custom styles for the configuration item so that it appears at the very bottom of the sidebar
    configuration: {
        marginTop: 'auto',
    },
});

const MyMenu = () => {
    const classes = useStyles();

    return (
        <MultiLevelMenu variant="categories">
            <MenuItemCategory
                name="dashboard"
                to="/"
                exact
                label="Dashboard"
                icon={<DashboardIcon />}
            />
            <MenuItemCategory
                name="songs"
                icon={<MusicIcon />}
                to="/songs"
                label="Songs"
            />
            {/* The empty filter is required to avoid falling back to the previously set filter */}
            <MenuItemCategory
                name="artists"
                to={'/artists?filter={}'}
                label="Artists"
                icon={<PeopleIcon />}
            >
                {/* CardContent to get consistent spacings */}
                <CardContent>
                    <Typography variant="h3" gutterBottom>
                        Custom title
                    </Typography>
                    {/* Note that we must wrap our MenuItem components in a Menu */}
                    <Menu>
                        <MenuItem
                            name="artists.rock"
                            to={'/artists?filter={"type":"Rock"}'}
                            label="Rock"
                        >
                            <MenuItem
                                name="artists.rock.pop"
                                to={'/artists?filter={"type":"Pop Rock"}'}
                                label="Pop Rock"
                            />
                            <MenuItem
                                name="artists.rock.folk"
                                to={'/artists?filter={"type":"Folk Rock"}'}
                                label="Folk Rock"
                            />
                        </MenuItem>
                        <MenuItem
                            name="artists.jazz"
                            to={'/artists?filter={"type":"Jazz"}'}
                            label="Jazz"
                        >
                            <MenuItem
                                name="artists.jazz.rb"
                                to={'/artists?filter={"type":"RB"}'}
                                label="R&B"
                            />
                        </MenuItem>
                    </Menu>
                </CardContent>
            </MenuItemCategory>
            <MenuItemCategory
                className={classes.configuration}
                name="configuration"
                to="/"
                exact
                label="Configuration"
                icon={<SettingsIcon />}
            />
        </MultiLevelMenu>
    );
};

const MyLayout = props => {
    return (
        <AppLocationContext>
            <Layout {...props} menu={MyMenu} />
        </AppLocationContext>
    );
};

export const MyApp = () => (
    <Admin
        dataProvider={dataProvider}
        layout={MyLayout}
        dashboard={Dashboard}
        /* Apply the theme provided by ra-navigation */
        theme={theme}
    >
        <Resource name="songs" list={SongList} />
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
import { Admin, Layout, Resource } from 'react-admin';
import {
    AppLocationContext,
    Menu,
    MenuItem,
    MenuItemCategory,
    MultiLevelMenu,
    theme,
} from '@react-admin/ra-navigation';
import { makeStyles } from '@material-ui/core/styles';

import { Dashboard } from './Dashboard';
import { dataProvider } from './dataProvider';
import { SongList } from './songs';
import { ArtistList } from './artists';

const useStyles = makeStyles({
    // Custom styles for the configuration item so that it appears at the very bottom of the sidebar
    configuration: {
        marginTop: 'auto',
    },
});

const MyMenu = () => {
    const classes = useStyles();

    return (
        <MultiLevelMenu variant="categories">
            <MenuItemCategory
                name="dashboard"
                to="/"
                exact
                label="Dashboard"
                icon={<DashboardIcon />}
            />
            <MenuItemCategory
                name="songs"
                icon={<MusicIcon />}
                to="/songs"
                label="Songs"
            />
            {/* The empty filter is required to avoid falling back to the previously set filter */}
            <MenuItemCategory
                name="artists"
                to={'/artists?filter={}'}
                label="Artists"
                icon={<PeopleIcon />}
            >
                {/* CardContent to get consistent spacings */}
                <CardContent>
                    <Typography variant="h3" gutterBottom>
                        Custom title
                    </Typography>
                    {/* Note that we must wrap our MenuItem components in a Menu */}
                    <Menu>
                        <MenuItem
                            name="artists.rock"
                            to={'/artists?filter={"type":"Rock"}'}
                            label="Rock"
                        >
                            <MenuItem
                                name="artists.rock.pop"
                                to={'/artists?filter={"type":"Pop Rock"}'}
                                label="Pop Rock"
                            />
                            <MenuItem
                                name="artists.rock.folk"
                                to={'/artists?filter={"type":"Folk Rock"}'}
                                label="Folk Rock"
                            />
                        </MenuItem>
                        <MenuItem
                            name="artists.jazz"
                            to={'/artists?filter={"type":"Jazz"}'}
                            label="Jazz"
                        >
                            <MenuItem
                                name="artists.jazz.rb"
                                to={'/artists?filter={"type":"RB"}'}
                                label="R&B"
                            />
                        </MenuItem>
                    </Menu>
                </CardContent>
            </MenuItemCategory>
            <MenuItemCategory
                className={classes.configuration}
                name="configuration"
                to="/"
                exact
                label="Configuration"
                icon={<SettingsIcon />}
            />
        </MultiLevelMenu>
    );
};

const MyLayout = props => {
    return (
        <AppLocationContext>
            <Layout {...props} menu={MyMenu} />
        </AppLocationContext>
    );
};

export const MyApp = () => (
    <Admin
        dataProvider={dataProvider}
        layout={MyLayout}
        dashboard={Dashboard}
        /* Apply the theme provided by ra-navigation */
        theme={theme}
    >
        <Resource name="songs" list={SongList} />
        <Resource name="artists" list={ArtistList} />
    </Admin>
);

<AppLocationContext>

To define or retrieve the current App Location, your React components must be located inside a <AppLocationContext>, which creates a React context dedicated to the user App Location. This component must be contained by our admin to be able to access the current registred resources from the redux store.

So, the easiest way to include it is to use a custom Layout as a wrapper:

import { AppLocationContext } from '@react-admin/ra-navigation';
import { Admin, Resource, Layout } from 'react-admin';

const MyLayout = props => {
    const classes = useStyles();

    return (
        <AppLocationContext>
            <Layout {...props} />
        </AppLocationContext>
    );
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
    </Admin>
);
import { AppLocationContext } from '@react-admin/ra-navigation';
import { Admin, Resource, Layout } from 'react-admin';

const MyLayout = props => {
    const classes = useStyles();

    return (
        <AppLocationContext>
            <Layout {...props} />
        </AppLocationContext>
    );
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="posts" list={PostList} />
    </Admin>
);

useAppLocationState

The useAppLocationState let you access the current App Location and also define a new App Location just like the React.useState API.

Here is how to read the current app location:

import { useAppLocationState } from '@react-admin/ra-navigation';

const MySubComponent = props => {
    const [location] = useAppLocationState();

    return <span>{`Hey! You're on the ${location.path} location!`}</span>;
};
import { useAppLocationState } from '@react-admin/ra-navigation';

const MySubComponent = props => {
    const [location] = useAppLocationState();

    return <span>{`Hey! You're on the ${location.path} location!`}</span>;
};

To define the current app location:

const DensityTab = () => {
    const [_, setLocation] = useAppLocationState();

    useEffect(() => {
        setLocation('experiences.parameters.density');
    }, []);

    // return (/* ... */);
};
const DensityTab = () => {
    const [_, setLocation] = useAppLocationState();

    useEffect(() => {
        setLocation('experiences.parameters.density');
    }, []);

    // return (/* ... */);
};

You can also pass a second argument to setLocation with any value which is relative to the App Location context (e.g. to display the label of the current resource in a breadcrumb path for example).

const DensityTab = () => {
    const [_, setLocation] = useAppLocationState();

    useEffect(() => {
        setLocation('experiences.parameters.density', { density: 0.1 });
    }, []);

    // return (/* ... */);
};
const DensityTab = () => {
    const [_, setLocation] = useAppLocationState();

    useEffect(() => {
        setLocation('experiences.parameters.density', { density: 0.1 });
    }, []);

    // return (/* ... */);
};

So, you can retrieve it in another place...

const Breadcrumb = () => {
    const [location] = useAppLocationState();
    // location = { path: 'experiences.parameters.density', density: 0.1 }

    // return (/* ... */);
};
const Breadcrumb = () => {
    const [location] = useAppLocationState();
    // location = { path: 'experiences.parameters.density', density: 0.1 }

    // return (/* ... */);
};

Warning:

The dashboard location is a reserved word used for the Dashboard page when it exists.

By default, useAppLocationState() will resolve the current App Location from the current React-Admin path.

Let's say you're on the posts list page:

import { useAppLocationState } from '@react-admin/ra-navigation';
import { Admin, Resource } from 'react-admin';

const PostList = () => {
    const [location] = useAppLocationState();

    // location is automatically resolved to "posts.list"
    // location = { path: "posts.list", values: {} }

    // return (/* ... */);
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
        <Resource name="posts" list={PostList} />
    </Admin>
);
import { useAppLocationState } from '@react-admin/ra-navigation';
import { Admin, Resource } from 'react-admin';

const PostList = () => {
    const [location] = useAppLocationState();

    // location is automatically resolved to "posts.list"
    // location = { path: "posts.list", values: {} }

    // return (/* ... */);
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
        <Resource name="posts" list={PostList} />
    </Admin>
);

If a deepest component in the tree defines a different location than the resource one, it will be overriden.

You can also retrieve the current resource from its location for edit and show actions.

import {
    AppLocationContext,
    useAppLocationState,
} from '@react-admin/ra-navigation';
import { Admin, Resource } from 'react-admin';

const AnotherComponent = () => {
    const [location] = useAppLocationState();

    // On Edit view
    // location = { path: "post.edit", values: { record: { id: 1, ...} } };

    // On Show view
    // location = { path: "post.show", values: { record: { id: 1, ...} } };
};

// const PostShow = () => (/* ... */);
// const PostEdit= () => (/* ... */);

const App = () => (
    <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
        <Resource name="posts" edit={PostEdit} show={PostShow} />
    </Admin>
);
import {
    AppLocationContext,
    useAppLocationState,
} from '@react-admin/ra-navigation';
import { Admin, Resource } from 'react-admin';

const AnotherComponent = () => {
    const [location] = useAppLocationState();

    // On Edit view
    // location = { path: "post.edit", values: { record: { id: 1, ...} } };

    // On Show view
    // location = { path: "post.show", values: { record: { id: 1, ...} } };
};

// const PostShow = () => (/* ... */);
// const PostEdit= () => (/* ... */);

const App = () => (
    <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
        <Resource name="posts" edit={PostEdit} show={PostShow} />
    </Admin>
);

The record attribute is always passed for show and edit actions. This way, you can display the title of your post outside the show or edit component itself.

useDefineAppLocation

Using useAppLocationState only to define the current location can be tedious. So, to avoid using a specific useEffect, you can rely on the useDefineAppLocation() which does exactly the same thing in less code.

import { useDefineAppLocation } from '@react-admin/ra-navigation';

const DensityTab = () => {
    useDefineAppLocation('experiences.parameters.density', { density: 0.1 });
    // return (/* ... */);
};
import { useDefineAppLocation } from '@react-admin/ra-navigation';

const DensityTab = () => {
    useDefineAppLocation('experiences.parameters.density', { density: 0.1 });
    // return (/* ... */);
};

useAppLocationMatcher

The useAppLocationMatcher hook returns a function that can be used to check if the path that is passed as argument matches the current location path.

If the path matches, the match function returns the current location. If not, it returns null.

import { useAppLocationMatcher } from '@react-admin/ra-navigation';

function matchFooBar() {
    const match = useAppLocationMatcher();
    return match('foo.bar');
}

// matchFooBar() on "foo.bar.baz" location
// will return { path: 'foo.bar', values: {} }

// matchFooBar() on "cov.fefe" location
// will return null
import { useAppLocationMatcher } from '@react-admin/ra-navigation';

function matchFooBar() {
    const match = useAppLocationMatcher();
    return match('foo.bar');
}

// matchFooBar() on "foo.bar.baz" location
// will return { path: 'foo.bar', values: {} }

// matchFooBar() on "cov.fefe" location
// will return null

useResourceAppLocation

While the useAppLocationState let you access the current App Location, useResourceAppLocation give you the app location from the react-admin "resource" perspective only.

That mean it returns an AppLocation only if the current routes matches a React-Admin one (eg: "/songs/1" for song edition, "/songs" for songs listing, etc). In other case, it'll return null.

This hook can be useful to override some "native" routes.

Example:

import React, { useEffect } from 'react';

import {
    AppLocationContext,
    useAppLocationState,
    useResourceAppLocation,
} from '@react-admin/ra-navigation';

const SongsGrid = props => {
    const [, setLocation] = useAppLocationState();
    const resourceLocation = useResourceAppLocation();

    useEffect(() => {
        const { artist_id: artistId } = props.filterValues;
        if (typeof artistId !== 'undefined') {
            // It'll change location and display "Filtered by artist X" in the breadcrumb
            setLocation('songs_by_artist.filter', { artistId });
        }
    }, [JSON.stringify({ resourceLocation, filters: props.filterValues })]);

    return (
        <Datagrid {...props}>
            <TextField source="title" />
            <ReferenceField source="artist_id" reference="artists">
                <TextField source="name" />
            </ReferenceField>
        </Datagrid>
    );
};

const SongFilter = props => (
    <Filter {...props}>
        <ReferenceInput alwaysOn source="artist_id" reference="artists">
            <SelectInput optionText="name" />
        </ReferenceInput>
    </Filter>
);

const SongList = props => (
    <>
        <MyBreadcrumb />
        <List {...props} filters={<SongFilter />}>
            <SongsGrid />
        </List>
    </>
);

const MyBreadcrumb = () => (
    <Breadcrumb>
        <ResourceBreadcrumbItems />
        <BreadcrumbItem
            name="songs_by_artist.filter"
            label={({ artistId }) => `Filtered by artist #${artistId}`}
        />
    </Breadcrumb>
);

const MyLayout = props => (
    <AppLocationContext>
        <Layout {...props} />
    </AppLocationContext>
);

export default () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="songs" list={SongList} />
        <Resource name="artists" />
    </Admin>
);
import React, { useEffect } from 'react';

import {
    AppLocationContext,
    useAppLocationState,
    useResourceAppLocation,
} from '@react-admin/ra-navigation';

const SongsGrid = props => {
    const [, setLocation] = useAppLocationState();
    const resourceLocation = useResourceAppLocation();

    useEffect(() => {
        const { artist_id: artistId } = props.filterValues;
        if (typeof artistId !== 'undefined') {
            // It'll change location and display "Filtered by artist X" in the breadcrumb
            setLocation('songs_by_artist.filter', { artistId });
        }
    }, [JSON.stringify({ resourceLocation, filters: props.filterValues })]);

    return (
        <Datagrid {...props}>
            <TextField source="title" />
            <ReferenceField source="artist_id" reference="artists">
                <TextField source="name" />
            </ReferenceField>
        </Datagrid>
    );
};

const SongFilter = props => (
    <Filter {...props}>
        <ReferenceInput alwaysOn source="artist_id" reference="artists">
            <SelectInput optionText="name" />
        </ReferenceInput>
    </Filter>
);

const SongList = props => (
    <>
        <MyBreadcrumb />
        <List {...props} filters={<SongFilter />}>
            <SongsGrid />
        </List>
    </>
);

const MyBreadcrumb = () => (
    <Breadcrumb>
        <ResourceBreadcrumbItems />
        <BreadcrumbItem
            name="songs_by_artist.filter"
            label={({ artistId }) => `Filtered by artist #${artistId}`}
        />
    </Breadcrumb>
);

const MyLayout = props => (
    <AppLocationContext>
        <Layout {...props} />
    </AppLocationContext>
);

export default () => (
    <Admin dataProvider={dataProvider} layout={MyLayout}>
        <Resource name="songs" list={SongList} />
        <Resource name="artists" />
    </Admin>
);

A component displaying a breadcumb based on the current AppLocation.

A breadcrumb

Accepts the following props:

  • className: Optional. The class name to apply to its root element
  • separator: Optional. A custom sepator to put inside the items. Either a string or a function returning a string. dashboard: Optional. The component you might have specified on the <Admin> component. Used to determine whether a dashboard is present. If it is, the dashboard item will be added in the breadcrumb on every pages. hasDashboard: Optional. A boolean indicating whether a dashboard is present. If it is, the dashboard item will be added in the breadcrumb on every pages. variant: Optional. Either 'default' or 'actions'. Use the actions variant when you add the <Breadcrumb> inside an action component such as the <ListActions>.
import { Breadcrumb, ResourceBreadcrumbItems, BreadcrumbItem } from '@react-admin/ra-navigation';

// default breadcrumb based on resources, using the dashboard as root
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
    <Breadcrumb dashboard={dashboard}>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);

// custom breadcrumb
const MyBreadcrumb = ({ children, ...props }) => (
    <Breadcrumb>
        <ResourceBreadcrumbItems resources={['otherResources']} />
        <BreadcrumbItem name="posts" label="Posts">
            <BreadcrumbItem
                name="edit"
                label={({ record }) => `Edit "${record.title}"`}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/edit`
                }
            />
            <BreadcrumbItem
                name="show"
                label={({ record }) => record.title}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/show`
                }
            />
            <BreadcrumbItem name="list" label="My Post List" />
            <BreadcrumbItem name="create" label="Let's write a Post!" />
        </BreadcrumbItem>
    </Breadcrumb>
);
import { Breadcrumb, ResourceBreadcrumbItems, BreadcrumbItem } from '@react-admin/ra-navigation';

// default breadcrumb based on resources, using the dashboard as root
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
    <Breadcrumb dashboard={dashboard}>
        <ResourceBreadcrumbItems />
    </Breadcrumb>
);

// custom breadcrumb
const MyBreadcrumb = ({ children, ...props }) => (
    <Breadcrumb>
        <ResourceBreadcrumbItems resources={['otherResources']} />
        <BreadcrumbItem name="posts" label="Posts">
            <BreadcrumbItem
                name="edit"
                label={({ record }) => `Edit "${record.title}"`}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/edit`
                }
            />
            <BreadcrumbItem
                name="show"
                label={({ record }) => record.title}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/show`
                }
            />
            <BreadcrumbItem name="list" label="My Post List" />
            <BreadcrumbItem name="create" label="Let's write a Post!" />
        </BreadcrumbItem>
    </Breadcrumb>
);

A component displaying a single breadcumb item.

A breadcrumb item

Accepts the following props:

  • name: Required. The name of this item which will be used to infer its full path.
  • label: Required. The label to display for this item. It accepts translation keys.
  • path: Internal prop used to build the item full path.
  • to: Optional. The react-router path to redirect to.
  • hasDashboard: Optional. A boolean indicating whether a dashboard is present. If it is, the dashboard item will be added in the breadcrumb on every pages. You shouldn't have to pass this prop unless you're wrapping the <BreadcrumbItem>.
import { Breadcrumb, ResourceBreadcrumbItems, BreadcrumbItem } from '@react-admin/ra-navigation';

// custom breadcrumb
const MyBreadcrumb = ({ children, ...props }) => (
    <Breadcrumb>
        <ResourceBreadcrumbItems resources={['otherResources']} />
        <BreadcrumbItem name="posts" label="Posts">
            <BreadcrumbItem
                name="edit"
                label={({ record }) => `Edit "${record.title}"`}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/edit`
                }
            />
            <BreadcrumbItem
                name="show"
                label={({ record }) => record.title}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/show`
                }
            />
            <BreadcrumbItem name="list" label="My Post List" />
            <BreadcrumbItem name="create" label="Let's write a Post!" />
        </BreadcrumbItem>
    </Breadcrumb>
);
import { Breadcrumb, ResourceBreadcrumbItems, BreadcrumbItem } from '@react-admin/ra-navigation';

// custom breadcrumb
const MyBreadcrumb = ({ children, ...props }) => (
    <Breadcrumb>
        <ResourceBreadcrumbItems resources={['otherResources']} />
        <BreadcrumbItem name="posts" label="Posts">
            <BreadcrumbItem
                name="edit"
                label={({ record }) => `Edit "${record.title}"`}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/edit`
                }
            />
            <BreadcrumbItem
                name="show"
                label={({ record }) => record.title}
                to={({ record }) =>
                    record && `${linkToRecord('/songs', record.id)}/show`
                }
            />
            <BreadcrumbItem name="list" label="My Post List" />
            <BreadcrumbItem name="create" label="Let's write a Post!" />
        </BreadcrumbItem>
    </Breadcrumb>
);

<DashboardBreadcrumbItem>

A version of the <BreadcrumbItem> dedicated to the dashboard.

Accepts the following props:

  • name: Optional. The name of this item which will be used to infer its full path.
  • label: Optional. The label to display for this item. It accepts translation keys.
  • path: Internal prop used to build the item full path.
  • to: Optional. The react-router path to redirect to.
  • hasDashboard: Optional. A boolean indicating whether a dashboard is present. If it is, the dashboard item will be added in the breadcrumb on every pages. You shouldn't have to pass this prop unless you're wrapping the <BreadcrumbItem>.

<ResourceBreadcrumbItems>

A component which will infer multiple breadcumb item for every registered resource.

Accepts the following props:

  • resources: Optional. The resources for which to infer the breadcrumb items. You don't need to specify this props if you want all resources to be handled.
  • hasDashboard: Optional. A boolean indicating whether a dashboard is present. If it is, the dashboard item will be added in the breadcrumb on every pages. You shouldn't have to pass this prop unless you're wrapping the <BreadcrumbItem>.

<MultiLevelMenu>

A menu to use instead of the default react-admin menu, which supports multi level items. It also offers a variant for a reduced menu bar with a sliding panel for second-level menu items:

The <MultiLevelMenu> accepts the following props:

  • initialOpen: Whether the menu items with sub menus should be open initialy. Has no effect if using the categories variant. Defaults to false.
  • variant: Either default or categories. Applies proper styles on the Menu when using MenuItemCategory items.
import { MultiLevelMenu, MenuItem } from '@react-admin/ra-navigation';

import { Dashboard } from './Dashboard';
import { dataProvider } from './dataProvider';
import { SongList } from './songs';
import { ArtistList } from './artists';

const MyMenu = () => (
    <MultiLevelMenu>
        <MenuItem name="dashboard" to="/" exact label="Dashboard" />
        <MenuItem name="songs" to="/songs" label="Songs" />
        {/* The empty filter is required to avoid falling back to the previously set filter */}
        <MenuItem name="artists" to={'/artists?filter={}'} label="Artists">
            <MenuItem
                name="artists.rock"
                to={'/artists?filter={"type":"Rock"}'}
                label="Rock"
            >
                <MenuItem
                    name="artists.rock.pop"
                    to={'/artists?filter={"type":"Pop Rock"}'}
                    label="Pop Rock"
                />
                <MenuItem
                    name="artists.rock.folk"
                    to={'/artists?filter={"type":"Folk Rock"}'}
                    label="Folk Rock"
                />
            </MenuItem>
            <MenuItem
                name="artists.jazz"
                to={'/artists?filter={"type":"Jazz"}'}
                label="Jazz"
            >
                <MenuItem
                    name="artists.jazz.rb"
                    to={'/artists?filter={"type":"RB"}'}
                    label="R&B"
                />
            </MenuItem>
        </MenuItem>
    </MultiLevelMenu>
);
import { MultiLevelMenu, MenuItem } from '@react-admin/ra-navigation';

import { Dashboard } from './Dashboard';
import { dataProvider } from './dataProvider';
import { SongList } from './songs';
import { ArtistList } from './artists';

const MyMenu = () => (
    <MultiLevelMenu>
        <MenuItem name="dashboard" to="/" exact label="Dashboard" />
        <MenuItem name="songs" to="/songs" label="Songs" />
        {/* The empty filter is required to avoid falling back to the previously set filter */}
        <MenuItem name="artists" to={'/artists?filter={}'} label="Artists">
            <MenuItem
                name="artists.rock"
                to={'/artists?filter={"type":"Rock"}'}
                label="Rock"
            >
                <MenuItem
                    name="artists.rock.pop"
                    to={'/artists?filter={"type":"Pop Rock"}'}
                    label="Pop Rock"
                />
                <MenuItem
                    name="artists.rock.folk"
                    to={'/artists?filter={"type":"Folk Rock"}'}
                    label="Folk Rock"
                />
            </MenuItem>
            <MenuItem
                name="artists.jazz"
                to={'/artists?filter={"type":"Jazz"}'}
                label="Jazz"
            >
                <MenuItem
                    name="artists.jazz.rb"
                    to={'/artists?filter={"type":"RB"}'}
                    label="R&B"
                />
            </MenuItem>
        </MenuItem>
    </MultiLevelMenu>
);

A version of react-admin <MenuItemLink> accepting other <MenuItem> as children. Users can show/hide child <MenuItem> elements like a classic collapsible menu.

MultiLevelMenu

In addition to the props of react-router <NavLink> and those of material-ui <ListItem>, it accepts the following props:

  • icon: Optional. An icon element to display in front of the item
  • name: Required: The name of the item. Used to manage its open/closed state.
  • label: Optional. The label to display for this item. Accepts translation keys.

A version of <MenuItem> displaying main menu categories as a small box. When clicked, the menu categories displays the child menu inside a drawer panel to save a lot of screen real estate.

In addition to the props of react-router <NavLink> and those of material-ui <ListItem>, it accepts the following props:

  • icon: Optional. An icon element to display in front of the item
  • name: Required: The name of the item. Used to manage its open/closed state.
  • label: Optional. The label to display for this item. Accepts translation keys.

A wrapper to display <MenuItems> as a list with proper sytles.

Accepts the same props as the material-ui <List> component. See https://material-ui.com/api/list/.

CHANGELOG

v2.3.5

2021-09-03

  • (fix) Fix useAppLocationMatcher should not confuse resources with names starting with the same characters (product and productCatalog for instance)

v2.3.4

2021-07-16

  • (fix) Fix "cannot read prop style of undefined" error in <MenuItem>

v2.3.3

2021-07-07

  • (fix) Fix resource path resolution to support TabbedForm and TabbedShowLayout tabs with path
  • (fix) Fix resource path resolution to support multiple resources which have names starting with the same characters

v2.3.2

2021-06-29

  • (fix) Update peer dependencies ranges (support react 17)

v2.3.1

2021-06-21

  • (doc) Update the documentation

v2.3.0

2021-06-16

  • (feat) Add translation key support on <BreadcrumbItem>

v2.2.4

2021-06-15

  • (fix) Fix custom routes for a resource might be inferred as the edit view for that resource

v2.2.3

2021-05-06

  • (fix) Fix Breadcrumb resource items for details views are not translated

v2.2.2

2021-05-03

  • (fix) Fix Breadcrumb Dark Mode Support

v2.2.1

2021-04-27

  • (fix) Fix split on undefined in getDeepestLocation

v2.2.0

2021-04-22

  • (feat) Add the initialOpen prop on the <MultiLevelMenu>. Defines whether the menu items with sub menus should be open initialy.

v2.1.0

2021-04-08

  • (feat) Add the hasDashboard prop on the <AppLocationContext> This allows to avoid specifying this prop on the <Breacrumb> itself. It's used in ra-enterprise to setup the breadcrumb automatically regarding the dashboard.

  • (feat) Introduce the useHasDashboard hook to check if a dashboard has been defined.

  • (fix) Ensure the AppLocation and breadcrumb behave correctly when views are included in other views (Create/Edit/Show in aside for example).

v2.0.0

2021-04-01

Breaking change

  • (feat) Introduce variant prop on <Breadcrumb>.
import * as React from "react";
import { TopToolbar, ShowButton } from 'react-admin';
-import { BreadcrumbForActions } from '@react-admin/ra-navigation';
+import { Breadcrumb } from '@react-admin/ra-navigation';

const PostEditActions = ({ basePath, data, resource }) => (
    <TopToolbar>
-        <BreadcrumbForActions />
+        <Breadcrumb variant="actions" />
        <ShowButton basePath={basePath} record={data} />
    </TopToolbar>
);

export const PostEdit = (props) => (
    <Edit actions={<PostEditActions />} {...props}>
        ...
    </Edit>
);

v1.3.3

2021-03-23

  • (fix) Allow to Override BreadcrumbForActions className

v1.3.2

2021-03-22

  • (fix) Fix BreacrumbForActions props interface

v1.3.1

2021-03-19

  • (fix) Fix Breacrumb Styles
  • (fix) Move Breadcrumb out of Layout

v1.3.0

2021-03-18

  • (feat) Added <BreadcrumbForActions>, a Breadcrumb variation with custom styles to make it fit inside an actions toolbar.
import * as React from 'react';
import { TopToolbar, ShowButton } from 'react-admin';
import { BreadcrumbForActions } from '@react-admin/ra-navigation';

const PostEditActions = ({ basePath, data, resource }) => (
    <TopToolbar>
        <BreadcrumbForActions />
        <ShowButton basePath={basePath} record={data} />
    </TopToolbar>
);

export const PostEdit = props => (
    <Edit actions={<PostEditActions />} {...props}>
        ...
    </Edit>
);
import * as React from 'react';
import { TopToolbar, ShowButton } from 'react-admin';
import { BreadcrumbForActions } from '@react-admin/ra-navigation';

const PostEditActions = ({ basePath, data, resource }) => (
    <TopToolbar>
        <BreadcrumbForActions />
        <ShowButton basePath={basePath} record={data} />
    </TopToolbar>
);

export const PostEdit = props => (
    <Edit actions={<PostEditActions />} {...props}>
        ...
    </Edit>
);

v1.2.5

2021-03-17

  • (fix) Fix MenuItemCategory popover is always at the page top

v1.2.4

2020-11-27

  • (fix) Fix MenuItem inside <MenuItemCategory> do not display their label when sidebar is collapsed
  • (fix) Fix custom menu cannot be collapsed in ra-enterprise by upgrading react-admin

v1.2.3

2020-11-03

  • (fix) Fix <MenuItemCategory> blocks scroll

v1.2.2

2020-10-23

  • (fix) Fix <MenuItemCategory> sometimes hidden by the <AppBar>

v1.2.1

2020-10-15

  • (feat) Show by default which <MenuItem> is hovered by using a grey background
  • (fix) Clicking on <MenuItem> borders wasn't possible

v1.2.0

2020-10-05

  • Upgrade to react-admin 3.9

v1.1.5

2020-10-01

  • (fix) Fix menu overlapping when passing from a <MenuItemCtagory /> to another one

v1.1.4

2020-09-30

  • Update Readme

v1.1.3

2020-09-29

  • (fix) Export breadcrumb types

v1.1.2

2020-09-25

  • (fix) Render the <BreadcrumbItem> using material-ui <Typography> and <Link>

v1.1.1

2020-09-17

  • (fix) Fix <MenuItemCategory> props types

v1.1.0

2020-09-17

  • (feat) Replace home by dashboard
  • (fix) Ensure the label of the dashboard <BreadcrumbItem> is translatable and uses react-admin defaults

v1.0.5

2020-09-16

  • (feat) Add a hover effect for the <MenuItemCategory>
  • (fix) Fix the dark mode for the <MultiLevelMenu>
  • (deps) Upgrade dependencies

v1.0.4

2020-09-03

  • (feat) Add a home link to the <Breadcrumb>
  • (feat) Allow to design the <Breadcrumb
  • (fix) Fix the breadcrumbs when used in the home page
  • (deps) Upgrade dependencies

v1.0.3

2020-08-21

  • (fix) Fix the <MenuItemCategory> blur

v1.0.2

2020-08-21

  • (feat) Allow the <MenuItemCategory> customization

v1.0.1

2020-08-20

  • (feat) Introduce the <MultiLevelMenu>
  • (doc) Improve the documentation
  • (deps) Upgrade dependencies

v1.0.0

2020-07-31

  • First release