From bfc13dc8855577926eeb03078da907f4269e4a3d Mon Sep 17 00:00:00 2001 From: Matthieu Chaffotte Date: Mon, 16 Oct 2023 17:01:27 +0200 Subject: [PATCH 1/2] doc: improve StackedFilters documentation --- docs/StackedFilters.md | 543 +++++++++++++++++++++++++++-------------- 1 file changed, 353 insertions(+), 190 deletions(-) diff --git a/docs/StackedFilters.md b/docs/StackedFilters.md index 37d2dd07ff8..e4483b5244c 100644 --- a/docs/StackedFilters.md +++ b/docs/StackedFilters.md @@ -5,27 +5,41 @@ title: "The StackedFilters Component" # `` -This [Enterprise Edition](https://marmelab.com/ra-enterprise) component provides an alternative filter UI for `` pages. It lets users build complex filters by combining a field, an operator, and a value. +This [Enterprise Edition](https://marmelab.com/ra-enterprise) component provides an alternative filter UI for `` pages. It introduces the concept of operators to allow richer filtering. ## Usage -Create a filter configuration object by specifying the operators and UI for each source that can be used as a filter. Then, pass it to the `` component, and pass that component to the `filters` prop of the `` component. - -```jsx -import { CreateButton, Datagrid, List, TextField, NumberField, BooleanField, ReferenceArrayField, TopToolbar } from 'react-admin'; -import { StackedFilters, textFilter, dateFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout'; - -const postListFilters = { - id: textFilter({ operators: ['eq', 'neq'] }), +```tsx +import { + BooleanField, + CreateButton, + Datagrid, + List, + NumberField, + ReferenceArrayField, + TextField, + TopToolbar, +} from 'react-admin'; +import { + StackedFilters, + FiltersConfig, + textFilter, + numberFilter, + referenceFilter, + booleanFilter, +} from '@react-admin/ra-form-layout'; + +const postListFilters: FiltersConfig = { title: textFilter(), - published_at: dateFilter(), - is_public: booleanFilter(), - tags: referenceFilter({ reference: 'tags' }), + views: numberFilter(), + tag_ids: referenceFilter({ reference: 'tags' }), + published: booleanFilter(), }; const PostListToolbar = () => ( @@ -44,87 +58,152 @@ const PostList = () => ( -) +); ``` -The `config` prop accepts an object with the following structure: +## Filters Configuration -```jsx -{ - [source]: { - operators: [operator], - input: ReactElement, +`` and its underlying component, `` needs a filter configuration. This is an object defining the operators and UI for each source that can be used as a filter. + +It looks like this: + +```tsx +import { FiltersConfig } from '@react-admin/ra-form-layout'; +import { NumberInput } from 'react-admin'; +import { MyNumberRangeInput } from './MyNumberRangeInput'; + +const postListFilters: FiltersConfig = { + views: { + operators: [ + { value: 'eq', label: 'Equals' }, + { value: 'neq', label: 'Not Equals' }, + { + value: 'between', + label: 'Between', + input: ({ source }) => , + }, + ], + input: ({ source }) => , }, +}; +``` + +As you can see, the `source` is the `config` object key. It contains an array of `operators` and a default `input`, used for operators that don't define their own. + +An operator is an object that has a `label` and a `value`. The `label` can be a translation key. The `value` will be used as a suffix to the `source` and passed to the list filters. For instance, with the source `views`, the operator `eq` and value set to `0` using the `NumberInput`, the dataProvider will receive the following filter: + +```js +{ + views_eq: 0; } ``` -Check the [built-in filters](#built-in-filters) section for more information about helpers for building filters. +Besides, any operator can provide its own input if it needs. -## Data Provider Integration +## Filter Configuration Builders -Upon user interaction, `` sets the list `filter` with keys concatenating the selected source with the selected operator, separated by an underscore (`_`). In the screencast above, the user selection triggers a call to the `dataProvider` with the following filter: +To make it easier to create a filter configuration, we provide some helper functions. Each of them has predefined operators and inputs. They accept an array of operators if you want to remove some of them. -```jsx -dataProvider.getList('posts', { - filter: { - title_contains: 'volup', - is_public_eq: true, - }, - sort: { field: 'id', order: 'DESC' }, - pagination: { page: 1, perPage: 10 }, -}); -``` +- `textFilter`: A filter for text fields. Defines the following operator: `eq`, `neq` and `q`. +- `numberFilter`: A filter for number fields. Defines the following operator: `eq`, `neq`, `lt` and `gt`. +- `dateFilter`: A filter for date fields. Defines the following operator: `eq`, `neq`, `lt` and `gt`. +- `booleanFilter`: A filter for boolean fields. Defines the following operator: `eq`. +- `choicesFilter`: A filter for fields that accept a value from a list of choices. Defines the following operator: `eq`, `neq`, `eq_any` and `neq_any`. +- `choicesArrayFilter`: A filter for array fields. Defines the following operator: `inc`, `inc_any` and `ninc_any`. +- `referenceFilter`: A filter for reference fields. Defines the following operator: `eq`, `neq`, `eq_any` and `neq_any`. -It's your responsibility to handle these compound keys in your data provider. For instance, to handle the `title_contains` filter, you can use the following code: - -```jsx -// in dataProvider.js -const dataProvider = { - getList: (resource, params) => { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; - let filter = params.filter; - if (resource === 'posts') { - if (filter.title_contains) { - filter = { ...filter, title: { ilike: `%${filter.title_contains}%` } }; - delete filter.title_contains; - } - if (filter.is_public_eq) { - filter = { ...filter, is_public: filter.is_public_eq }; - delete filter.is_public_eq; - } - } - const query = { - sort: JSON.stringify([field, order]), - range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]), - filter: JSON.stringify(params.filter), - }; - const url = `${apiUrl}/${resource}?${stringify(query)}`; - return httpClient(url).then(({ headers, json }) => ({ - data: json, - total: parseInt(headers.get('content-range').split('/').pop(), 10), - })); - }, +Build your filter configuration by calling the helpers for each source: + +```tsx +import { + FiltersConfig, + textFilter, + numberFilter, + referenceFilter, + booleanFilter, +} from '@react-admin/ra-form-layout'; + +const postListFilters: FiltersConfig = { + title: textFilter(), + views: numberFilter(), + tag_ids: referenceFilter({ reference: 'tags' }), + published: booleanFilter(), }; ``` -**Tip**: Build a generic solution to handle compound keys for all resources in your data provider, or add support for compound keys in your API directly. +## Internationalization -## `config` +The source field names are translatable. `ra-form-layout` uses the react-admin [resource and field name translation system](./Translation.md#translation-files). This is an example of an English translation file: -`` requires a filter configuration object defining the possible fields and operators. +```ts +// in i18n/en.js -For convenience, react-admin provides a set of [built-in filters](#built-in-filters) for common field types (text, number, date, reference, etc). +export default { + resources: { + customer: { + name: 'Customer |||| Customers', + fields: { + first_name: 'First name', + last_name: 'Last name', + dob: 'Date of birth', + }, + }, + }, +}; +``` + +`` also supports internationalization for operators. To leverage it, pass a translation key as the operator label: + +```tsx +import { FiltersConfig } from '@react-admin/ra-form-layout'; +import DateRangeInput from './DateRangeInput'; -```jsx -import { StackedFilters, textFilter, dateFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout'; +const MyFilterConfig: FiltersConfig = { + published_at: { + operators: [ + { + value: 'between', + label: 'resources.posts.filters.operators.between', + }, + { + value: 'nbetween', + label: 'resources.posts.filters.operators.nbetween', + }, + ], + input: ({ source }) => , + }, +}; +``` -const postListFilters = { - id: textFilter({ operators: ['eq', 'neq'] }), +## `` + +This component is responsible for showing the Filters button that displays the filtering form inside a MUI Popover. It must be given the [filtering configuration](#filters-configuration) through its `config` prop. + +```tsx +import { + BooleanField, + CreateButton, + Datagrid, + List, + NumberField, + ReferenceArrayField, + TopToolbar, + TextField, +} from 'react-admin'; +import { + StackedFilters, + FiltersConfig, + textFilter, + numberFilter, + referenceFilter, + booleanFilter, +} from '@react-admin/ra-form-layout'; + +const postListFilters: FiltersConfig = { title: textFilter(), - published_at: dateFilter(), - is_public: booleanFilter(), - tags: referenceFilter({ reference: 'tags' }), + views: numberFilter(), + tag_ids: referenceFilter({ reference: 'tags' }), + published: booleanFilter(), }; const PostListToolbar = () => ( @@ -136,177 +215,261 @@ const PostListToolbar = () => ( const PostList = () => ( }> - {/* ... */} + + + + + + ); ``` -You can also define your own filters. Each filter is an object that defines the operators and UI for a given source. For instance, the following filter configuration defines a filter for the `views` field that uses a `NumberInput` for the `eq` and `neq` operators, and a custom `MyNumberRangeInput` for the `between` operator. +### Props -```jsx -import { NumberInput } from 'react-admin'; -import { textFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout'; +| Prop | Required | Type | Default | Description | +| ------------------------- | ------------- | -------- | --------------- | ------------------------------------------------------ | +| `BadgeProps` | Optional | object | - | Additional props to pass to the [MUI Badge](https://mui.com/material-ui/react-badge/) | +| `ButtonProps` | Optional | object | - | Additional props to pass to the [Button](./Buttons.html#button) | +| `className` | Optional | string | - | Additional CSS class applied on the root component | +| `config` | Required (\*) | object | - | The stacked filters configuration | +| `PopoverProps` | Optional | Object | - | Additional props to pass to the [MUI Popover](https://mui.com/material-ui/react-popover/) | +| `StackedFiltersFormProps` | Optional | Object | - | Additional props to pass to the [StackedFiltersForm](#stackedfiltersform) | +| `sx` | Optional | Object | - | An object containing the MUI style overrides to apply to the root component | -import { MyNumberRangeInput } from './MyNumberRangeInput'; +### `BadgeProps` -const postListFilters = { - title: textFilter(), - views: { - operators: [ - { value: 'eq', label: 'Equals', input: ({ source }) => , }, - { value: 'neq', label: 'Not Equals', input: ({ source }) => , }, - { value: 'between', label: 'Between', input: ({ source }) => }, - ], - }, - tag_ids: referenceFilter({ reference: 'tags' }), - published: booleanFilter(), -}; +This prop lets you pass additional props for the [MUI Badge](https://mui.com/material-ui/react-badge/). + +```tsx +import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; + +export const MyStackedFilter = (props: StackedFiltersProps) => ( + +); +``` + +### `ButtonProps` + +This prop lets you pass additional props for the [Button](./Buttons.html#button). + +```tsx +import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; + +export const MyStackedFilter = (props: StackedFiltersProps) => ( + +); ``` -An operator is an object with the shape `{ label, value, input }`. +### `className` -- `label`: Operator input label. Can be a translation key. -- `value`: used as a suffix to the `source` when creating the list filters. -- `input`: a component accepting a `source` prop. React-admin will pass the source of the filter to the input component ('views' in the above example) +This prop lets you pass additional CSS classes to apply to the root element (a `div`). -For instance, with the source `views`, the operator `eq`, and value set to `0`, the dataProvider will receive the following `filter` parameter: +```tsx +import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; -```jsx -{ views_eq: 0 } +export const MyStackedFilter = (props: StackedFiltersProps) => ( + +); ``` -**Tip**: You can define a default input for all operators of a filter by using the `input` property of the filter object: +### `config` + +This prop lets you define the filter configuration, which is required. This is an object defining the operators and UI for each source that can be used as a filter: -```jsx +```tsx +import { FiltersConfig, StackedFilters } from '@react-admin/ra-form-layout'; import { NumberInput } from 'react-admin'; -import { textFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout'; +import { MyNumberRangeInput } from './MyNumberRangeInput'; -const postListFilters = { - title: textFilter(), +const postListFilters: FiltersConfig = { views: { - input: ({ source }) => , operators: [ { value: 'eq', label: 'Equals' }, { value: 'neq', label: 'Not Equals' }, - { value: 'between', label: 'Between', input: ({ source }) => }, + { + value: 'between', + label: 'Between', + input: ({ source }) => , + }, ], + input: ({ source }) => , }, - tag_ids: referenceFilter({ reference: 'tags' }), - published: booleanFilter(), }; + +export const MyStackedFilter = (props: StackedFiltersProps) => ( + +); ``` -## Built-In Filters +### `PopoverProps` -To facilitate the creation of filter configurations, react-admin provides some helpers for common filter types. Each of them has predefined operators and inputs. They accept an array of operators if you want to remove some of them. +This prop lets you pass additional props for the [MUI Popover](https://mui.com/material-ui/react-popover/). -- `textFilter`: A filter for text fields. Defines the following operator: `eq`, `neq`, and `q`. -- `numberFilter`: A filter for number fields. Defines the following operator: `eq`, `neq`, `lt`, and `gt`. -- `dateFilter`: A filter for date fields. Defines the following operator: `eq`, `neq`, `lt`, and `gt`. -- `booleanFilter`: A filter for boolean fields. Defines the following operator: `eq`. -- `choicesFilter`: A filter for fields that accept a value from a list of choices. Defines the following operator: `eq`, `neq`, `eq_any`, and `neq_any`. -- `choicesArrayFilter`: A filter for array fields. Defines the following operator: `inc`, `inc_any`, and `ninc_any`. -- `referenceFilter`: A filter for reference fields. Defines the following operator: `eq`, `neq`, `eq_any`, and `neq_any`. +```tsx +import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; -The operators are defined as follows: +export const MyStackedFilter = (props: StackedFiltersProps) => ( + +); +``` -- `eq`: Equals -- `neq`: Not Equals -- `q`: Full-text search -- `lt`: Less than -- `gt`: Greater than -- `eq_any`: Equals any -- `neq_any`: Not Equals any -- `inc`: Includes -- `inc_any`: Includes any -- `ninc_any`: Not Includes any +### `StackedFiltersFormProps` -Use these built-in helpers to build your filter configuration: +This prop lets you pass additional props for the [StackedFiltersForm](#stackedfiltersform). -```jsx -import { textFilter, numberFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout'; +```tsx +import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; -const postListFilters = { +export const MyStackedFilter = (props: StackedFiltersProps) => ( + +); +``` + +### `sx`: CSS API + +This prop lets you override the styles of the inner components thanks to the `sx` property. This property accepts the following subclasses: + +| Rule name | Description | +| ----------------------------------- | ------------------------------------------------------------------------ | +| `RaStackedFilters` | Applied to the root component | +| `& .RaStackedFilters-popover` | Applied to the [MUI Popover](https://mui.com/material-ui/react-popover/) | +| `& .RaStackedFilters-formContainer` | Applied to the form container (a `div`) | + +## `` + +This component is responsible for handling the filtering form. It must be given the [filtering configuration](#filters-configuration) through its `config` prop. + +If you need to be notified when users have applied filters, pass a function to the `onFiltersApplied` prop. This is useful if you want to close the filters container (``, ``, etc.). + +```tsx +import { + Datagrid, + List, + TextField, + NumberField, + BooleanField, + ReferenceArrayField, +} from 'react-admin'; +import { + StackedFiltersForm, + FiltersConfig, + textFilter, + numberFilter, + referenceFilter, + booleanFilter, +} from '@react-admin/ra-editable-datagrid'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Card, + Typography, +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + +const postListFilters: FiltersConfig = { title: textFilter(), views: numberFilter(), tag_ids: referenceFilter({ reference: 'tags' }), published: booleanFilter(), }; + +const PostList = () => ( + + + } + aria-controls="filters-content" + id="filters-header" + > + Filters + + + + + + + + + + + + + + +); ``` -With the configuration above, the possible filter keys are: - -- `title_eq` -- `title_neq` -- `title_q` -- `views_eq` -- `views_neq` -- `views_lt` -- `views_gt` -- `tag_ids_eq` -- `tag_ids_neq` -- `tag_ids_eq_any` -- `tag_ids_neq_any` -- `published_eq` +### Props -## Internationalization +| Prop | Required | Type | Default | Description | +| ------------------------- | ------------- | -------- | --------------- | ------------------------------------------------------ | +| `className` | Optional | string | - | Additional CSS class applied on the root component | +| `config` | Required (\*) | object | - | The stacked filters configuration | +| `onFiltersApplied` | Optional | Function | - | A function called when users click on the apply button | +| `sx` | Optional | Object | - | An object containing the MUI style overrides to apply to the root component | -React-admin uses the keys of the filter configuration to display the field names. Each key goes through the standard [resource and field name translation system](./Translation.md#translation-files), so you can customize them using a translation file. +### `className` -```jsx -// in i18n/en.js -export default { - resources: { - posts: { - name: 'Post |||| Posts', - fields: { - title: 'Title', - views: '# views', - tag_ids: 'Tags', - published: 'Published', - } - }, - }, -}; +This prop lets you pass additional CSS classes to apply to the root element (a `Form`). + +```tsx +import { StackedFiltersForm, StackedFiltersFormProps } from '@react-admin/ra-form-layout'; + +export const MyStackedFilterForm = (props: StackedFiltersFormProps) => ( + +); ``` -`` also uses internationalization for operators. To leverage it, pass a translation key as the operator label: +### `config` -```jsx -import DateRangeInput from './DateRangeInput'; +This prop lets you define the filter configuration, which is required. This is an object defining the operators and UI for each source that can be used as a filter: -const MyFilterConfig = { - published_at: { +```tsx +import { FiltersConfig, StackedFiltersForm, StackedFiltersFormProps } from '@react-admin/ra-form-layout'; +import { NumberInput } from 'react-admin'; +import { MyNumberRangeInput } from './MyNumberRangeInput'; + +const postListFilters: FiltersConfig = { + views: { operators: [ + { value: 'eq', label: 'Equals' }, + { value: 'neq', label: 'Not Equals' }, { value: 'between', - label: 'resources.posts.filters.operators.between', - }, - { - value: 'nbetween', - label: 'resources.posts.filters.operators.nbetween', + label: 'Between', + input: ({ source }) => , }, ], - input: ({ source }) => , + input: ({ source }) => , }, }; + +export const MyStackedFiltersForm = (props: StackedFiltersFormProps) => ( + +); ``` -Then translate the operators in the translation messages: +### `onFiltersApplied` -```jsx -// in i18n/en.js -export default { - resources: { - posts: { - name: 'Post |||| Posts', - filters: { - operators: { - between: 'Between', - nbetween: 'Not Between', - }, - }, - }, - }, -}; +This prop lets you provide a function that will be called when users click the apply button: + +```tsx +import { FiltersConfig, StackedFiltersForm } from '@react-admin/ra-form-layout'; + +export const MyStackedFiltersForm = (props: StackedFiltersProps) => ( + alert('Filters applied')} /> +); ``` + +### `sx`: CSS API + +This prop lets you override the styles of the inner components thanks to the `sx` property. This property accepts the following subclasses: + +| Rule name | Description | +| --------------------------------------- | -------------------------------------------------------------------- | +| `RaStackedFiltersForm` | Applied to the root component | +| `& .RaStackedFiltersForm-sourceInput` | Applied to the [AutocompleteInput](./AutocompleteInput.md) that allows users to select the field | +| `& .RaStackedFiltersForm-operatorInput` | Applied to the [SelectInput](./SelectInput.md) that allows users to select the field | +| `& .RaStackedFiltersForm-valueInput` | Applied to the input that allows users to set the filter value | From 90b44fcbd6b407150ad37479a5d1915e13369728 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Tue, 17 Oct 2023 18:09:03 +0200 Subject: [PATCH 2/2] [no ci] add missing raw/endraw --- docs/StackedFilters.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/StackedFilters.md b/docs/StackedFilters.md index e4483b5244c..8e69b54ca49 100644 --- a/docs/StackedFilters.md +++ b/docs/StackedFilters.md @@ -241,6 +241,8 @@ const PostList = () => ( This prop lets you pass additional props for the [MUI Badge](https://mui.com/material-ui/react-badge/). +{% raw %} + ```tsx import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; @@ -249,10 +251,14 @@ export const MyStackedFilter = (props: StackedFiltersProps) => ( ); ``` +{% endraw %} + ### `ButtonProps` This prop lets you pass additional props for the [Button](./Buttons.html#button). +{% raw %} + ```tsx import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; @@ -261,6 +267,8 @@ export const MyStackedFilter = (props: StackedFiltersProps) => ( ); ``` +{% endraw %} + ### `className` This prop lets you pass additional CSS classes to apply to the root element (a `div`). @@ -306,6 +314,8 @@ export const MyStackedFilter = (props: StackedFiltersProps) => ( This prop lets you pass additional props for the [MUI Popover](https://mui.com/material-ui/react-popover/). +{% raw %} + ```tsx import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; @@ -314,10 +324,14 @@ export const MyStackedFilter = (props: StackedFiltersProps) => ( ); ``` +{% endraw %} + ### `StackedFiltersFormProps` This prop lets you pass additional props for the [StackedFiltersForm](#stackedfiltersform). +{% raw %} + ```tsx import { StackedFilters, StackedFiltersProps } from '@react-admin/ra-form-layout'; @@ -326,6 +340,8 @@ export const MyStackedFilter = (props: StackedFiltersProps) => ( ); ``` +{% endraw %} + ### `sx`: CSS API This prop lets you override the styles of the inner components thanks to the `sx` property. This property accepts the following subclasses: @@ -342,6 +358,8 @@ This component is responsible for handling the filtering form. It must be given If you need to be notified when users have applied filters, pass a function to the `onFiltersApplied` prop. This is useful if you want to close the filters container (``, ``, etc.). +{% raw %} + ```tsx import { Datagrid, @@ -401,6 +419,8 @@ const PostList = () => ( ); ``` +{% endraw %} + ### Props | Prop | Required | Type | Default | Description |