`).
## Tabs
-Children of `
` must be `` components.
+Children of `` must be `` components.
-The `` component renders tabs headers and the active tab. It manages the tab change, either via the URL, or an internal state.
+The `` component renders tabs headers and the active tab. It manages the tab change, either via the URL, or an internal state.
It accepts the following props:
@@ -82,18 +82,18 @@ It accepts the following props:
import * as React from "react";
import FavoriteIcon from '@mui/icons-material/Favorite';
import PersonPinIcon from '@mui/icons-material/PersonPin';
-import { Show, TabbedShowLayout, Tab, TextField } from 'react-admin';
+import { Show, TabbedShowLayout, TextField } from 'react-admin';
export const PostShow = () => (
- }>
+ }>
-
- } path="metadata">
+
+ } path="metadata">
-
+
);
@@ -101,15 +101,15 @@ export const PostShow = () => (
## Tab Fields
-`
` renders each child inside a `` component. This component uses the humanized source as label by default. You can customize it by passing a `label` prop to the fields:
+`` renders each child inside a `` component. This component uses the humanized source as label by default. You can customize it by passing a `label` prop to the fields:
```jsx
const PostShow = () => (
-
+
-
+
);
@@ -122,10 +122,10 @@ The `` uses the humanized source by default. You can customize it
const PostShow = () => (
-
+
-
+
);
@@ -151,15 +151,15 @@ You can disable the `` decoration by passing setting `label={false}` on
const PostShow = () => (
-
+
-
+
);
```
-`` children can be anything you want. Try passing your own components:
+`` children can be anything you want. Try passing your own components:
```jsx
const PostTitle = () => {
@@ -170,9 +170,9 @@ const PostTitle = () => {
const PostShow = () => (
-
+
-
+
);
@@ -189,22 +189,22 @@ import { TabbedShowLayout, Tab } from 'react-admin'
export const PostShow = () => (
-
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -212,26 +212,26 @@ export const PostShow = () => (
-
+
);
```
{% endraw %}
-**Tip**: When `syncWithLocation` is `false`, the `path` prop of the `` components is ignored.
+**Tip**: When `syncWithLocation` is `false`, the `path` prop of the `` components is ignored.
## Spacing
-`` renders a MUI ``. You can customize the spacing of each row by passing a `spacing` prop:
+`` renders a MUI ``. You can customize the spacing of each row by passing a `spacing` prop:
```jsx
const PostShow = () => (
-
+
-
+
);
@@ -249,9 +249,9 @@ import { Divider } from '@mui/material';
const PostShow = () => (
}>
-
+
-
+
);
@@ -289,9 +289,9 @@ By default, `` reads the record from the `ResourceContext`. Bu
```jsx
const StaticPostShow = () => (
-
+
-
+
);
```
@@ -309,7 +309,7 @@ The `` component accepts the usual `className` prop but you ca
To override the style of all instances of `` using the [MUI style overrides](https://mui.com/customization/theme-components/), use the `RaTabbedShowLayout` key.
-To style the tabs, the `` component accepts two props:
+To style the tabs, the `` component accepts two props:
- `className` is passed to the tab *header*
- `contentClassName` is passed to the tab *content*
@@ -318,16 +318,16 @@ To style the tabs, the `` component accepts two props:
* [Field components](./Fields.md)
* [Show Guesser](./ShowGuesser.md) guesses the fields based on the record type
-* [SimpleShowLayout](./SimpleShowLayout.md) provides a simpler layout with no tabs
+* [SimpleShowLayout](./TabbedShowLayout.md) provides a simpler layout with no tabs
## API
* [``]
-* [``]
+* [``]
* [``]
* [`useRecordContext`]
[``]: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/Labeled.tsx
[``]: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/detail/TabbedShowLayout.tsx
-[``]: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/detail/Tab.tsx
+[``]: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/detail/Tab.tsx
[`useRecordContext`]: https://github.com/marmelab/react-admin/blob/master/packages/ra-core/src/controller/record/useRecordContext.ts
diff --git a/examples/demo/src/products/ProductCreate.tsx b/examples/demo/src/products/ProductCreate.tsx
index fd81586a80c..d7767832b76 100644
--- a/examples/demo/src/products/ProductCreate.tsx
+++ b/examples/demo/src/products/ProductCreate.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { Create, FormTab, TabbedForm, TextInput, required } from 'react-admin';
+import { Create, TabbedForm, TextInput, required } from 'react-admin';
import { RichTextInput } from 'ra-input-rich-text';
import { ProductEditDetails } from './ProductEditDetails';
@@ -8,7 +8,7 @@ const ProductCreate = () => {
return (
-
@@ -23,20 +23,20 @@ const ProductCreate = () => {
fullWidth
validate={required()}
/>
-
-
+
-
-
+
-
+
);
diff --git a/examples/demo/src/products/ProductEdit.tsx b/examples/demo/src/products/ProductEdit.tsx
index bdec376bdef..fa161df6790 100644
--- a/examples/demo/src/products/ProductEdit.tsx
+++ b/examples/demo/src/products/ProductEdit.tsx
@@ -4,7 +4,6 @@ import {
DateField,
Edit,
EditButton,
- FormTab,
Pagination,
ReferenceManyField,
required,
@@ -31,28 +30,28 @@ const ProductTitle = () => {
const ProductEdit = () => (
}>
-
-
-
+
-
-
+
-
+
{
if (!isLoading) {
label += ` (${total})`;
}
- return ;
+ return ;
};
export default ProductEdit;
diff --git a/examples/simple/src/posts/PostEdit.tsx b/examples/simple/src/posts/PostEdit.tsx
index 8e54a888866..3f920215c3f 100644
--- a/examples/simple/src/posts/PostEdit.tsx
+++ b/examples/simple/src/posts/PostEdit.tsx
@@ -14,7 +14,6 @@ import {
CreateButton,
ShowButton,
EditButton,
- FormTab,
ImageField,
ImageInput,
NumberInput,
@@ -107,7 +106,7 @@ const PostEdit = () => {
defaultValues={{ average_note: 0 }}
warnWhenUnsavedChanges
>
-
+
{
)}
-
-
+
+
-
-
+
+
{
-
-
+
+
{
-
+
);
diff --git a/examples/simple/src/posts/PostShow.tsx b/examples/simple/src/posts/PostShow.tsx
index a06642cee1e..fb94597cab5 100644
--- a/examples/simple/src/posts/PostShow.tsx
+++ b/examples/simple/src/posts/PostShow.tsx
@@ -15,7 +15,6 @@ import {
ShowContextProvider,
ShowView,
SingleFieldList,
- Tab,
TabbedShowLayout,
TextField,
UrlField,
@@ -43,7 +42,7 @@ const PostShow = () => {
}>
-
+
{controllerProps.record &&
@@ -57,15 +56,15 @@ const PostShow = () => {
-
-
+
+
-
-
+
+
{
-
-
+
+
{
-
+
diff --git a/examples/simple/src/users/UserCreate.tsx b/examples/simple/src/users/UserCreate.tsx
index 51b6459e89e..7621a203c9e 100644
--- a/examples/simple/src/users/UserCreate.tsx
+++ b/examples/simple/src/users/UserCreate.tsx
@@ -3,7 +3,6 @@ import * as React from 'react';
import { useFormContext } from 'react-hook-form';
import {
Create,
- FormTab,
SaveButton,
AutocompleteInput,
TabbedForm,
@@ -61,16 +60,16 @@ const UserCreate = () => {
warnWhenUnsavedChanges
toolbar={}
>
-
+
-
+
{permissions === 'admin' && (
-
+
{
]}
validate={[required()]}
/>
-
+
)}
diff --git a/examples/simple/src/users/UserEdit.tsx b/examples/simple/src/users/UserEdit.tsx
index 232fca45506..1e849e4e026 100644
--- a/examples/simple/src/users/UserEdit.tsx
+++ b/examples/simple/src/users/UserEdit.tsx
@@ -4,7 +4,6 @@ import {
CloneButton,
DeleteWithConfirmButton,
Edit,
- FormTab,
required,
SaveButton,
SelectInput,
@@ -64,16 +63,16 @@ const UserEditForm = ({ save, ...props }: { save?: any }) => {
{...props}
onSubmit={newSave}
>
-
+
{permissions === 'admin' && }
-
+
{permissions === 'admin' && (
-
+
{
]}
defaultValue={'user'}
/>
-
+
)}
);
diff --git a/examples/simple/src/users/UserShow.tsx b/examples/simple/src/users/UserShow.tsx
index ee97277ea58..28a75a666a7 100644
--- a/examples/simple/src/users/UserShow.tsx
+++ b/examples/simple/src/users/UserShow.tsx
@@ -1,12 +1,6 @@
/* eslint react/jsx-key: off */
import * as React from 'react';
-import {
- Show,
- Tab,
- TabbedShowLayout,
- TextField,
- usePermissions,
-} from 'react-admin';
+import { Show, TabbedShowLayout, TextField, usePermissions } from 'react-admin';
import Aside from './Aside';
@@ -15,14 +9,17 @@ const UserShow = () => {
return (
-
+
-
+
{permissions === 'admin' && (
-
+
-
+
)}
diff --git a/packages/ra-ui-materialui/src/detail/Tab.tsx b/packages/ra-ui-materialui/src/detail/Tab.tsx
index bf3c3decc17..a7ada401b0e 100644
--- a/packages/ra-ui-materialui/src/detail/Tab.tsx
+++ b/packages/ra-ui-materialui/src/detail/Tab.tsx
@@ -18,23 +18,25 @@ import { Labeled } from '../Labeled';
* - icon: The icon to show before the label (optional). Must be a component.
* - path: The string used for custom urls
*
+ * It is also available as TabbedShowLayout.Tab.
+ *
* @example
* // in src/posts.js
* import * as React from "react";
* import FavoriteIcon from '@mui/icons-material/Favorite';
* import PersonPinIcon from '@mui/icons-material/PersonPin';
- * import { Show, TabbedShowLayout, Tab, TextField } from 'react-admin';
+ * import { Show, TabbedShowLayout, TextField } from 'react-admin';
*
* export const PostShow = (props) => (
*
*
- * }>
+ * }>
*
*
- *
- * } path="metadata">
+ *
+ * } path="metadata">
*
- *
+ *
*
*
* );
diff --git a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.spec.tsx b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.spec.tsx
index 6b0b9b09cd9..70ccf618554 100644
--- a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.spec.tsx
+++ b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.spec.tsx
@@ -5,7 +5,6 @@ import { createMemoryHistory } from 'history';
import { CoreAdminContext, testDataProvider } from 'ra-core';
import { TabbedShowLayout } from './TabbedShowLayout';
-import { Tab } from './Tab';
import { TextField } from '../field';
describe('', () => {
@@ -17,12 +16,12 @@ describe('', () => {
history={history}
>
-
+
-
-
+
+
-
+
);
@@ -40,12 +39,12 @@ describe('', () => {
>
{null}
-
+
-
-
+
+
-
+
);
@@ -64,12 +63,12 @@ describe('', () => {
>
{null}
-
+
-
-
+
+
-
+
);
@@ -94,12 +93,12 @@ describe('', () => {
>
{null}
-
+
-
-
+
+
-
+
);
@@ -124,12 +123,12 @@ describe('', () => {
>
{null}
-
+
-
-
+
+
-
+
);
diff --git a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx
index 047bf64c318..518f54eaddf 100644
--- a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx
+++ b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx
@@ -10,7 +10,6 @@ import {
import { Labeled } from '../Labeled';
import { TextField, NumberField } from '../field';
import { TabbedShowLayout } from './TabbedShowLayout';
-import { Tab } from './Tab';
export default { title: 'ra-ui-materialui/detail/TabbedShowLayout' };
@@ -28,15 +27,15 @@ export const Basic = () => (
-
+
-
-
+
+
-
+
@@ -53,12 +52,12 @@ export const CustomChild = () => (
-
+
{record.author}}
/>
-
+
@@ -70,7 +69,7 @@ export const CustomLabel = () => (
-
+
@@ -78,7 +77,7 @@ export const CustomLabel = () => (
-
+
@@ -90,13 +89,13 @@ export const Spacing = () => (
-
+
-
+
@@ -108,13 +107,13 @@ export const Divider = () => (
}>
-
+
-
+
@@ -132,13 +131,13 @@ export const SX = () => (
bgcolor: 'text.disabled',
}}
>
-
+
-
+
diff --git a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.tsx b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.tsx
index 5526695ebe3..2cd76603c36 100644
--- a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.tsx
+++ b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.tsx
@@ -23,6 +23,7 @@ import {
TabbedShowLayoutTabs,
getShowLayoutTabFullPath,
} from './TabbedShowLayoutTabs';
+import { Tab } from './Tab';
/**
* Layout for a Show view showing fields grouped in tabs and laid out in a single column.
@@ -31,24 +32,24 @@ import {
* each of which contains a list of record fields in a single-column layout
* (via MUI's `` component).
* `` delegates the actual rendering of fields to its children,
- * which should be `` components.
- * `` wraps each field inside a component to add a label.
+ * which should be `` components.
+ * `` wraps each field inside a `` component to add a label.
*
* @example
* // in src/posts.js
* import * as React from "react";
- * import { Show, TabbedShowLayout, Tab, TextField } from 'react-admin';
+ * import { Show, TabbedShowLayout, TextField } from 'react-admin';
*
* export const PostShow = () => (
*
*
- *
+ *
*
*
- *
- *
+ *
+ *
*
- *
+ *
*
*
* );
@@ -180,6 +181,8 @@ export const TabbedShowLayout = (props: TabbedShowLayoutProps) => {
);
};
+TabbedShowLayout.Tab = Tab;
+
export interface TabbedShowLayoutProps {
children: ReactNode;
className?: string;
diff --git a/packages/ra-ui-materialui/src/form/FormTab.spec.tsx b/packages/ra-ui-materialui/src/form/FormTab.spec.tsx
index 86e1e0d0c1e..2f67485a9aa 100644
--- a/packages/ra-ui-materialui/src/form/FormTab.spec.tsx
+++ b/packages/ra-ui-materialui/src/form/FormTab.spec.tsx
@@ -3,19 +3,18 @@ import expect from 'expect';
import { testDataProvider } from 'ra-core';
import { render, screen, waitFor } from '@testing-library/react';
import { TabbedForm } from './TabbedForm';
-import { FormTab } from './FormTab';
import { TextInput } from '../input';
import { AdminContext } from '../AdminContext';
-describe('', () => {
+describe('', () => {
it('should display ', async () => {
render(
-
+
-
+
);
@@ -24,7 +23,7 @@ describe('', () => {
});
});
- it('should render a TabbedForm with FormTabs having custom props without warnings', async () => {
+ it('should render a TabbedForm with TabbedForm.Tabs having custom props without warnings', async () => {
let countWarnings = 0;
const spy = jest
.spyOn(console, 'error')
@@ -39,7 +38,7 @@ describe('', () => {
const { container } = render(
- ', () => {
variant="standard"
>
-
-
+ ', () => {
variant="filled"
>
-
-
+ ', () => {
variant="outlined"
>
-
+
);
diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx b/packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx
index d7b2d8336f4..8e8025babb8 100644
--- a/packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx
+++ b/packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx
@@ -17,7 +17,6 @@ import {
import { AdminContext } from '../AdminContext';
import { TabbedForm } from './TabbedForm';
import { TabbedFormClasses } from './TabbedFormView';
-import { FormTab } from './FormTab';
import { TextInput } from '../input';
describe('', () => {
@@ -26,8 +25,8 @@ describe('', () => {
render(
-
-
+
+
);
@@ -42,20 +41,20 @@ describe('', () => {
-
+
-
-
+
+
-
+
@@ -87,20 +86,20 @@ describe('', () => {
-
+
-
-
+
+
-
+
@@ -128,20 +127,20 @@ describe('', () => {
-
+
-
-
+
+
-
+
@@ -171,20 +170,20 @@ describe('', () => {
-
+
-
-
+
+
-
+
@@ -220,15 +219,15 @@ describe('', () => {
-
+
-
-
+
+
-
+
@@ -268,7 +267,7 @@ describe('', () => {
-
+
diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx b/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx
index 657d1f1e63c..389b3fac798 100644
--- a/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx
+++ b/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx
@@ -5,7 +5,6 @@ import { AdminContext } from '../AdminContext';
import { Edit } from '../detail';
import { NumberInput, TextInput } from '../input';
import { TabbedForm } from './TabbedForm';
-import { FormTab } from './FormTab';
import { Stack } from '@mui/material';
export default { title: 'ra-ui-materialui/forms/TabbedForm' };
@@ -41,11 +40,11 @@ const Wrapper = ({ children }) => (
export const Basic = () => (
-
+
-
+
);
@@ -53,14 +52,14 @@ export const Basic = () => (
export const MultipleTabs = () => (
-
+
-
-
+
+
-
+
);
@@ -68,13 +67,13 @@ export const MultipleTabs = () => (
export const CustomLayout = () => (
-
+
-
+
);
@@ -82,11 +81,11 @@ export const CustomLayout = () => (
export const NoToolbar = () => (
-
+
-
+
);
diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.tsx b/packages/ra-ui-materialui/src/form/TabbedForm.tsx
index 2ebf80bf1c3..1293c86bf0f 100644
--- a/packages/ra-ui-materialui/src/form/TabbedForm.tsx
+++ b/packages/ra-ui-materialui/src/form/TabbedForm.tsx
@@ -12,11 +12,12 @@ import get from 'lodash/get';
import { TabbedFormView, TabbedFormViewProps } from './TabbedFormView';
import { useFormRootPath } from './useFormRootPath';
+import { FormTab } from './FormTab';
/**
* Form layout where inputs are divided by tab, one input per line.
*
- * Pass FormTab components as children.
+ * Pass components as children.
*
* @example
*
@@ -24,7 +25,6 @@ import { useFormRootPath } from './useFormRootPath';
* import {
* Edit,
* TabbedForm,
- * FormTab,
* Datagrid,
* TextField,
* DateField,
@@ -39,22 +39,22 @@ import { useFormRootPath } from './useFormRootPath';
* export const PostEdit = (props) => (
*
*
- *
+ *
*
*
*
- *
- *
+ *
+ *
*
- *
- *
+ *
+ *
*
*
*
*
*
- *
- *
+ *
+ *
*
*
*
@@ -62,7 +62,7 @@ import { useFormRootPath } from './useFormRootPath';
*
*
*
- *
+ *
*
*
* );
@@ -88,6 +88,8 @@ export const TabbedForm = (props: TabbedFormProps) => {
);
};
+TabbedForm.Tab = FormTab;
+
const sanitizeRestProps = ({
criteriaMode,
defaultValues,
diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx
index faa79893e71..2ba06baf634 100644
--- a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx
+++ b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx
@@ -5,7 +5,7 @@ import { createMemoryHistory } from 'history';
import { InputAdornment } from '@mui/material';
import { Edit, Create } from '../../detail';
-import { SimpleForm, TabbedForm, FormTab } from '../../form';
+import { SimpleForm, TabbedForm } from '../../form';
import { ArrayInput } from './ArrayInput';
import { SimpleFormIterator } from './SimpleFormIterator';
import { TextInput } from '../TextInput';
@@ -442,7 +442,7 @@ const CreateGlobalValidationInFormTab = () => {
We still need `validate={required()}` to indicate fields are required
with a '*' symbol after the label, but the real validation happens in `globalValidator`
*/}
-
+
{
-
+
);