ra-enterprise

Preconfigured components replacing the default react-admin ones to quickly integrate the Enterprise Edition modules.

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

ra-enterprise exports the following "augmented" components: <Admin> and <Layout>. Use them instead of the react-admin versions to get additional features:

- import { Admin } from 'react-admin';
+ import { Admin } from '@react-admin/ra-enterprise';

const App = ()  => (
    <Admin dataProvider={dataProvider}>
        { /** Put your resources here */ }
    </Admin>
);

Installation

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

Tip: ra-enterprise 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.

<Admin>

ra-enterprise exports a replacement for the <Admin> component, which pre-configures many of the enterprise edition modules. It comes with:

This modifies the look and feel of react-admin:

ra-enterprise Admin

To be compared with the default look and feel:

Classic Admin

Usage

Replace react-admin's <Admin> component the same component from ra-enterprise:

- import { Admin } from 'react-admin';
+ import { Admin } from '@react-admin/ra-enterprise';

const App = ()  => (
    <Admin dataProvider={dataProvider}>
        { /** Put your resources here */ }
    </Admin>
);

Properties

The alternative <Admin> accepts the same properties as the default one. Please read the documentation about the react-admin <Admin> to see all the available properties.

In addition, you can pass the following properties to customize the ra-enterprise features:

Prop Required Type Default Description
i18nProvider Optional object - Replace the ra-enterprise default i18nProvider with your own
lightTheme Optional object - Override the light mode with your own light theme. This theme will be merged with the theme passed in theme when using light mode. See Theming for more information.
darkTheme Optional object - Override the dark mode with your own dark theme. This theme will be merged with the theme passed in theme when using dark mode. See Theming for more information.

Examples

  1. Basic usage
import { Admin } from '@react-admin/ra-enterprise';

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider}>{/** Put your resources here */}</Admin>
);
import { Admin } from "@react-admin/ra-enterprise";

const dataProvider = {
    // Connect to your API
};

const App = () => <Admin dataProvider={dataProvider}>{/** Put your resources here */}</Admin>;
  1. Override the dark theme
import { Admin } from '@react-admin/ra-enterprise';

const darkTheme = {
    palette: {
        mode: 'dark', // Don't forget to specify the palette mode
        primary: {
            main: '#90caf9',
        },
        secondary: {
            main: '#ffff00',
        },
    },
};

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} darkTheme={darkTheme}>
        {/** Put your resources here */}
    </Admin>
);
import { Admin } from "@react-admin/ra-enterprise";

const darkTheme = {
    palette: {
        mode: "dark", // Don't forget to specify the palette mode
        primary: {
            main: "#90caf9",
        },
        secondary: {
            main: "#ffff00",
        },
    },
};

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} darkTheme={darkTheme}>
        {/** Put your resources here */}
    </Admin>
);
  1. Pass your own translations
import { Admin, buildI18nProvider } from '@react-admin/ra-enterprise';

const languages = [
    { locale: 'en', name: 'English' },
    { locale: 'fr', name: 'Français' },
];

const messages = {
    en: {
        // Put your english translations here
    },
    fr: {
        // Put your french translations here
    },
};

const dataProvider = {
    // Connect to your API
};

const i18nProvider = buildI18nProvider(messages, 'en', languages);

const App = () => (
    <Admin dataProvider={dataProvider} i18nProvider={i18nProvider}>
        {/** Put your resources here */}
    </Admin>
);
import { Admin, buildI18nProvider } from "@react-admin/ra-enterprise";

const languages = [
    { locale: "en", name: "English" },
    { locale: "fr", name: "Français" },
];

const messages = {
    en: {
        // Put your english translations here
    },
    fr: {
        // Put your french translations here
    },
};

const dataProvider = {
    // Connect to your API
};

const i18nProvider = buildI18nProvider(messages, "en", languages);

const App = () => (
    <Admin dataProvider={dataProvider} i18nProvider={i18nProvider}>
        {/** Put your resources here */}
    </Admin>
);

Theming

While the theme, lightTheme, darkTheme and defaultTheme props of ra-enterprise's <Admin> and react-admin's <Admin> look alike, they are actually a bit different.

With react-admin's <Admin> With ra-enterprise's <Admin>
theme is the default (full) theme, and should contain the light theme when there is also a dark theme theme is the base theme. It should contain all parts of the theme that are common for light and dark modes.
lightTheme serves the same purpose as theme (allows for a symmetric prop naming with darkTheme) lightTheme contains the part of the theme that is specific to the light mode. It will be merged with theme.
darkTheme is the (full) dark theme darkTheme contains the part of the theme that is specific to the dark mode. It will be merged with theme.
The <ToggleThemeButton> will be displayed in the <AppBar> as soon as a darkTheme is provided The <ToggleThemeButton> will be always be displayed in the <AppBar>, since ra-enterprise's <Admin> comes with a default dark theme
theme, lightTheme and darkTheme must all contain a full theme (i.e. with the palette, but also the shape and components overrides) ra-enterprise's <Admin> takes care of merging the common part of the theme (from theme) with the mode-specific part (lightTheme and darkTheme)
defaultTheme only accepts 'light' or 'dark' as values defaultTheme is set to 'light' by default, but this default can be disabled by passing defaultTheme={false}

Here is an example of an admin with custom light and dark themes, that will select the mode depending on the user's OS settings.

With react-admin's <Admin>:

import { Admin } from 'react-admin';
import { ThemeOptions } from '@mui/material';
import merge from 'lodash/merge';
import { dataProvider } from './dataProvider';
import { ArtistList } from './artists';

const theme: Partial<ThemeOptions> = {
    shape: {
        borderRadius: 10,
    },
};

const darkTheme: Partial<ThemeOptions> = merge(theme, {
    palette: {
        mode: 'dark',
        primary: {
            main: '#90f995',
        },
    },
});

const lightTheme: Partial<ThemeOptions> = merge(theme, {
    palette: {
        mode: 'light',
        primary: {
            main: '#298d37',
        },
        secondary: {
            light: '#5f5fc4',
            main: '#389328',
            dark: '#001064',
            contrastText: '#fff',
        },
        background: {
            default: '#fcfcfe',
        },
    },
});

const App = () => (
    <Admin dataProvider={dataProvider} theme={lightTheme} darkTheme={darkTheme}>
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
import { Admin } from "react-admin";
import merge from "lodash/merge";
import { dataProvider } from "./dataProvider";
import { ArtistList } from "./artists";

const theme = {
    shape: {
        borderRadius: 10,
    },
};

const darkTheme = merge(theme, {
    palette: {
        mode: "dark",
        primary: {
            main: "#90f995",
        },
    },
});

const lightTheme = merge(theme, {
    palette: {
        mode: "light",
        primary: {
            main: "#298d37",
        },
        secondary: {
            light: "#5f5fc4",
            main: "#389328",
            dark: "#001064",
            contrastText: "#fff",
        },
        background: {
            default: "#fcfcfe",
        },
    },
});

const App = () => (
    <Admin dataProvider={dataProvider} theme={lightTheme} darkTheme={darkTheme}>
        <Resource name="artists" list={ArtistList} />
    </Admin>
);

With ra-enterprise's <Admin>:

import { Admin, ThemeOptions } from '@react-admin/ra-enterprise';
import { dataProvider } from './dataProvider';
import { ArtistList } from './artists';

const theme: ThemeOptions = {
    shape: {
        borderRadius: 10,
    },
};

const darkTheme: ThemeOptions = {
    palette: {
        mode: 'dark',
        primary: {
            main: '#90f995',
        },
    },
};

const lightTheme: ThemeOptions = {
    palette: {
        mode: 'light',
        primary: {
            main: '#298d37',
        },
        secondary: {
            light: '#5f5fc4',
            main: '#389328',
            dark: '#001064',
            contrastText: '#fff',
        },
        background: {
            default: '#fcfcfe',
        },
    },
};

const App = () => (
    <Admin
        dataProvider={dataProvider}
        theme={theme}
        lightTheme={lightTheme}
        darkTheme={darkTheme}
        defaultTheme={false}
    >
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
import { Admin } from "@react-admin/ra-enterprise";
import { dataProvider } from "./dataProvider";
import { ArtistList } from "./artists";

const theme = {
    shape: {
        borderRadius: 10,
    },
};

const darkTheme = {
    palette: {
        mode: "dark",
        primary: {
            main: "#90f995",
        },
    },
};

const lightTheme = {
    palette: {
        mode: "light",
        primary: {
            main: "#298d37",
        },
        secondary: {
            light: "#5f5fc4",
            main: "#389328",
            dark: "#001064",
            contrastText: "#fff",
        },
        background: {
            default: "#fcfcfe",
        },
    },
};

const App = () => (
    <Admin dataProvider={dataProvider} theme={theme} lightTheme={lightTheme} darkTheme={darkTheme} defaultTheme={false}>
        <Resource name="artists" list={ArtistList} />
    </Admin>
);

Tip: In addition to merging the mode-specific styles, ra-enterprise's <Admin> will also automatically apply the theme provided by ra-navigation.

Tip: theme, lightTheme, darkTheme and defaultTheme are all facultative. ra-enterprise's <Admin> comes with its own defaults for lightTheme and darkTheme.

Tip: In the second example, we have set defaultTheme={false}. This allows to automatically select the mode (light or dark) depending on the user's OS settings. See the defaultTheme documentation for more information.

<Layout>

Alternative to react-admin's default <Layout>, which adds the following features:

  • A compact menu with submenus in sliding panels (from ra-navigation)
  • A breadcrumb based on resources and a compact menu with submenus in sliding panels (from ra-navigation)
  • An <AppLocationContext>, to allow the use of ra-navigation features.
  • The Tour context, allowing to show guided tours everywhere in the admin (from ra-tour)

Usage

If you use the <Admin> component from ra-enterprise, this layout is already used by default.

If you use the <Admin> component from react-admin, pass this component as the layout prop.

import { Admin } from 'react-admin';
+import { Layout } from '@react-admin/ra-enterprise';

const App = ()  => (
    <Admin
        dataProvider={dataProvider}
+       layout={Layout}
    >
        { /** Put your resources here */ }
    </Admin>
);

Default Properties

The alternative <Layout> accepts the same properties as the default one with the addition of a breadcrumb prop allowing you to override the default Breadcrumb. Please read the documentation about react-admin <Layout> to discover all the available properties.

Customizing The Sidebar Width

The react-admin layout requires that you specify the width of the sidebar with an arbitrary default value. You might have to adjust it with a custom theme.

If you use the default menu with multilevel support:

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

export const theme: ThemeOptions = {
    ...defaultTheme,
    overrides: {
        sidebar: {
            width: 240,
            closedWidth: 55,
        },
        RaSidebar: {
            fixed: {
                zIndex: 1200,
            },
        },
    },
};
import { defaultTheme } from "react-admin";

export const theme = {
    ...defaultTheme,
    overrides: {
        sidebar: {
            width: 240,
            closedWidth: 55,
        },
        RaSidebar: {
            fixed: {
                zIndex: 1200,
            },
        },
    },
};

If you use the default category variant:

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

export const theme: ThemeOptions = {
    ...defaultTheme,
    sidebar: {
        width: 96,
        closedWidth: 48,
    },
    overrides: {
        RaSidebar: {
            fixed: {
                zIndex: 1200,
            },
        },
    },
};
import { defaultTheme } from "react-admin";

export const theme = {
    ...defaultTheme,
    sidebar: {
        width: 96,
        closedWidth: 48,
    },
    overrides: {
        RaSidebar: {
            fixed: {
                zIndex: 1200,
            },
        },
    },
};

Examples

  1. Change the appBar
import { Button } from '@mui/material';
import { useTour } from '@react-admin/ra-tour';
import { AppBar, TitlePortal } from 'react-admin';
import { Admin, Layout } from '@react-admin/ra-enterprise';

const CustomAppBarWithTour = (props: AppBarProps) => {
    const [_, { start }] = useTour();

    const handleStart = (): void => {
        start('example');
    };

    return (
        <AppBar {...props}>
            <TitlePortal />
            <Button onClick={handleStart} variant="contained">
                Start Tour
            </Button>
        </AppBar>
    );
};

const CustomLayout = props => (
    <Layout appBar={CustomAppBarWithTour} {...props} />
);

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={CustomLayout}>
        {/** Put your resources here */}
    </Admin>
);
import { Button } from "@mui/material";
import { useTour } from "@react-admin/ra-tour";
import { AppBar, TitlePortal } from "react-admin";
import { Admin, Layout } from "@react-admin/ra-enterprise";

const CustomAppBarWithTour = (props) => {
    const [_, { start }] = useTour();

    const handleStart = () => {
        start("example");
    };

    return (
        <AppBar {...props}>
            <TitlePortal />
            <Button onClick={handleStart} variant="contained">
                Start Tour
            </Button>
        </AppBar>
    );
};

const CustomLayout = (props) => <Layout appBar={CustomAppBarWithTour} {...props} />;

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={CustomLayout}>
        {/** Put your resources here */}
    </Admin>
);
  1. Change the sidebar
import { Admin, Layout, Sidebar } from '@react-admin/ra-enterprise';

const CustomSidebar = props => <Sidebar {...props} />;

const CustomLayout = props => <Layout sidebar={CustomSidebar} {...props} />;

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={CustomLayout}>
        {/** Put your resources here */}
    </Admin>
);
import { Admin, Layout, Sidebar } from "@react-admin/ra-enterprise";

const CustomSidebar = (props) => <Sidebar {...props} />;

const CustomLayout = (props) => <Layout sidebar={CustomSidebar} {...props} />;

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={CustomLayout}>
        {/** Put your resources here */}
    </Admin>
);
  1. Change the menu
import { Admin, Layout, Menu } from '@react-admin/ra-enterprise';

const CustomMenu = props => <Menu {...props} />;

const CustomLayout = props => <Layout menu={CustomMenu} {...props} />;

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={CustomLayout}>
        {/** Put your resources here */}
    </Admin>
);
import { Admin, Layout, Menu } from "@react-admin/ra-enterprise";

const CustomMenu = (props) => <Menu {...props} />;

const CustomLayout = (props) => <Layout menu={CustomMenu} {...props} />;

const dataProvider = {
    // Connect to your API
};

const App = () => (
    <Admin dataProvider={dataProvider} layout={CustomLayout}>
        {/** Put your resources here */}
    </Admin>
);

buildI18nProvider

The buildI18nProvider function can help you build an i18nProvider. Indeed, it will automatically merge the passed messages object with the default english messages from react-admin and from all the Enterprise packages.

It accepts the following arguments:

  1. The messages object containing your translations
  2. The default locale (e.g. 'en')
  3. The list of available locales. It defaults to [{ locale: 'en', name: 'English' }]
  4. An object with additional options to pass to polyglot. Check the Polyglot documentation for a list of options you can pass to Polyglot at startup. Defaults to {}.

Examples

  1. Use buildI18nProvider with no arguments to get an i18nProvider with the default english messages for both react-admin and the Enterprise packages:
import { Resource } from 'react-admin';
import { Admin, buildI18nProvider } from '@react-admin/ra-enterprise';
import { dataProvider } from './dataProvider';
import { ArtistList } from './artists';

export const DefaultTranslations = () => (
    <Admin dataProvider={dataProvider} i18nProvider={buildI18nProvider()}>
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
import { Resource } from "react-admin";
import { Admin, buildI18nProvider } from "@react-admin/ra-enterprise";
import { dataProvider } from "./dataProvider";
import { ArtistList } from "./artists";

export const DefaultTranslations = () => (
    <Admin dataProvider={dataProvider} i18nProvider={buildI18nProvider()}>
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
  1. Use buildI18nProvider with custom messages to merge your translations with the default ones:
import { Resource } from 'react-admin';
import { Admin, buildI18nProvider } from '@react-admin/ra-enterprise';
import { dataProvider } from './dataProvider';
import { ArtistList } from './artists';

const customMessages = {
    en: {
        resources: {
            artists: {
                name: 'Painter |||| Painters',
            },
        },
    },
};

export const DefaultTranslations = () => (
    <Admin
        dataProvider={dataProvider}
        i18nProvider={buildI18nProvider(customMessages)}
    >
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
import { Resource } from "react-admin";
import { Admin, buildI18nProvider } from "@react-admin/ra-enterprise";
import { dataProvider } from "./dataProvider";
import { ArtistList } from "./artists";

const customMessages = {
    en: {
        resources: {
            artists: {
                name: "Painter |||| Painters",
            },
        },
    },
};

export const DefaultTranslations = () => (
    <Admin dataProvider={dataProvider} i18nProvider={buildI18nProvider(customMessages)}>
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
  1. Use buildI18nProvider to build an i18nProvider supporting both english and french, which will also make the <LocalesMenuButton> appear in the <AppBar>:
import { Resource, mergeTranslations } from 'react-admin';
import frenchTranslations from 'ra-language-french';
import { Admin, buildI18nProvider } from '@react-admin/ra-enterprise';
import { dataProvider } from './dataProvider';
import { ArtistList } from './artists';

const languages = [
    { locale: 'en', name: 'English' },
    { locale: 'fr', name: 'Français' },
];

const customMessages = {
    en: {
        resources: {
            artists: {
                name: 'Painter |||| Painters',
            },
        },
    },
    fr: mergeTranslations(frenchTranslations, {
        resources: {
            artists: {
                name: 'Peintre |||| Peintres',
            },
        },
    }),
};

export const DefaultTranslations = () => (
    <Admin
        dataProvider={dataProvider}
        i18nProvider={buildI18nProvider(customMessages, 'fr', languages)}
    >
        <Resource name="artists" list={ArtistList} />
    </Admin>
);
import { Resource, mergeTranslations } from "react-admin";
import frenchTranslations from "ra-language-french";
import { Admin, buildI18nProvider } from "@react-admin/ra-enterprise";
import { dataProvider } from "./dataProvider";
import { ArtistList } from "./artists";

const languages = [
    { locale: "en", name: "English" },
    { locale: "fr", name: "Français" },
];

const customMessages = {
    en: {
        resources: {
            artists: {
                name: "Painter |||| Painters",
            },
        },
    },
    fr: mergeTranslations(frenchTranslations, {
        resources: {
            artists: {
                name: "Peintre |||| Peintres",
            },
        },
    }),
};

export const DefaultTranslations = () => (
    <Admin dataProvider={dataProvider} i18nProvider={buildI18nProvider(customMessages, "fr", languages)}>
        <Resource name="artists" list={ArtistList} />
    </Admin>
);

<AccordionForm>

Alternative to react-admin's <AccordionForm> that adds RBAC control to the delete button.

To learn more about the permissions format, please refer to the @react-admin/ra-rbac documentation.

import { AccordionForm } from '@react-admin/ra-enterprise';

const authProvider = {
    checkAuth: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    getPermissions: () =>
        Promise.resolve([
            // 'delete' is missing
            { action: ['list', 'edit'], resource: 'products' },
            { action: 'write', resource: 'products.reference' },
            { action: 'write', resource: 'products.width' },
            { action: 'write', resource: 'products.height' },
            // 'products.description' is missing
            { action: 'write', resource: 'products.thumbnail' },
            // 'products.image' is missing
            { action: 'write', resource: 'products.panel.description' },
            { action: 'write', resource: 'products.panel.images' },
            // 'products.panel.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <AccordionForm>
            <AccordionForm.Panel label="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                <TextInput source="description" />
            </AccordionForm.Panel>
            <AccordionForm.Panel label="images" label="Images">
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </AccordionForm.Panel>
            <AccordionForm.Panel label="stock" label="Stock">
                <TextInput source="stock" />
            </AccordionForm.Panel>
            // delete button not displayed
        </AccordionForm>
    </Edit>
);
import { AccordionForm } from "@react-admin/ra-enterprise";

const authProvider = {
    checkAuth: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    getPermissions: () =>
        Promise.resolve([
            // 'delete' is missing
            { action: ["list", "edit"], resource: "products" },
            { action: "write", resource: "products.reference" },
            { action: "write", resource: "products.width" },
            { action: "write", resource: "products.height" },
            // 'products.description' is missing
            { action: "write", resource: "products.thumbnail" },
            // 'products.image' is missing
            { action: "write", resource: "products.panel.description" },
            { action: "write", resource: "products.panel.images" },
            // 'products.panel.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <AccordionForm>
            <AccordionForm.Panel label="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                <TextInput source="description" />
            </AccordionForm.Panel>
            <AccordionForm.Panel label="images" label="Images">
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </AccordionForm.Panel>
            <AccordionForm.Panel label="stock" label="Stock">
                <TextInput source="stock" />
            </AccordionForm.Panel>
            // delete button not displayed
        </AccordionForm>
    </Edit>
);

<AccordionFormPanel>

Replacement for the default <AccordionFormPanel> that only renders a section if the user has the right permissions. Add a name prop to the AccordionFormPanel to define the sub-resource that the user needs to have the right permissions for. <AccordionFormPanel> also only renders the child inputs for which the user has the 'write' permissions.

To learn more about the permissions format, please refer to the @react-admin/ra-rbac documentation.

import { Edit, TextInput } from 'react-admin';
import { AccordionForm } from '@react-admin/ra-form-layout';
import { AccordionFormPanel } from '@react-admin/ra-enterprise';

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ['list', 'edit'], resource: 'products' },
            { action: 'write', resource: 'products.reference' },
            { action: 'write', resource: 'products.width' },
            { action: 'write', resource: 'products.height' },
            // 'products.description' is missing
            { action: 'write', resource: 'products.thumbnail' },
            // 'products.image' is missing
            { action: 'write', resource: 'products.panel.description' },
            { action: 'write', resource: 'products.panel.images' },
            // 'products.panel.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <AccordionForm>
            <AccordionFormPanel label="Description" name="description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </AccordionFormPanel>
            <AccordionFormPanel label="Images" name="images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </AccordionFormPanel>
            // not displayed
            <AccordionFormPanel label="Stock" name="stock">
                <TextInput source="stock" />
            </AccordionFormPanel>
        </AccordionForm>
    </Edit>
);
import { Edit, TextInput } from "react-admin";
import { AccordionForm } from "@react-admin/ra-form-layout";
import { AccordionFormPanel } from "@react-admin/ra-enterprise";

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ["list", "edit"], resource: "products" },
            { action: "write", resource: "products.reference" },
            { action: "write", resource: "products.width" },
            { action: "write", resource: "products.height" },
            // 'products.description' is missing
            { action: "write", resource: "products.thumbnail" },
            // 'products.image' is missing
            { action: "write", resource: "products.panel.description" },
            { action: "write", resource: "products.panel.images" },
            // 'products.panel.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <AccordionForm>
            <AccordionFormPanel label="Description" name="description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </AccordionFormPanel>
            <AccordionFormPanel label="Images" name="images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </AccordionFormPanel>
            // not displayed
            <AccordionFormPanel label="Stock" name="stock">
                <TextInput source="stock" />
            </AccordionFormPanel>
        </AccordionForm>
    </Edit>
);

<AccordionSection>

Replacement for the default <AccordionSection> that only renders a section if the user has the right permissions. Add a name prop to the AccordionSection to define the sub-resource that the user needs to have the right permissions for. <AccordionSection> also only renders the child inputs for which the user has the 'write' permissions.

To learn more about the permissions format, please refer to the @react-admin/ra-rbac documentation.

import { Edit, SimpleForm, TextInput } from 'react-admin';
import { AccordionSection } from '@react-admin/ra-enterprise';

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ['list', 'edit'], resource: 'products' },
            { action: 'write', resource: 'products.reference' },
            { action: 'write', resource: 'products.width' },
            { action: 'write', resource: 'products.height' },
            // 'products.description' is missing
            { action: 'write', resource: 'products.thumbnail' },
            // 'products.image' is missing
            { action: 'write', resource: 'products.section.description' },
            { action: 'write', resource: 'products.section.images' },
            // 'products.section.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <SimpleForm>
            <AccordionSection label="Description" name="description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </AccordionSection>
            <AccordionSection label="Images" name="images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </AccordionSection>
            // not displayed
            <AccordionSection label="Stock" name="stock">
                <TextInput source="stock" />
            </AccordionSection>
        </SimpleForm>
    </Edit>
);
import { Edit, SimpleForm, TextInput } from "react-admin";
import { AccordionSection } from "@react-admin/ra-enterprise";

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ["list", "edit"], resource: "products" },
            { action: "write", resource: "products.reference" },
            { action: "write", resource: "products.width" },
            { action: "write", resource: "products.height" },
            // 'products.description' is missing
            { action: "write", resource: "products.thumbnail" },
            // 'products.image' is missing
            { action: "write", resource: "products.section.description" },
            { action: "write", resource: "products.section.images" },
            // 'products.section.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <SimpleForm>
            <AccordionSection label="Description" name="description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </AccordionSection>
            <AccordionSection label="Images" name="images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </AccordionSection>
            // not displayed
            <AccordionSection label="Stock" name="stock">
                <TextInput source="stock" />
            </AccordionSection>
        </SimpleForm>
    </Edit>
);

<LongForm>

Alternative to react-admin's <LongForm> that adds RBAC control to the delete button and hides sections users don't have access to.

Use in conjunction with ra-enterprise's <LongForm.Section> to render inputs based on permissions.

To learn more about the permissions format, please refer to the @react-admin/ra-rbac documentation.

import { LongForm } from '@react-admin/ra-enterprise';

const authProvider = {
    checkAuth: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    getPermissions: () =>
        Promise.resolve([
            // 'delete' is missing
            { action: ['list', 'edit'], resource: 'products' },
            { action: 'write', resource: 'products.reference' },
            { action: 'write', resource: 'products.width' },
            { action: 'write', resource: 'products.height' },
            // 'products.description' is missing
            { action: 'write', resource: 'products.thumbnail' },
            // 'products.image' is missing
            { action: 'write', resource: 'products.Section.description' },
            { action: 'write', resource: 'products.Section.images' },
            // 'products.Section.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <LongForm>
            <LongForm.Section name="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                <TextInput source="description" />
            </LongForm.Section>
            <LongForm.Section name="images" label="Images">
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </LongForm.Section>
            <LongForm.Section name="stock" label="Stock">
                <TextInput source="stock" />
            </LongForm.Section>
            // delete button not displayed
        </LongForm>
    </Edit>
);
import { LongForm } from "@react-admin/ra-enterprise";

const authProvider = {
    checkAuth: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    getPermissions: () =>
        Promise.resolve([
            // 'delete' is missing
            { action: ["list", "edit"], resource: "products" },
            { action: "write", resource: "products.reference" },
            { action: "write", resource: "products.width" },
            { action: "write", resource: "products.height" },
            // 'products.description' is missing
            { action: "write", resource: "products.thumbnail" },
            // 'products.image' is missing
            { action: "write", resource: "products.Section.description" },
            { action: "write", resource: "products.Section.images" },
            // 'products.Section.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <LongForm>
            <LongForm.Section name="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                <TextInput source="description" />
            </LongForm.Section>
            <LongForm.Section name="images" label="Images">
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </LongForm.Section>
            <LongForm.Section name="stock" label="Stock">
                <TextInput source="stock" />
            </LongForm.Section>
            // delete button not displayed
        </LongForm>
    </Edit>
);

<LongFormSection>

Replacement for the default <LongFormSection> that only renders a section if the user has the right permissions. Add a name prop to the LongFormSection to define the sub-resource that the user needs to have the right permissions for. <LongFormSection> also only renders the child inputs for which the user has the 'write' permissions.

To learn more about the permissions format, please refer to the @react-admin/ra-rbac documentation.

import { LongForm, LongFormSection } from '@react-admin/ra-enterprise';

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ['list', 'edit'], resource: 'products' },
            { action: 'write', resource: 'products.reference' },
            { action: 'write', resource: 'products.width' },
            { action: 'write', resource: 'products.height' },
            // 'products.description' is missing
            { action: 'write', resource: 'products.thumbnail' },
            // 'products.image' is missing
            { action: 'write', resource: 'products.panel.description' },
            { action: 'write', resource: 'products.panel.images' },
            // 'products.panel.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <LongForm>
            <LongFormSection name="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </LongFormSection>
            <LongFormSection name="images" label="Images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </LongFormSection>
            // not displayed
            <LongFormSection name="stock" label="Stock">
                <TextInput source="stock" />
            </LongFormSection>
        </LongForm>
    </Edit>
);
import { LongForm, LongFormSection } from "@react-admin/ra-enterprise";

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ["list", "edit"], resource: "products" },
            { action: "write", resource: "products.reference" },
            { action: "write", resource: "products.width" },
            { action: "write", resource: "products.height" },
            // 'products.description' is missing
            { action: "write", resource: "products.thumbnail" },
            // 'products.image' is missing
            { action: "write", resource: "products.panel.description" },
            { action: "write", resource: "products.panel.images" },
            // 'products.panel.stock' is missing
        ]),
};

const ProductEdit = () => (
    <Edit>
        <LongForm>
            <LongFormSection name="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </LongFormSection>
            <LongFormSection name="images" label="Images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </LongFormSection>
            // not displayed
            <LongFormSection name="stock" label="Stock">
                <TextInput source="stock" />
            </LongFormSection>
        </LongForm>
    </Edit>
);

<WizardForm>

Alternative to react-admin's <WizardForm> that adds RBAC control to hide steps users don't have access to. Use in conjunction with ra-enterprise's <WizardForm.Step> to render inputs based on permissions.

To learn more about the permissions format, please refer to the @react-admin/ra-rbac documentation.

import { WizardForm } from '@react-admin/ra-enterprise';

const authProvider = {
    checkAuth: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    getPermissions: () =>
        Promise.resolve([
            // 'delete' is missing
            { action: ['list', 'edit'], resource: 'products' },
            { action: 'write', resource: 'products.reference' },
            { action: 'write', resource: 'products.width' },
            { action: 'write', resource: 'products.height' },
            // 'products.description' is missing
            { action: 'write', resource: 'products.thumbnail' },
            // 'products.image' is missing
            { action: 'write', resource: 'products.step.description' },
            { action: 'write', resource: 'products.step.images' },
            // 'products.step.stock' is missing
        ]),
};

const ProductCreate = () => (
    <Create>
        <WizardForm>
            <WizardForm.Step name="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                {/* Won't be displayed */}
                <TextInput source="description" />
            </WizardForm.Step>
            <WizardForm.Step name="images" label="Images">
                {/* Won't be displayed */}
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </WizardForm.Step>
            {/* Won't be displayed */}
            <WizardForm.Step name="stock" label="Stock">
                <TextInput source="stock" />
            </WizardForm.Step>
        </WizardForm>
    </Create>
);
import { WizardForm } from "@react-admin/ra-enterprise";

const authProvider = {
    checkAuth: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    getPermissions: () =>
        Promise.resolve([
            // 'delete' is missing
            { action: ["list", "edit"], resource: "products" },
            { action: "write", resource: "products.reference" },
            { action: "write", resource: "products.width" },
            { action: "write", resource: "products.height" },
            // 'products.description' is missing
            { action: "write", resource: "products.thumbnail" },
            // 'products.image' is missing
            { action: "write", resource: "products.step.description" },
            { action: "write", resource: "products.step.images" },
            // 'products.step.stock' is missing
        ]),
};

const ProductCreate = () => (
    <Create>
        <WizardForm>
            <WizardForm.Step name="description" label="Description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                {/* Won't be displayed */}
                <TextInput source="description" />
            </WizardForm.Step>
            <WizardForm.Step name="images" label="Images">
                {/* Won't be displayed */}
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </WizardForm.Step>
            {/* Won't be displayed */}
            <WizardForm.Step name="stock" label="Stock">
                <TextInput source="stock" />
            </WizardForm.Step>
        </WizardForm>
    </Create>
);

<WizardFormStep>

Replacement for the default <WizardFormStep> that only renders a step if the user has the right permissions. Use it with <WizardForm> from @react-admin/ra-enterprise to only display the steps the user has access to in the stepper.

Add a name prop to the WizardFormStep to define the sub-resource that the user needs to have the right permissions for. <WizardFormStep> also only renders the child inputs for which the user has the 'write' permissions.

To learn more about the permissions format, please refer to the @react-admin/ra-rbac documentation.

import { Edit, TextInput } from 'react-admin';
import { WizardForm, WizardFormStep } from '@react-admin/ra-enterprise';

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ['list', 'edit'], resource: 'products' },
            { action: 'write', resource: 'products.reference' },
            { action: 'write', resource: 'products.width' },
            { action: 'write', resource: 'products.height' },
            // 'products.description' is missing
            { action: 'write', resource: 'products.thumbnail' },
            // 'products.image' is missing
            { action: 'write', resource: 'products.step.description' },
            { action: 'write', resource: 'products.step.images' },
            // 'products.step.stock' is missing
        ]),
};

const ProductCreate = () => (
    <Create>
        <WizardForm>
            <WizardForm.Step label="Description" name="description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </WizardForm.Step>
            <WizardForm.Step label="Images" name="images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </WizardForm.Step>
            // not displayed
            <WizardForm.Step label="Stock" name="stock">
                <TextInput source="stock" />
            </WizardForm.Step>
        </WizardForm>
    </Create>
);
import { TextInput } from "react-admin";
import { WizardForm } from "@react-admin/ra-enterprise";

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve([
            { action: ["list", "edit"], resource: "products" },
            { action: "write", resource: "products.reference" },
            { action: "write", resource: "products.width" },
            { action: "write", resource: "products.height" },
            // 'products.description' is missing
            { action: "write", resource: "products.thumbnail" },
            // 'products.image' is missing
            { action: "write", resource: "products.step.description" },
            { action: "write", resource: "products.step.images" },
            // 'products.step.stock' is missing
        ]),
};

const ProductCreate = () => (
    <Create>
        <WizardForm>
            <WizardForm.Step label="Description" name="description">
                <TextInput source="reference" />
                <TextInput source="width" />
                <TextInput source="height" />
                // not displayed
                <TextInput source="description" />
            </WizardForm.Step>
            <WizardForm.Step label="Images" name="images">
                // not displayed
                <TextInput source="image" />
                <TextInput source="thumbnail" />
            </WizardForm.Step>
            // not displayed
            <WizardForm.Step label="Stock" name="stock">
                <TextInput source="stock" />
            </WizardForm.Step>
        </WizardForm>
    </Create>
);

CHANGELOG

v8.0.0

2024-06-17

  • Upgrade to react-admin v5

Breaking Changes

  • We don't provide custom themes anymore. The themes are now provided by react-admin OSS. You can always set your custom theme by using the dedicated props in the <Admin> component. See the theming documentation for more information.

  • ra-enterprise's <Layout> is no longer based on react-admin's default <Layout>. Instead, it uses the <SolarLayout> from ra-navigation.

  • <IconMenuResources> component from @react-admin/ra-navigation is no longer ne default menu. Now, the default menu is the <SolarMenu> component from @react-admin/ra-navigation. If you want to override these components, you can use your own app bar and/or menu by passing a custom layout component to <Admin>:

import { LoadingIndicator } from 'react-admin';
import { Admin, Layout } from '@react-admin/ra-enterprise';
import {
    SolarAppBar,
    SolarMenu,
} from '@react-admin/ra-navigation';
import { Search } from '@react-admin/ra-search';

const CustomAppBar = () => (
    <SolarAppBar>
        <Search />
        <LoadingIndicator />
    </SolarAppBar>
);

const CustomMenu = () => (
    <SolarMenu dense />
);

const CustomLayout = () => (
    <Layout menu={CustomMenu} appBar={CustomAppBar} />
);

export const MyAdmin = () => <Admin layout={CustomLayout}>{/*...*/}</Admin>;
import { LoadingIndicator } from "react-admin";
import { Admin, Layout } from "@react-admin/ra-enterprise";
import { SolarAppBar, SolarMenu } from "@react-admin/ra-navigation";
import { Search } from "@react-admin/ra-search";

const CustomAppBar = () => (
    <SolarAppBar>
        <Search />
        <LoadingIndicator />
    </SolarAppBar>
);

const CustomMenu = () => <SolarMenu dense />;

const CustomLayout = () => <Layout menu={CustomMenu} appBar={CustomAppBar} />;

export const MyAdmin = () => <Admin layout={CustomLayout}>{/*...*/}</Admin>;

If you want to get back the previous layout, you can use the <LayoutWrapper> from @react-admin/ra-enterprise which contains pre-configured <AppLocationContext>, and <TourContext>:

import { Admin, LayoutWrapper } from '@react-admin/ra-enterprise';
import { IconMenuResources } from '@react-admin/ra-navigation';
import { AppBar, Layout, LayoutProps } from 'react-admin';

const CustomLayout = (props: LayoutProps) => (
    <LayoutWrapper>
        <Layout {...props} menu={IconMenuResources} appBar={AppBar} />
    </LayoutWrapper>
);

export const MyAdmin = () => <Admin layout={CustomLayout}>{/*...*/}</Admin>;
import { Admin, LayoutWrapper } from "@react-admin/ra-enterprise";
import { IconMenuResources } from "@react-admin/ra-navigation";
import { AppBar, Layout } from "react-admin";

const CustomLayout = (props) => (
    <LayoutWrapper>
        <Layout {...props} menu={IconMenuResources} appBar={AppBar} />
    </LayoutWrapper>
);

export const MyAdmin = () => <Admin layout={CustomLayout}>{/*...*/}</Admin>;

v7.1.0

2023-09-18

  • Fix default theme adds top margin to page toolbar. This minor visual change may impact your page layouts. Use component overrides in a custom theme to re-add the top margin in RaListToolbar and RaTopToolbar if needed.

v7.0.0

2023-06-16

  • [Breaking Change] The ThemesContext was removed. We now forward the lightTheme and darkTheme directly to react-admin's <Admin>, which will create its own ThemesContext and add a <ToggleThemeButton> to the <AppBar> automatically.
  • [Breaking Change] The AppBar was removed. We can now use react-admin's <AppBar> instead, as it will automatically add the <LocalesMenuButton> based on the i18nProvider.
  • buildI18nProvider now supports availableLocales as 3rd parameter, and polyglotOptions as 4th parameter.
  • <Admin> now uses the <ToggleThemeButton> from react-admin, which allows to automatically choose the light or dark theme based on the user's OS preferences.
  • The documentation about the following theming props has been improved: theme, lightTheme, darkTheme and defaultTheme
  • Upgraded to react-admin 4.11.2

Breaking Changes

If you were using the ThemesContext from ra-enterprise, you will now need to use the one from react-admin with useThemesContext instead:

-import { useContext } from 'react';
-import { ThemesContext } from '@react-admin/ra-enterprise';
-import { useTheme } from 'react-admin';
+import { useTheme, useThemesContext } from 'react-admin';

const MyCustomThemeButtons = () => {
-   const { lightTheme, darkTheme } = useContext(ThemesContext);
+   const { lightTheme, darkTheme } = useThemesContext();
    const [theme, setTheme] = useTheme();

    return (
        <button onClick={() => setTheme(lightTheme)}>Light Theme</button>
        <button onClick={() => setTheme(darkTheme)}>Dark Theme</button>
    );
};

If you used a custom <AppBar>, you no longer need to pass it the <ToggleThemeButton>, as it will be added automatically by react-admin.

-import { AppBar, ToggleThemeButton } from 'react-admin';
+import { AppBar } from 'react-admin';

const MyCustomAppBar = props => (
    <AppBar {...props}>
-       <ToggleThemeButton />
    </AppBar>
);

If you used a custom <AppBar> to set the available locales, you no longer need a custom <AppBar> to do that, and instead, can simply provide them to buildI18nProvider:

 import englishTranslations from 'ra-language-english';
 import frenchTranslations from 'ra-language-french';
 import React from 'react';
-import { LayoutProps, Resource, mergeTranslations } from 'react-admin';
+import { Resource, mergeTranslations } from 'react-admin';
 import {
     Admin,
-    AppBar,
-    AppBarProps,
     Layout,
     RaEnterpriseTranslationMessages,
     buildI18nProvider,
} from '@react-admin/ra-enterprise';
import { ArtistEdit, ArtistList } from './artists';
import dataProvider from './dataProvider';

const languages = [
    { locale: 'en', name: 'English 🇬🇧' },
    { locale: 'fr', name: 'Français 🇫🇷' },
];

const messages = {
    en: mergeTranslations(englishTranslations, {
        resources: {
            artists: {
                name: 'Painter |||| Painters',
            },
        },
    }),
    fr: mergeTranslations(frenchTranslations, {
        resources: {
            artists: {
                name: 'Peintre |||| Peintres',
            },
        },
    }),
};

-const CustomAppBar = (props: AppBarProps) => (
-    <AppBar languages={languages} {...props} />
-);
-
-const CustomLayout = (props: LayoutProps) => (
-    <Layout appBar={CustomAppBar} {...props} />
-);

 export const WithCustomizedTranslations = () => (
     <Admin
         dataProvider={dataProvider}
-        layout={CustomLayout}
+        layout={Layout}
         i18nProvider={buildI18nProvider<RaEnterpriseTranslationMessages>(
             messages,
-            'fr'
+            'fr',
+            languages
         )}
     >
         <Resource name="artists" list={ArtistList} edit={ArtistEdit} />
    </Admin>
);

If you used a custom AppBar based on ra-enterprise's <AppBar>, you can simply replace it with react-admin's <AppBar>, without forgetting to add the <TitlePortal>:

import { Button } from '@mui/material';
import { useTour } from '@react-admin/ra-tour';
-import { AppBar, AppBarProps } from '@react-admin/ra-enterprise';
+import { AppBar, AppBarProps, TitlePortal } from 'react-admin';

const CustomAppBarWithTour = (props: AppBarProps) => {
    const [_, { start }] = useTour();

    const handleStart = (): void => {
        start('example');
    };

    return (
        <AppBar {...props}>
+           <TitlePortal />
            <Button onClick={handleStart} variant="contained">
                Start Tour
            </Button>
        </AppBar>
    );
};

v6.2.0

2023-05-24

  • Upgraded to react-admin 4.10.6

v6.1.0

2023-05-23

  • Added ra-form-layout forms alternatives that leverage ra-rbac (<AccordionForm>, <AccordionFormPanel>, <AccordionFormSection>, <LongForm>, <LongFormSection>, <WizardForm> and <WizardFormStep>)

v6.0.5

2023-05-22

  • (fix) Fix <AppBar> title style for react-admin v4.9.0 and greater

v6.0.4

2022-06-29

  • (fix) Fix <FilterForm> and <ListActions> width in RaTopToolbar

v6.0.3

2022-06-10

  • (fix) Bump enterprise packages versions (specifically including ra-navigation v4.0.4)

v6.0.2

2022-06-08

  • (fix) Update peer dependencies ranges (support React 18)

v6.0.1

2022-06-07

  • Pass queryClient as a tool to the TourProvider

v6.0.0

2022-06-07

  • Upgrade to react-admin v4

Breaking changes

  • Removed <List>, <Create>, <Edit> and <Show> components. You can simply import them from react-admin:
-import { Datagrid, TextField } from 'react-admin';
-import { List } from '@react-admin/ra-enterprise';
+import { Datagrid, List, TextField } from 'react-admin';

const ArtistList = () => (
    <List>
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
        </Datagrid>
    </List>
);
  • Removed <EventList> and <RealTimeList> components. You can simply import them from their package:
// In App.js
import { Admin, Resource } from 'react-admin';
-import { EventList } from '@react-admin/ra-enterprise';
+import { EventList } from '@react-admin/ra-audit-log';

export const App = () => (
    <Admin>
        <Resource name="events" list={EventList} />
    </Admin>
);
import React from 'react';
import { ListProps, Datagrid, TextField } from 'react-admin';
-import { RealTimeList } from '@react-admin/ra-enterprise';
+import { RealTimeList } from '@react-admin/ra-realtime';

const PostList = () => (
    <RealTimeList>
        <Datagrid>
            <TextField source="title" />
        </Datagrid>
    </RealTimeList>
);
  • The breadcrumb is now inside the <Layout> instead of individual views.

We used to include the breadcrumb in the actions bar for each view. However, this had limitations as the breadcrumb often need a lot of horizontal space. Hence, we moved it back to the layout.

If you used to pass your custom layout to each view through the actions component we provided, you can now pass it to the <Layout> component.

// in ./src/MyBreadcrumb.js
import { Breadcrumb, BreadcrumbItem } from '@react-admin/ra-navigation';

export const MyBreadcrumb = props => (
    <Breadcrumb {...props}>
        <BreadcrumbItem name="dashboard" label="My Home">
            <ResourceBreadcrumbItems resources={['songs', 'artists']} />
        </BreadcrumbItem>
    </Breadcrumb>
);

// in ./src/MyLayout.js
import { Layout } from '@react-admin/ra-enterprise';
+import { MyBreadcrumb } from './MyBreadcrumb';

export const MyLayout = props => (
-   <Layout {...props} />
+   <Layout {...props} breadcrumb={<MyBreadcrumb />} />
);

// in ./src/App.js
import { Admin } from '@react-admin/ra-enterprise';
import { MyLayout } from './MyLayout';

export const MyAdmin = () => (
    <Admin layout={MyLayout}>
        // ...
    </Admin>
)

// in ./src/ArtistList.js
-import { Datagrid, TextField } from 'react-admin';
-import { List, ListActions } from '@react-admin/ra-enterprise';
-import { MyBreadcrumb } from './MyBreadcrumb';
+import { Datagrid, List, TextField } from 'react-admin';

-const ArtistListActions = props => (
-    <ListActions {...props} breadcrumb={<MyBreadcrumb variant="actions" />} />
-);

const ArtistList = () => (
    <List
-        actions={<ArtistListActions />}
    >
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
        </Datagrid>
    </List>
);
  • Removed the <Menu> component. Use the <MenuItemCategoryResources> component from ra-navigation instead:
import { Layout } from 'react-admin';
-import { Menu } from '@react-admin/ra-enterprise';
+import { MenuItemCategoryResources } from '@react-admin/ra-navigation';

-const MyLayout = props => <Layout menu={<Menu />} />
+const MyLayout = props => <Layout menu={<MenuItemCategoryResources />} />

v5.1.3

2021-12-23

  • (fix) Fix <AppBar> logs a warning when overriding styles via the classes prop.

v5.1.2

2021-12-02

  • (chore) Upgrade react-admin to 3.19.2
  • (fix) Fix i18nProvider jsDoc

v5.1.1

2021-09-16

  • (fix) Fix <List> toolbar styles

v5.1.0

2021-09-14

  • (feat) Include ra-audit-log

v5.0.0

2021-09-08

  • (chore) Upgrade react-admin to 3.18
  • (fix) Breaking change: Following the upgrade to react-admin 3.18, we now have to specify the width of the sidebar with an arbitrary default value. You might have to adjust it with a custom theme.
import { defaultTheme } from 'react-admin';
import { ThemeOptions } from '@react-admin/ra-navigation';

export const theme: ThemeOptions = {
    ...defaultTheme,
    overrides: {
        RaSidebar: {
            drawerPaper: {
                width: 96,
            },
            fixed: {
                zIndex: 1200,
            },
        },
    },
};
import { defaultTheme } from "react-admin";

export const theme = {
    ...defaultTheme,
    overrides: {
        RaSidebar: {
            drawerPaper: {
                width: 96,
            },
            fixed: {
                zIndex: 1200,
            },
        },
    },
};

v4.2.1

2021-08-03

  • (fix) Merge user provided themes with the default ones.

v4.2.0

2021-07-21

  • (feat) Introduce a customized version of react-admin <Filter> component which fixes the issue where too much space was taken by an empty filter form. To use it, you must update your imports:

    - import { Filter } from 'react-admin';
    + import { Filter } from '@react-admin/ra-enterprise';
    

v4.1.5

2021-07-21

  • (fix) Fix build by upgrading it to 3.17.0

v4.1.4

2021-06-29

  • (fix) Fix imports from react-admin by upgrading it to 3.16.6
  • (fix) Export all prop interfaces

v4.1.3

2021-06-29

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

v4.1.2

2021-04-22

  • (fix) Avoid spreading react-admin props on html elements

v4.1.1

2021-04-29

  • (fix) Setup translations for all existing modules.

v4.1.0

2021-04-08

  • (feat) Automatically set up the breadcrumb correctly when a dashboard has been specified.

v4.0.0

2021-04-01

Breaking change

  • (feat) Remove <BreadcrumbForActions> in favor of <Breadcrumb variant="actions">

v3.2.0

2021-03-23

  • (feat) Provide Custom RealTimeList With Breadcrumb
  • (fix) Fix and Simplify Props Interfaces
  • (fix) Ensure Breadcrumb is Well Integrated in Views

v3.1.0

2021-03-19

  • (feat) Allow to customize breadcrumb in views and actions

v3.0.0

2021-02-19

BREAKING CHANGE

  • Layout does not accept a breadcrumb prop and does not include a default Breadcrumb anymore.

Instead, we recommend to includes the Breacrumb inside the views directly. This was motivated by the fact that the breadcrumb was taking too much space above the views. In order to keep the breadcrumb in the default react-admin views, we recommend using the alternative versions provided by this package. See below.

  • (feat) Add preconfigured Show and List which includes a Breacrumb in their actions toolbar.
  • (feat) Update Create and Edit to includes a Breacrumb in their actions toolbar.
  • (feat) Provide CreateActions, EditAction, ListActions and ShowActions which includes a Breacrumb.

v2.0.1

2021-02-16

  • (fix) Remove dependency to ra-language-french

v2.0.0

2020-11-19

  • Upgrade ra-relationships to 2.0.0
  • Provide <Create> and <Edit> components which wraps their children with a <ManyToManyReferenceContextProvider>

v1.1.0

2021-01-08

  • Update to react-admin 3.11

v1.0.5

2020-12-10

  • (fix) Fix custom menu cannot be collapsed by upgrading react-admin

v1.0.4

2020-12-04

  • Clarify documentation about exported components

v1.0.3

2020-11-18

  • Upgrade to react-admin 3.10

v1.0.2

2020-11-04

  • (fix) Fix default messages export

v1.0.1

2020-10-31

  • (fix) Fix theme switcher works on some elements only

v1.0.0

2020-10-14

  • First release ✨
  • (deps) Add @react-admin/ra-search to dependencies

v0.1.2

2020-10-13

  • (fix) Fix the Messages type
  • (deps) Upgrade react-admin to v3.9.3

v0.1.1

2020-10-08

  • (feat) Enable <MultiLevelMenu> by default

v0.1.0

2020-10-05

  • (deps) Upgrade to react-admin v3.9.0

v0.0.5

2020-10-01

  • (fix) Remove the create button in the list view of the Admin and Layout stories
  • (deps) Upgrade @react-admin/ra-tree to v1.2.6 to fix issues with custom reducers

v0.0.4

2020-09-29

  • (fix) Dashboard is not detected

v0.0.3

2020-09-25

  • (fix) Fix an error that makes the theme unchangeable
  • (fix) Fix the background color of the default dark theme

v0.0.2

2020-09-24

  • (fix) Fix TypeScript types for the theme
  • (fix) Allow passing an empty theme to the <Admin>
  • (deps) Add @react-admin/ra-form-layout to dependencies

v0.0.1

2020-09-18

  • (feat) Add a pre-configured <Admin> component
  • (feat) Add pre-configured layout components (<Layout>, <AppBar>, <Breadcrumb>, <Sidebar>)
  • (feat) Add i18nProvider methods
  • (feat) Add theme methods