Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add <SingleFieldList empty gap direction> props, and allow it to be used without children #9439

Merged
merged 4 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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