From 42ea7c76d436390c98df3871383f07aca2c1e70c Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Mon, 5 Jun 2023 13:51:45 +0200 Subject: [PATCH 1/3] [Doc] Add documentation for `` and `` --- docs/Features.md | 1 + docs/Inputs.md | 5 +- docs/Reference.md | 2 + docs/ReferenceInput.md | 31 +++ docs/ReferenceNodeInput.md | 113 +++++++++++ docs/SelectInput.md | 37 ++++ docs/TreeInput.md | 189 ++++++++++++++++++ docs/TreeWithDetails.md | 37 ++++ .../ReferenceNodeInput-TreeInput-basic.mp4 | Bin 0 -> 112530 bytes .../ReferenceNodeInput-TreeInput-basic.webm | Bin 0 -> 135846 bytes .../ReferenceNodeInput-TreeInput-multiple.mp4 | Bin 0 -> 117563 bytes ...ReferenceNodeInput-TreeInput-multiple.webm | Bin 0 -> 117004 bytes docs/navigation.html | 2 + 13 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 docs/ReferenceNodeInput.md create mode 100644 docs/TreeInput.md create mode 100644 docs/img/ReferenceNodeInput-TreeInput-basic.mp4 create mode 100644 docs/img/ReferenceNodeInput-TreeInput-basic.webm create mode 100644 docs/img/ReferenceNodeInput-TreeInput-multiple.mp4 create mode 100644 docs/img/ReferenceNodeInput-TreeInput-multiple.webm diff --git a/docs/Features.md b/docs/Features.md index 57db5de0564..5bb4c68d488 100644 --- a/docs/Features.md +++ b/docs/Features.md @@ -864,6 +864,7 @@ export const CategoriesList = () => ( Check out the following components for displaying hierarchical data: - [``](./TreeWithDetails.md): A list view for tree structures, with a details panel. +- [``](./TreeInput.md): An input component for tree structures. - [``](https://marmelab.com/ra-enterprise/modules/ra-tree#tree-component): A list view for tree structures, with a Material UI skin. ## Application Building Blocks diff --git a/docs/Inputs.md b/docs/Inputs.md index 51dcef8fd98..9b3f3b3ca71 100644 --- a/docs/Inputs.md +++ b/docs/Inputs.md @@ -75,14 +75,13 @@ React-admin provides a set of Input components, each one designed for a specific | Date & time | `'2022-10-24T19:40:28.003Z'` | [``](./DateTimeInput.md) | | Object | `{ foo: 'bar' }` | All inputs (see [ `source`](#source)) | | Enum | `'foo'` | [``](./SelectInput.md), [``](./AutocompleteInput.md), [``](./RadioButtonGroupInput.md) | +| Tree node | `42` | [``](./TreeInput.md) | | Foreign key | `42` | [``](./ReferenceInput.md) | | Array of objects | `[{ item: 'jeans', qty: 3 }, { item: 'shirt', qty: 1 }]` | [``](./ArrayInput.md) | | Array of Enums | `['foo', 'bar']` | [``](./SelectArrayInput.md), [``](./AutocompleteArrayInput.md), [``](./CheckboxGroupInput.md), [``](./DualListInput.md) | | Array of foreign keys | `[42, 43]` | [``](./ReferenceArrayInput.md) | | Translations | `{ en: 'Hello', fr: 'Bonjour' }` | [``](./TranslatableInputs.md) | -| Related records | `[{ id: 42, title: 'Hello' }, { id: 43, title: 'World' }]` | [``](./ReferenceManyInput.md), [``](./ReferenceManyToManyInput.md), [``](./ReferenceOneInput.md) | - - +| Related records | `[{ id: 42, title: 'Hello' }, { id: 43, title: 'World' }]` | [``](./ReferenceManyInput.md), [``](./ReferenceManyToManyInput.md), [``](./ReferenceNodeInput.md), [``](./ReferenceOneInput.md) | ## `className` diff --git a/docs/Reference.md b/docs/Reference.md index 9624bc92bd8..b64a9ce5170 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -135,6 +135,7 @@ title: "Index" * [``](./ReferenceManyInput.md) * [``](./ReferenceManyToManyField.md) * [``](./ReferenceManyToManyInput.md) +* [``](./ReferenceNodeInput.md) * [``](./ReferenceOneField.md) * [``](./ReferenceOneInput.md) * [``](./Resource.md) @@ -181,6 +182,7 @@ title: "Index" * [``](./TranslatableFields.md) * [``](./TranslatableInputs.md) * [``](https://marmelab.com/ra-enterprise/modules/ra-tree#tree-component) +* [``](./TreeInput.md) * [``](./TreeWithDetails.md) * [``](./Toolbar.md) diff --git a/docs/ReferenceInput.md b/docs/ReferenceInput.md index bcc16228b11..29cf7e4c611 100644 --- a/docs/ReferenceInput.md +++ b/docs/ReferenceInput.md @@ -341,6 +341,37 @@ const filterToQuery = searchText => ({ name_ilike: `%${searchText}%` }); ``` +## Tree Structure + +If the reference resource is a tree, use `` instead of ``. + + + +For instance, to edit the category of a product and let the user choose the category in a tree: + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { ReferenceNodeInput } from '@react-admin/ra-tree'; + +const ProductEdit = () => ( + + + + + + + +); +``` + + ## Performance Why does `` use the `dataProvider.getMany()` method with a single value `[id]` instead of `dataProvider.getOne()` to fetch the record for the current value? diff --git a/docs/ReferenceNodeInput.md b/docs/ReferenceNodeInput.md new file mode 100644 index 00000000000..35285e350af --- /dev/null +++ b/docs/ReferenceNodeInput.md @@ -0,0 +1,113 @@ +--- +layout: default +title: "The ReferenceNodeInput Component" +--- + +# `` Component + +This [Enterprise Edition](https://marmelab.com/ra-enterprise) component allows users to select one or several nodes from a tree of a reference resource. For instance, this is useful to select a category for a product. + + + +## Usage + +Use `` in a react-admin form, and set the `reference` and `source` props just like for a ``. + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { ReferenceNodeInput } from '@react-admin/ra-tree'; + +const ProductEdit = () => ( + + + + + + +); +``` + +`` is a controller component, i.e. it fetches the tree from the reference resource, creates a tree choices context, and renders its child component. + +By default `` will render a simple [``](./TreeInput.md) as its child. If you need to customize the `` props, e.g. set the `multiple` prop, you will need to pass the child explicitly: + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { ReferenceNodeInput, TreeInput } from '@react-admin/ra-tree'; + +const ProductEdit = () => ( + + + + + + + + +); +``` + + + +## Props + +| Prop | Required | Type | Default | Description | +| ----------------- | ------------ | ---------------- | --------------- | ----------------------------------------------------------------------------------------- | +| `reference` | Required | string | - | The reference resource | +| `source` | Required | string | - | The name of the source field | +| `children` | Optional | React Element | `` | The child component responsible for rendering the input | +| `meta` | Optional | object | - | An object containing metadata to be passed when calling the dataProvider | + +## `children` + +`` accepts only one child, which is responsible for rendering the input. By default, it renders a simple `` with no props. If you need to pass additional props to ``, you will need to pass them explicitely: + +```tsx + + + +``` + +## `meta` + +Use the `meta` prop to pass metadata to the dataProvider when calling `getTree()`: + +{% raw %} +```tsx + +``` +{% endraw %} + +## `reference` + +Use the `reference` prop to specify the reference resource: + +```tsx + +``` + +## `source` + +Use the `source` prop to specify the name of the source field: + +```tsx + +``` diff --git a/docs/SelectInput.md b/docs/SelectInput.md index 18a5239041b..ffb5c2aea39 100644 --- a/docs/SelectInput.md +++ b/docs/SelectInput.md @@ -712,3 +712,40 @@ const CreateCategory = () => { }; ``` {% endraw %} + +## Tree Structure + +If the choices form a hierarchy or a tree, use the [``](./TreeInput.md) component instead of ``. It renders a collapsible tree structure, and lets users select a value by clicking on a node. + + + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { TreeInput } from '@react-admin/ra-tree'; + +export const ProductEdit = () => ( + + + + + + + +); +``` \ No newline at end of file diff --git a/docs/TreeInput.md b/docs/TreeInput.md new file mode 100644 index 00000000000..d4e53929646 --- /dev/null +++ b/docs/TreeInput.md @@ -0,0 +1,189 @@ +--- +layout: default +title: "The TreeInput Component" +--- + +# `` Component + +This [Enterprise Edition](https://marmelab.com/ra-enterprise) component allows to select one or several nodes from a tree. + + + +## Usage + +Use `` in a react-admin form, and pass the possible choices as the `treeData` prop . It must be an array of nodes with a `children` field. + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { TreeInput } from '@react-admin/ra-tree'; + +export const ProductEdit = () => ( + + + + + + + +); +``` + +**Tip:** You can use the `` component in a [``](./ReferenceNodeInput.md) to automatically fetch the `treeData` from a reference resource. + +`` uses rc-tree's [`` component](https://tree-react-component.vercel.app/#tree-props) under the hood, and accepts all its props. + +## Props + +| Prop | Required | Type | Default | Description | +| ----------------- | ------------ | ---------------- | --------- | ----------------------------------------------------------------------------------------- | +| `source` | Required | string | - | The name of the source field. Required unless when used inside `` | +| `treeData` | Required | array of objects | - | The tree data | +| `multiple` | Optional | boolean | `false` | Set to true to allow selecting multiple nodes | +| `hideRootNodes` | Optional | boolean | `false` | Set to true to hide all root nodes | +| `titleField` | Optional | string | `'title'` | The name of the field holding the node title | + +`` also accepts the [common input props](./Inputs.md#common-input-props) and the [rc-tree](https://tree-react-component.vercel.app/) props. + +## `checkStrictly` + +By default, `` uses the `checkStrictly` prop from rc-tree's [`` component](https://tree-react-component.vercel.app/#tree-props) to allow selecting leaf and parent nodes independently. If you want to disable this feature, you can set the `checkStrictly` prop to `false`: + +```tsx + +``` + +## `hideRootNodes` + +Use the `hideRootNodes` prop to hide all root nodes: + +```tsx + +``` + +## `multiple` + +Use the `multiple` prop to allow selecting multiple nodes. In that case, `` renders a tree with one checkbox per line. + + + + +```tsx +import { SimpleForm } from 'react-admin'; +import { TreeInput } from '@react-admin/ra-tree'; +import treeData from './treeData'; + +export const SimpleTreeForm = () => ( + + + +); +``` + +## `titleField` + +Use the `titleField` prop to specify the name of the field holding the node title: + +```tsx + +``` + +## `treeData` + +The list of possible choices must be passed as the `treeData` prop. It must be an array of nodes with a `children` field. + +```tsx + +``` + +If you need to fetch the `treeData`, you're probably editing a relationship. In that case, you should use the [``](./ReferenceNodeInput.md) component, which fetches the `treeData` from a reference resource on mount . + +## Fetching Choices + +You can use `dataProvider.getTree()` to fetch choices. For example, to fetch a list of categories for a product: + +```tsx +import { useGetTree, TreeInput } from '@react-admin/ra-tree'; + +const CategoryInput = () => { + const { isLoading, data: tree } = useGetTree('categories'); + if (isLoading) return ; + return ( + + ); +}; +``` + +The `isLoading` prop is used to display a loading indicator while the data is being fetched. + +However, most of the time, if you need to populate a `` with choices fetched from another resource, it's because you are trying to set a foreign key. In that case, you should use [``](./ReferenceNodeInput.md) to fetch the choices instead (see next section). + +## Selecting a Foreign Key + +If you use `` to set a foreign key for a many-to-one or a one-to-one relationship, you’ll have to [fetch choices](#fetching-choices), as explained in the previous section. You’ll also have to fetch the record corresponding to the current value of the foreign key, as it may not be in the list of choices. + +As this is a common task, react-admin provides a shortcut to do the same in a declarative way: [``](./ReferenceNodeInput.md): + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { ReferenceNodeInput, TreeInput } from '@react-admin/ra-tree'; + +const ProductEdit = () => ( + + + + + + + + +); +``` + diff --git a/docs/TreeWithDetails.md b/docs/TreeWithDetails.md index 3f31da98f10..bdeef0b6734 100644 --- a/docs/TreeWithDetails.md +++ b/docs/TreeWithDetails.md @@ -69,3 +69,40 @@ const App = () => ( ``` Check [the `ra-tree` documentation](https://marmelab.com/ra-enterprise/modules/ra-tree#treewithdetails-component) for more details. + +## Selecting a Node + +If you need to let users select a node in a tree, use the [``](./TreeInput.md) component. + +```tsx +import { Edit, SimpleForm, TextInput } from 'react-admin'; +import { TreeInput } from '@react-admin/ra-tree'; + +export const ProductEdit = () => ( + + + + + + + +); +``` + + \ No newline at end of file diff --git a/docs/img/ReferenceNodeInput-TreeInput-basic.mp4 b/docs/img/ReferenceNodeInput-TreeInput-basic.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c703fff13247181fbfd5beba7ea783bae3697f76 GIT binary patch literal 112530 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