From 05e9348afde017b599805f64b2ba76d1df8f9364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Tue, 4 Apr 2023 09:04:23 +0200 Subject: [PATCH 1/4] Add ability to change the sort, filter and selection of ArrayField Closes #8761 --- .../src/field/ArrayField.stories.tsx | 71 +++++++++++++++++++ .../ra-ui-materialui/src/field/ArrayField.tsx | 36 ++-------- 2 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 packages/ra-ui-materialui/src/field/ArrayField.stories.tsx diff --git a/packages/ra-ui-materialui/src/field/ArrayField.stories.tsx b/packages/ra-ui-materialui/src/field/ArrayField.stories.tsx new file mode 100644 index 00000000000..63bbecceb22 --- /dev/null +++ b/packages/ra-ui-materialui/src/field/ArrayField.stories.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { MemoryRouter } from 'react-router'; +import { useListContext, useRecordContext } from 'ra-core'; + +import { ArrayField } from './ArrayField'; +import { SingleFieldList } from '../list'; +import { ChipField } from './ChipField'; + +export default { title: 'ra-ui-materialui/fields/ArrayField' }; + +let books = [ + { id: 1, title: 'War and Peace', author_id: 1 }, + { id: 2, title: 'Les Misérables', author_id: 2 }, + { id: 3, title: 'Anna Karenina', author_id: 1 }, + { id: 4, title: 'The Count of Monte Cristo', author_id: 3 }, + { id: 5, title: 'Resurrection', author_id: 1 }, +]; + +export const Basic = () => ( + + + + + + + +); + +const SortButton = () => { + const { setSort } = useListContext(); + return ( + + ); +}; + +export const FilterButton = () => { + const { setFilters } = useListContext(); + return ( + + ); +}; + +export const SelectedChip = () => { + const { selectedIds, onToggleItem } = useListContext(); + const record = useRecordContext(); + return ( + { + onToggleItem(record.id); + }} + color={selectedIds.includes(record.id) ? 'primary' : 'default'} + /> + ); +}; + +export const ListContext = () => ( + + + + + + + + +); diff --git a/packages/ra-ui-materialui/src/field/ArrayField.tsx b/packages/ra-ui-materialui/src/field/ArrayField.tsx index a1a83dcf589..168e882771e 100644 --- a/packages/ra-ui-materialui/src/field/ArrayField.tsx +++ b/packages/ra-ui-materialui/src/field/ArrayField.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { memo, FC, ReactElement } from 'react'; +import { memo, FC, ReactNode } from 'react'; import get from 'lodash/get'; -import { ListContextProvider, useRecordContext } from 'ra-core'; +import { ListContextProvider, useRecordContext, useList } from 'ra-core'; import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; @@ -76,35 +76,9 @@ export const ArrayField: FC = memo(props => { const { children, resource, source } = props; const record = useRecordContext(props); const data = get(record, source, emptyArray) || emptyArray; - + const listContext = useList({ data, resource }); return ( - + {children} ); @@ -115,7 +89,7 @@ ArrayField.propTypes = { }; export interface ArrayFieldProps extends PublicFieldProps, InjectedFieldProps { - children: ReactElement; + children: ReactNode; } ArrayField.displayName = 'ArrayField'; From 6d57fc5661d444203ae4d590955ff59df3972819 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Tue, 4 Apr 2023 23:23:47 +0200 Subject: [PATCH 2/4] Add documentation and expand list manipulartion capabilities --- docs/ArrayField.md | 191 ++++++++++++++++-- docs/img/array-field.webp | Bin 0 -> 13860 bytes docs/useList.md | 2 + examples/simple/src/posts/PostList.tsx | 2 +- examples/simple/src/posts/PostShow.tsx | 5 +- .../src/field/ArrayField.spec.tsx | 26 ++- .../src/field/ArrayField.stories.tsx | 99 ++++++++- .../ra-ui-materialui/src/field/ArrayField.tsx | 20 +- .../src/list/SingleFieldList.tsx | 2 - 9 files changed, 318 insertions(+), 29 deletions(-) create mode 100644 docs/img/array-field.webp diff --git a/docs/ArrayField.md b/docs/ArrayField.md index 1d6cf4ee594..c6ad191396d 100644 --- a/docs/ArrayField.md +++ b/docs/ArrayField.md @@ -5,17 +5,21 @@ title: "The ArrayField Component" # `` -Display a collection using `` child components. +`` renders an embedded array of objects. -Ideal for embedded arrays of objects, e.g. `tags` and `backlinks` in the following `post` object: +![ArrayField](./img/array-field.webp) + +`` creates a [`ListContext`](./useListContext.md) with the field value, and renders its children components - usually iterator components like [``](./Datagrid.md) or [``](./SingleFieldList.md). + +## Usage + +`` is ideal for collections of objects, e.g. `tags` and `backlinks` in the following `post` object: ```js { id: 123, - tags: [ - { name: 'foo' }, - { name: 'bar' } - ], + title: 'Lorem Ipsum Sit Amet', + tags: [{ name: 'dolor' }, { name: 'sit' }, { name: 'amet' }], backlinks: [ { uuid: '34fdf393-f449-4b04-a423-38ad02ae159e', @@ -31,34 +35,191 @@ Ideal for embedded arrays of objects, e.g. `tags` and `backlinks` in the followi } ``` -The child must be an iterator component (like `` or ``). +Leverage `` e.g. in a Show view, to display the `tags` as a `` and the `backlinks` as a ``: + +```jsx +import { + ArrayField, + ChipField, + Datagrid, + Show, + SimpleShowLayout, + SingleFieldList, + TextField +} from 'react-admin'; + +const PostShow = () => ( + + + + + + + + + + + + + + + + + +) +``` + +## Props + +| Prop | Required | Type | Default | Description | +|------------|----------|-------------------|---------|------------------------------------------| +| `children` | Required | `ReactNode` | | The component to render the list. | +| `filter` | Optional | `object` | | The filter to apply to the list. | +| `perPage` | Optional | `number` | 1000 | The number of items to display per page. | +| `sort` | Optional | `{ field, order}` | | The sort to apply to the list. | + +`` accepts the [common field props](./Fields.md#common-field-props), except `emptyText` (use the child `empty` prop instead). + +`` relies on [`useList`](./useList.md) to filter, paginate, and sort the data, so it accepts the same props. + +## `children` -Here is how to display all the backlinks of the current post as a ``: +`` renders its `children` component wrapped in a [``](./useListContext.md). Commonly used child components are [``](./Datagrid.md), [``](./SingleFieldList.md), and [``](./SimpleList.md). ```jsx +{/* using SingleFieldList as child */} + + + + + + +{/* using Datagrid as child */} - - + + + + +{/* using SimpleList as child */} + + record.url} + secondaryText={record => record.date} + /> + ``` -And here is how to display all the tags of the current post as `` components: +## `filter` + +You can use the `filter` prop to display only a subset of the items in the array. For instance, to display only the backlinks for a particular day: +{% raw %} ```jsx - + + + + + + + +``` +{% endraw %} + +The filtering capabilities are very limited. For instance, there is no "greater than" or "less than" operator. You can only filter on the equality of a field. + +## `perPage` + +If the value is a large array, and you don't need to display all the items, you can use the `perPage` prop to limit the number of items displayed. + +As `` creates a [`ListContext`](./useListContext.md), you can use the `` component to navigate through the items. + +```jsx +import { + ArrayField, + Datagrid, + Pagination, + Show, + SimpleShowLayout, + TextField +} from 'react-admin'; + +const PostShow = () => ( + + + + + + + + + + + + + +); +``` + +## `sort` + +By default, `` displays the items in the order they are stored in the field. You can use the `sort` prop to change the sort order. + +{% raw %} +```jsx + ``` +{% endraw %} -## Properties +## Using The List Context -`` accepts the [common field props](./Fields.md#common-field-props), except `emptyText` (use the child `empty` prop instead). +`` creates a [`ListContext`](./useListContext.md) with the field value, so you can use any of the list context values in its children. This includes callbacks to sort, filter, and select items. + +For instance, you can make the chips selectable as follows: + +```jsx +const SelectedChip = () => { + const { selectedIds, onToggleItem } = useListContext(); + const record = useRecordContext(); + return ( + { + onToggleItem(record.id); + }} + color={selectedIds.includes(record.id) ? 'primary' : 'default'} + /> + ); +}; + +const PostShow = () => ( + + + + + + + + + + +) +``` + +**Tip**: The selection logic uses the `id` field for each collection element, so the above example assumes that the `tags` field contains objects like `{ id: 123, name: 'bar' }`. + +Check [the `useListContext` documentation](./useListContext.md) for more information on the list context values. + +## Rendering An Array Of Strings -**Tip**: If you need to render a custom collection, it's often simpler to write your own component: +If you need to render a custom collection (e.g. an array of tags `['dolor', 'sit', 'amet']`), it's often simpler to write your own component: ```jsx import { useRecordContext } from 'react-admin'; diff --git a/docs/img/array-field.webp b/docs/img/array-field.webp new file mode 100644 index 0000000000000000000000000000000000000000..ddeb1228717365e8634bc19cc927d1d08a9ccf36 GIT binary patch literal 13860 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 literal 0 HcmV?d00001 diff --git a/docs/useList.md b/docs/useList.md index 986c53e2cdd..64b8fc7d665 100644 --- a/docs/useList.md +++ b/docs/useList.md @@ -103,6 +103,8 @@ const { data, total } = useList({ // data will be [{ id: 1, name: 'Arnold' }] and total will be 1 ``` +The filtering capabilities are very limited. For instance, there is no "greater than" or "less than" operator. You can only filter on the equality of a field. + ## `filterCallback` Property for custom filter definition. Lets you apply local filters to the fetched data. diff --git a/examples/simple/src/posts/PostList.tsx b/examples/simple/src/posts/PostList.tsx index 22e7f7db6fa..1e2a7072d59 100644 --- a/examples/simple/src/posts/PostList.tsx +++ b/examples/simple/src/posts/PostList.tsx @@ -163,7 +163,7 @@ const PostList = () => { cellClassName="hiddenOnSmallScreens" headerClassName="hiddenOnSmallScreens" > - + diff --git a/examples/simple/src/posts/PostShow.tsx b/examples/simple/src/posts/PostShow.tsx index 252615a0ce9..5691e93e489 100644 --- a/examples/simple/src/posts/PostShow.tsx +++ b/examples/simple/src/posts/PostShow.tsx @@ -72,7 +72,10 @@ const PostShow = () => { sort={{ field: `name.${locale}`, order: 'ASC' }} > - + diff --git a/packages/ra-ui-materialui/src/field/ArrayField.spec.tsx b/packages/ra-ui-materialui/src/field/ArrayField.spec.tsx index 06f26b53d0c..6ad0dab05e5 100644 --- a/packages/ra-ui-materialui/src/field/ArrayField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/ArrayField.spec.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { CoreAdminContext, ResourceContextProvider, @@ -12,6 +12,7 @@ import { NumberField } from './NumberField'; import { TextField } from './TextField'; import { Datagrid } from '../list'; import { SimpleList } from '../list'; +import { ListContext } from './ArrayField.stories'; describe('', () => { const sort = { field: 'id', order: 'ASC' }; @@ -130,4 +131,27 @@ describe('', () => { expect(queryByText('baz')).not.toBeNull(); expect(queryByText('456')).not.toBeNull(); }); + + it('should create a ListContext with working callbacks', async () => { + render(); + screen.getByText('War and Peace'); + screen.getByText('Filter by title').click(); + await waitFor(() => { + expect(screen.queryByText('War and Peace')).toBeNull(); + }); + const chip = screen.getByText('Resurrection'); + expect( + (chip.parentNode as HTMLElement).className.includes( + 'MuiChip-colorDefault' + ) + ).toBeTruthy(); + chip.click(); + await waitFor(() => { + expect( + (chip.parentNode as HTMLElement).className.includes( + 'MuiChip-colorPrimary' + ) + ).toBeTruthy(); + }); + }); }); diff --git a/packages/ra-ui-materialui/src/field/ArrayField.stories.tsx b/packages/ra-ui-materialui/src/field/ArrayField.stories.tsx index 63bbecceb22..93dd81796ed 100644 --- a/packages/ra-ui-materialui/src/field/ArrayField.stories.tsx +++ b/packages/ra-ui-materialui/src/field/ArrayField.stories.tsx @@ -1,10 +1,18 @@ import * as React from 'react'; import { MemoryRouter } from 'react-router'; -import { useListContext, useRecordContext } from 'ra-core'; +import { + RecordContextProvider, + useListContext, + useRecordContext, +} from 'ra-core'; +import { Card, ThemeProvider, createTheme } from '@mui/material'; import { ArrayField } from './ArrayField'; -import { SingleFieldList } from '../list'; +import { Datagrid, SingleFieldList } from '../list'; import { ChipField } from './ChipField'; +import { SimpleShowLayout } from '../detail'; +import { TextField } from './TextField'; +import { Pagination } from '../list/pagination'; export default { title: 'ra-ui-materialui/fields/ArrayField' }; @@ -26,6 +34,47 @@ export const Basic = () => ( ); +export const PerPage = () => ( + + + + + + + + + + +); + +export const Sort = () => ( + + + + + + + +); + +export const Filter = () => ( + + + + + + + +); + const SortButton = () => { const { setSort } = useListContext(); return ( @@ -35,7 +84,7 @@ const SortButton = () => { ); }; -export const FilterButton = () => { +const FilterButton = () => { const { setFilters } = useListContext(); return (