Skip to content

Commit

Permalink
Merge pull request #9439 from marmelab/singlefieldlist-empty
Browse files Browse the repository at this point in the history
Update `<SingleFieldList>` to make it more powerful
  • Loading branch information
slax57 authored Nov 14, 2023
2 parents 9a3c3c8 + b5f8b66 commit 9768ee1
Show file tree
Hide file tree
Showing 12 changed files with 415 additions and 140 deletions.
75 changes: 60 additions & 15 deletions docs/SingleFieldList.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,32 @@ Use `<SingleFieldList>` when you want to display only one property for each reco

## Usage

Use `<SingleFieldList>` wherever there is a `ListContext`. It is especially useful as child of `<ReferenceManyField>` and `<ReferenceArrayField>` components. `<SingleFieldList>` expects a single `<Field>` as child.
`<SingleFieldList>` grabs the current `ListContext`, and renders a Material UI `<Stack>` with one `<ChipField>` for each record in the list, using the `recordRepresentation`. It is especially useful as child of `<ReferenceManyField>` and `<ReferenceArrayField>` components.

Here is an example of a Post show page showing the list of tags for the current post:

```jsx
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
import {
Show,
SimpleShowLayout,
TextField,
ReferenceArrayField,
SingleFieldList
} from 'react-admin';

const PostShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="title" />
<ReferenceArrayField label="Tags" reference="tags" source="tags">
<SingleFieldList />
</ReferenceArrayField>
</SimpleShowLayout>
</Show>
);
```

The following example shows how to use `<SingleFieldList>` to display a list of tags for each post in a Datagrid:
You can also use `<SingleFieldList>` in a list view, e.g. to display the tags for each post in a `<Datagrid>`:

```jsx
import {
Expand All @@ -45,9 +62,7 @@ const PostList = () => (
<BooleanField source="commentable" />
<NumberField source="views" />
<ReferenceArrayField label="Tags" reference="tags" source="tags">
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
<SingleFieldList />
</ReferenceArrayField>
</Datagrid>
</List>
Expand All @@ -56,14 +71,46 @@ const PostList = () => (

![SingleFieldList in Datagrid](./img/singlefieldlist-datagrid.png)

You can customize how each record is displayed by passing a Field component as child. For example, you can change the field name used by the `<ChipField>`:

```jsx
<SingleFieldList>
<ChipField source="tag" clickable />
</SingleFieldList>
```

## Props

`<SingleFieldList>` accepts the following props:

| Prop | Required | Type | Default | Description |
| ----------- | -------- | ------------------------- | ------- | --------------------------------------------- |
| `linkType` | Optional | `'edit' | 'show' | false` | `edit` | The target of the link on each item |
| `sx` | Optional | `object` | | The sx props of the Material UI Box component |
| Prop | Required | Type | Default | Description |
| ----------- | -------- | ------------------------- | ------- | ----------------------------------------------- |
| `children` | Optional | `ReactNode` | | React element to render for each record |
| `empty` | Optional | `ReactNode` | | React element to display when the list is empty |
| `linkType` | Optional | `'edit' | 'show' | false` | `edit` | The target of the link on each item |
| `sx` | Optional | `object` | | The sx props of the Material UI Box component |

Additional props are passed down to the underlying [Material UI `<Stack>` component](https://mui.com/material-ui/react-stack/).

## `children`

By default, `<SingleFieldList>` renders a `<ChipField>` for each record. You can customize the rendering by passing a Field component as child.

For example, if you want to customize the field name used by the `<ChipField>`:

```jsx
<SingleFieldList>
<ChipField source="tag" clickable />
</SingleFieldList>
```

## `empty`

When the list is empty, `<SingleFieldList>` displays nothing. You can customize this behavior by passing a React element as the `empty` prop. For example, to display a message:

```jsx
<SingleFieldList empty={<p>Nothing to display</p>} />
```

## `linkType`

Expand All @@ -76,9 +123,7 @@ The `<SingleFieldList>` items link to the edition page by default. You can set t
reference="tags"
source="tags"
>
<SingleFieldList linkType="show">
<ChipField source="name" />
</SingleFieldList>
<SingleFieldList linkType="show" />
</ReferenceArrayField>
```

Expand Down
3 changes: 2 additions & 1 deletion examples/crm/src/contacts/ContactList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ const ContactListContent = () => {
<TextField source="name" />
</ReferenceField>{' '}
{contact.nb_notes &&
`- ${contact.nb_notes} notes `}
`- ${contact.nb_notes} notes`}
&nbsp;&nbsp;
<TagsList />
</>
}
Expand Down
2 changes: 1 addition & 1 deletion examples/crm/src/contacts/TagsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const TagsList = () => (
source="tags"
reference="tags"
>
<SingleFieldList linkType={false} component="span">
<SingleFieldList linkType={false}>
<ColoredChipField source="name" variant="outlined" size="small" />
</SingleFieldList>
</ReferenceArrayField>
Expand Down
4 changes: 2 additions & 2 deletions examples/simple/src/posts/PostList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ const PostListDesktop = () => (
cellClassName="hiddenOnSmallScreens"
headerClassName="hiddenOnSmallScreens"
>
<SingleFieldList sx={{ my: -2 }}>
<ChipField source="name.en" size="small" />
<SingleFieldList>
<ChipField clickable source="name.en" size="small" />
</SingleFieldList>
</ReferenceArrayField>
<NumberField source="average_note" />
Expand Down
1 change: 1 addition & 0 deletions examples/simple/src/posts/PostShow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const PostShow = () => {
<ChipField
source={`name.${locale}`}
size="small"
clickable
/>
</SingleFieldList>
</ReferenceArrayField>
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/field/ChipField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,5 @@ const StyledChip = styled(Chip, {
name: PREFIX,
overridesResolver: (props, styles) => styles.root,
})({
[`&.${ChipFieldClasses.chip}`]: { margin: 4, cursor: 'inherit' },
[`&.${ChipFieldClasses.chip}`]: { cursor: 'inherit' },
});
104 changes: 79 additions & 25 deletions packages/ra-ui-materialui/src/field/ReferenceArrayField.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,102 @@
import * as React from 'react';
import fakeRestProvider from 'ra-data-fakerest';
import { CardContent } from '@mui/material';
import { ResourceDefinitionContextProvider } from 'ra-core';

import { AdminContext } from '../AdminContext';
import { Datagrid } from '../list';
import { ReferenceArrayField } from './ReferenceArrayField';
import { TextField } from './TextField';
import { Show } from '../detail';
import { CardContent } from '@mui/material';
import { Show, SimpleShowLayout } from '../detail';

export default { title: 'ra-ui-materialui/fields/ReferenceArrayField' };

const fakeData = {
bands: [{ id: 1, name: 'band_1', members: [1, '2', '3'] }],
bands: [{ id: 1, name: 'The Beatles', members: [1, 2, 3, 4] }],
artists: [
{ id: 1, name: 'artist_1' },
{ id: 2, name: 'artist_2' },
{ id: 3, name: 'artist_3' },
{ id: 4, name: 'artist_4' },
{ id: 1, name: 'John Lennon' },
{ id: 2, name: 'Paul McCartney' },
{ id: 3, name: 'Ringo Star' },
{ id: 4, name: 'George Harrison' },
{ id: 5, name: 'Mick Jagger' },
],
};

export default { title: 'ra-ui-materialui/fields/ReferenceArrayField' };

const dataProvider = fakeRestProvider(fakeData, false);

export const DifferentIdTypes = () => {
return (
<AdminContext dataProvider={dataProvider}>
<CardContent>
<Show resource="bands" id={1} sx={{ width: 600 }}>
<TextField source="name" fullWidth />
<ReferenceArrayField
fullWidth
source="members"
reference="artists"
>
const resouceDefs = {
artists: {
name: 'artists',
hasList: true,
hasEdit: true,
hasShow: true,
hasCreate: true,
recordRepresentation: 'name',
},
};
export const Basic = () => (
<AdminContext dataProvider={dataProvider}>
<ResourceDefinitionContextProvider definitions={resouceDefs}>
<Show resource="bands" id={1} sx={{ width: 600 }}>
<SimpleShowLayout>
<TextField source="name" />
<ReferenceArrayField source="members" reference="artists" />
</SimpleShowLayout>
</Show>
</ResourceDefinitionContextProvider>
</AdminContext>
);

export const Children = () => (
<AdminContext dataProvider={dataProvider}>
<ResourceDefinitionContextProvider definitions={resouceDefs}>
<Show resource="bands" id={1} sx={{ width: 600 }}>
<SimpleShowLayout>
<TextField source="name" />
<ReferenceArrayField source="members" reference="artists">
<Datagrid bulkActionButtons={false}>
<TextField source="id" />
<TextField source="name" />
</Datagrid>
</ReferenceArrayField>
</Show>
</CardContent>
</AdminContext>
);
</SimpleShowLayout>
</Show>
</ResourceDefinitionContextProvider>
</AdminContext>
);

const fakeDataWidthDifferentIdTypes = {
bands: [{ id: 1, name: 'band_1', members: [1, '2', '3'] }],
artists: [
{ id: 1, name: 'artist_1' },
{ id: 2, name: 'artist_2' },
{ id: 3, name: 'artist_3' },
{ id: 4, name: 'artist_4' },
],
};
const dataProviderWithDifferentIdTypes = fakeRestProvider(
fakeDataWidthDifferentIdTypes,
false
);

export const DifferentIdTypes = () => (
<AdminContext dataProvider={dataProviderWithDifferentIdTypes}>
<CardContent>
<Show resource="bands" id={1} sx={{ width: 600 }}>
<TextField source="name" fullWidth />
<ReferenceArrayField
fullWidth
source="members"
reference="artists"
>
<Datagrid bulkActionButtons={false}>
<TextField source="id" />
<TextField source="name" />
</Datagrid>
</ReferenceArrayField>
</Show>
</CardContent>
</AdminContext>
);

const dataProviderWithLog = {
...dataProvider,
Expand Down
26 changes: 3 additions & 23 deletions packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@ import {
FilterPayload,
ResourceContextProvider,
useRecordContext,
useResourceDefinition,
RaRecord,
} from 'ra-core';
import { styled } from '@mui/material/styles';
import { SxProps } from '@mui/system';
import { UseQueryOptions } from 'react-query';

import { fieldPropTypes, FieldProps } from './types';
import { LinearProgress } from '../layout';
import { SingleFieldList } from '../list/SingleFieldList';
import { ChipField } from './ChipField';
import { UseQueryOptions } from 'react-query';

/**
* A container component that fetches records from another resource specified
Expand Down Expand Up @@ -152,27 +150,9 @@ export interface ReferenceArrayFieldViewProps
Omit<ListControllerProps, 'queryOptions'> {}

export const ReferenceArrayFieldView: FC<ReferenceArrayFieldViewProps> = props => {
const { children, pagination, reference, className, sx } = props;
const { children, pagination, className, sx } = props;
const { isLoading, total } = useListContext(props);

const { recordRepresentation } = useResourceDefinition({
resource: reference,
});
let child = children ? (
children
) : (
<SingleFieldList>
<ChipField
source={
typeof recordRepresentation === 'string'
? recordRepresentation
: 'id'
}
size="small"
/>
</SingleFieldList>
);

return (
<Root className={className} sx={sx}>
{isLoading ? (
Expand All @@ -181,7 +161,7 @@ export const ReferenceArrayFieldView: FC<ReferenceArrayFieldViewProps> = props =
/>
) : (
<span>
{child}
{children || <SingleFieldList />}
{pagination && total !== undefined ? pagination : null}
</span>
)}
Expand Down
5 changes: 0 additions & 5 deletions packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ export const SimpleList = <RecordType extends RaRecord = any>(
);
}

/**
* Once loaded, the data for the list may be empty. Instead of
* displaying the table header with zero data rows,
* the SimpleList the empty component.
*/
if (data == null || data.length === 0 || total === 0) {
if (empty) {
return empty;
Expand Down
Loading

0 comments on commit 9768ee1

Please sign in to comment.