diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c02e99fbf..4f60860a9a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,59 @@ # Changelog +## v3.11.1 + +* Fix select empty option in `` does not reset the input ([5698](https://github.com/marmelab/react-admin/pull/5698)) ([AnkitaGupta111](https://github.com/AnkitaGupta111)) +* Fix `` list component does not display when the `Resource` has no `create` component ([5688](https://github.com/marmelab/react-admin/pull/5688)) ([djhi](https://github.com/djhi)) +* Fix `` doesn't take permanent `filter` into account ([5675](https://github.com/marmelab/react-admin/pull/5675)) ([fzaninotto](https://github.com/fzaninotto)) +* Fix `` dialog shows a scroll bar on mobile ([5674](https://github.com/marmelab/react-admin/pull/5674)) ([rkfg](https://github.com/rkfg)) +* Fix `` and `` performance by showing loader only after a delay ([5668](https://github.com/marmelab/react-admin/pull/5668)) ([djhi](https://github.com/djhi)) +* [Doc] Fix link to react-final-form `Field` documentation in CreateEdit chapter ([5689](https://github.com/marmelab/react-admin/pull/5689)) ([WiXSL](https://github.com/WiXSL)) +* [Doc] Fix outdated Hasura Data Provider reference ([5686](https://github.com/marmelab/react-admin/pull/5686)) ([djhi](https://github.com/djhi)) +* [Doc] Fix syntax in actions example for `useUpdate` ([5681](https://github.com/marmelab/react-admin/pull/5681)) ([abdenny](https://github.com/abdenny)) +* [Doc] Fix custom theme doc doesn't explain how to override default theme ([5676](https://github.com/marmelab/react-admin/pull/5676)) ([fzaninotto](https://github.com/fzaninotto)) +* [Doc] Fix typos in Tutorial doc ([5669](https://github.com/marmelab/react-admin/pull/5669)) ([paulo9mv](https://github.com/paulo9mv)) + +## v3.11.0 + +Starting with this version, react-admin applications send an anonymous request on mount to a telemetry server operated by marmelab. You can see this request by looking at the Network tab of your browser DevTools: + +`https://react-admin-telemetry.marmelab.com/react-admin-telemetry` + +The only data sent to the telemetry server is the admin domain (e.g. "example.com") - no personal data is ever sent, and no cookie is included in the response. The react-admin team uses these domains to track the usage of the framework. + +You can opt out of telemetry by simply adding `disableTelemetry` to the `` component: + +```jsx +// in src/App.js +import * as React from "react"; +import { Admin } from 'react-admin'; + +const App = () => ( + + // ... + +); +``` + +* Add domain telemetry on app mount ([5631](https://github.com/marmelab/react-admin/pull/5631)) ([djhi](https://github.com/djhi)) +* Add ability to access (and override) side effects in `SaveContext` ([5604](https://github.com/marmelab/react-admin/pull/5604)) ([djhi](https://github.com/djhi)) +* Add support for `disabled` in `` ([5618](https://github.com/marmelab/react-admin/pull/5618)) ([fzaninotto](https://github.com/fzaninotto)) +* Add ability to customize the notification element in the `` page ([5630](https://github.com/marmelab/react-admin/pull/5630)) ([hieusmiths](https://github.com/hieusmiths)) +* Disable ripple effect on Buttons for improved performance ([5598](https://github.com/marmelab/react-admin/pull/5598)) ([fzaninotto](https://github.com/fzaninotto)) +* Fix `` doesn't contain `notifications` node ([5659](https://github.com/marmelab/react-admin/pull/5659)) ([fzaninotto](https://github.com/fzaninotto)) +* Fix `` fails to show compound filters with no default value ([5657](https://github.com/marmelab/react-admin/pull/5657)) ([fzaninotto](https://github.com/fzaninotto)) +* Fix "Missing translation" console error when the `dataProvider` fails ([5655](https://github.com/marmelab/react-admin/pull/5655)) ([fzaninotto](https://github.com/fzaninotto)) +* Fix `` doesn't appear selected when more than one filter is applied ([5644](https://github.com/marmelab/react-admin/pull/5644)) ([fzaninotto](https://github.com/fzaninotto)) +* Fix `usePermissions` always triggers a re-render even though the permissions are unchanged ([5607](https://github.com/marmelab/react-admin/pull/5607)) ([fzaninotto](https://github.com/fzaninotto)) +* [Doc] Add `rowStyle` example usage to `` jsDoc ([5661](https://github.com/marmelab/react-admin/pull/5661)) ([vdimitroff](https://github.com/vdimitroff)) +* [Doc] Fix `` prop type to show that it accepts a function ([5660](https://github.com/marmelab/react-admin/pull/5660)) ([vdimitroff](https://github.com/vdimitroff)) +* [Doc] Fix missing import in `List` example ([5658](https://github.com/marmelab/react-admin/pull/5658)) ([WiXSL](https://github.com/WiXSL)) +* [Doc] Fix syntax error in `` prop usage ([5649](https://github.com/marmelab/react-admin/pull/5649)) ([WiXSL](https://github.com/WiXSL)) +* [Doc] Fix Sidebar size change resets the theme color ([5646](https://github.com/marmelab/react-admin/pull/5646)) ([zheya08](https://github.com/zheya08)) +* [Doc] Fix `` and `` JSDocs point to the wrong `dataProvider` method ([5645](https://github.com/marmelab/react-admin/pull/5645)) ([WiXSL](https://github.com/WiXSL)) +* [Doc] Add mention of saved queries in List chapter ([5638](https://github.com/marmelab/react-admin/pull/5638)) ([fzaninotto](https://github.com/fzaninotto)) +* [Doc] Fix `` prop injection documentation misses package version constraint ([5538](https://github.com/marmelab/react-admin/pull/5538)) ([fzaninotto](https://github.com/fzaninotto)) + ## v3.10.4 * Fix `ra-data-simple-rest` delete method fails because of bad header ([5628](https://github.com/marmelab/react-admin/pull/5628)) ([fzaninotto](https://github.com/fzaninotto)) @@ -1973,7 +2027,7 @@ This new release is not backwards compatible with 1.x. Please refer to [the Upgr * Fix date filters ([djhi](https://github.com/djhi)) * Fix typo in custom actions documentation ([RWOverdijk](https://github.com/RWOverdijk)) -## v.1.3.0 +## v1.3.0 * Add permissions handling ([djhi](https://github.com/djhi)) * Add Not Found page ([fzaninotto](https://github.com/fzaninotto)) diff --git a/docs/Actions.md b/docs/Actions.md index bd2e144bb7f..d35410dda31 100644 --- a/docs/Actions.md +++ b/docs/Actions.md @@ -528,6 +528,7 @@ const ApproveButton = ({ record }) => { 'comments', record.id, { isApproved: true }, + record, { undoable: true, onSuccess: ({ data }) => { diff --git a/docs/CreateEdit.md b/docs/CreateEdit.md index 230eb7aab69..0fc9797f500 100644 --- a/docs/CreateEdit.md +++ b/docs/CreateEdit.md @@ -1092,37 +1092,7 @@ export const UserCreate = (props) => ( **Tip**: The props you pass to `` and `` are passed to the [
](https://final-form.org/docs/react-final-form/api/Form) of `react-final-form`. -**Tip**: The `validate` function can return a promise for asynchronous validation. For instance: - -```jsx -const validateUserCreation = async (values) => { - const errors = {}; - if (!values.firstName) { - errors.firstName = ['The firstName is required']; - } - if (!values.age) { - errors.age = ['The age is required']; - } else if (values.age < 18) { - errors.age = ['Must be over 18']; - } - - const isEmailUnique = await checkEmailIsUnique(values.userName); - if (!isEmailUnique) { - errors.email = ['Email already used']; - } - return errors -}; - -export const UserCreate = (props) => ( - - - - - - - -); -``` +**Tip**: The `validate` function can return a promise for asynchronous validation. See [the Server-Side Validation section](#server-side-validation) below. ### Per Input Validation: Built-in Field Validators @@ -1285,11 +1255,47 @@ export const ProductEdit = ({ ...props }) => ( ``` {% endraw %} -**Tip**: The props of your Input components are passed to a `react-final-form` [](https://final-form.org/docs/react-final-form/api/Field) component. +**Tip**: The props of your Input components are passed to a `react-final-form` [Field](https://final-form.org/docs/react-final-form/api/Field) component. **Tip**: You can use *both* Form validation and input validation. -**Tip**: The custom validator function can return a promise for asynchronous validation. For instance: +**Tip**: The custom validator function can return a promise, e.g. to use server-side validation. See next section for details. + +### Server-Side Validation + +You can validate the entire form data server-side by returning a Promise in the form `validate` function. For instance: + +```jsx +const validateUserCreation = async (values) => { + const errors = {}; + if (!values.firstName) { + errors.firstName = ['The firstName is required']; + } + if (!values.age) { + errors.age = ['The age is required']; + } else if (values.age < 18) { + errors.age = ['Must be over 18']; + } + + const isEmailUnique = await checkEmailIsUnique(values.userName); + if (!isEmailUnique) { + errors.email = ['Email already used']; + } + return errors +}; + +export const UserCreate = (props) => ( + + + + + + + +); +``` + +Per Input validators can also return a Promise to call the server for validation. For instance: ```jsx const validateEmailUnicity = async (value) => { @@ -1321,7 +1327,8 @@ export const UserCreate = (props) => ( ); ``` -**Important**: Note that asynchronous validators are not supported on the `ArrayInput` component due to a limitation of [react-final-form-arrays](https://github.com/final-form/react-final-form-arrays). +**Important**: Note that asynchronous validators are not supported on the `` component due to a limitation of [react-final-form-arrays](https://github.com/final-form/react-final-form-arrays). + ## Submit On Enter By default, pressing `ENTER` in any of the form fields submits the form - this is the expected behavior in most cases. However, some of your custom input components (e.g. Google Maps widget) may have special handlers for the `ENTER` key. In that case, to disable the automated form submission on enter, set the `submitOnEnter` prop of the form component to `false`: diff --git a/docs/DataProviders.md b/docs/DataProviders.md index 10f1797ba83..7a2598a213c 100644 --- a/docs/DataProviders.md +++ b/docs/DataProviders.md @@ -77,7 +77,8 @@ Developers from the react-admin community have open-sourced Data Providers for m * **[Firebase Realtime Database](https://firebase.google.com/docs/database)**: [aymendhaya/ra-data-firebase-client](https://github.com/aymendhaya/ra-data-firebase-client). * **[GraphQL](https://graphql.org/)**: [marmelab/ra-data-graphql](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-graphql) (uses [Apollo](https://www.apollodata.com/)) * **[HAL](http://stateless.co/hal_specification.html)**: [b-social/ra-data-hal](https://github.com/b-social/ra-data-hal) -* **[Hasura](https://github.com/hasura/graphql-engine)**: [hasura/ra-data-hasura](https://github.com/hasura/graphql-engine/tree/master/community/tools/ra-data-hasura) +* **[Hasura V1](https://github.com/hasura/graphql-engine)**: [hasura/ra-data-hasura](https://github.com/hasura/ra-data-hasura), communicates with Hasura V1, using standard REST and not GraphQL +* **[Hasura](https://github.com/hasura/graphql-engine)**: [Steams/ra-data-hasura-graphql](https://github.com/Steams/ra-data-hasura-graphql), auto generates valid GraphQL queries based on the properties exposed by the Hasura API. * **[Hydra](https://www.hydra-cg.com/) / [JSON-LD](https://json-ld.org/)**: [api-platform/admin/hydra](https://github.com/api-platform/admin/blob/master/src/hydra/dataProvider.js) * **[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)**: [tykoth/ra-data-dexie](https://github.com/tykoth/ra-data-dexie) * **[JSON API](https://jsonapi.org/)**: [henvo/ra-jsonapi-client](https://github.com/henvo/ra-jsonapi-client) diff --git a/docs/List.md b/docs/List.md index 339611d48e4..bf4590d120b 100644 --- a/docs/List.md +++ b/docs/List.md @@ -1392,6 +1392,20 @@ export const PostList = (props) => ( You can use a similar approach to customize the list filter completely, e.g. to display the filters in a sidebar, or as a line in the datagrid, etc. +### Global Search + +Although list filters allow to make precise queries using per-field criteria, users often prefer simpler interfaces like full-text search. After all, that's what they use every day on search engines, email clients, and in their file explorer. + +If you want to display a full-text search allowing to look for any record in the admin using a single form input, check out [ra-search](https://marmelab.com/ra-enterprise/modules/ra-search), an [Enterprise Edition](https://marmelab.com/ra-enterprise) module. + +![ra-search basic](https://marmelab.com/ra-enterprise/modules/assets/ra-search-overview.gif) + +`ra-search` can plug to any existing search engine (ElasticSearch, Lucene, or custom search engine), and lets you customize the search results to provide quick navigation to related items, turniun the search engine into an "Omnibox": + +![ra-search demo](https://marmelab.com/ra-enterprise/modules/assets/ra-search-demo.gif) + +For mode details about the global search, check the [`ra-search` module](https://marmelab.com/ra-enterprise/modules/ra-search) in React-Admin Enterprise Edition. + ## Sorting The List @@ -1898,7 +1912,7 @@ You can find many usage examples of `useListContext` in this page, including: - [Building an Aside Component](#aside-aside-component) - [Building a Custom Empty Page](#empty-empty-page-component) - [Building a Custom Filter](#building-a-custom-filter) -- [Building a Custom Sort Control](##building-a-custom-sort-control) +- [Building a Custom Sort Control](#building-a-custom-sort-control) - [Building a Custom Pagination Control](#building-a-custom-pagination-control) - [Building a Custom Iterator](#using-a-custom-iterator) diff --git a/docs/Reference.md b/docs/Reference.md index df8e056779f..be825a92f9e 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -106,6 +106,7 @@ title: "Reference" * [``](https://marmelab.com/ra-enterprise/modules/ra-editable-datagrid#rowform) * `` * [``](https://marmelab.com/ra-enterprise/modules/ra-preferences#savedquerieslist-and-filterwithsave-store-user-queries-in-preferences) +* [``](https://marmelab.com/ra-enterprise/modules/ra-search#the-search-component) * `` * [``](./Inputs.md#selectarrayinput) * [``](https://marmelab.com/ra-enterprise/modules/ra-preferences#selectcolumnsbutton-store-datagrid-columns-in-preferences) @@ -189,6 +190,7 @@ title: "Reference" * `useReferenceInputController` * `useReferenceManyFieldController` * [`useRefresh`](./Actions.md#handling-side-effects-in-usedataprovider) +* [`useSearch`](https://marmelab.com/ra-enterprise/modules/ra-search#the-usesearch-hook) * [`useSetLocale`](./Translation.md#usesetlocale-changing-locale-at-runtime) * [`useShowController`](./Show.md#useshowcontroller) * `useSortState` diff --git a/docs/Theming.md b/docs/Theming.md index f98749bce94..d81f59c70b5 100644 --- a/docs/Theming.md +++ b/docs/Theming.md @@ -310,15 +310,18 @@ const App = () => ( ## Writing a Custom Theme -If you need more fine-tuning, you'll need to write your own `theme` object, following [Material UI themes documentation](https://material-ui.com/customization/themes/). Material UI merges custom theme objects with the default theme. +If you need more fine-tuning, you'll need to write your own `theme` object, following [Material UI themes documentation](https://material-ui.com/customization/themes/). + +For instance, here is how to override the default react-admin theme: ```jsx -import { createMuiTheme } from '@material-ui/core/styles'; +import { defaultTheme } from 'react-admin'; +import merge from 'lodash/merge'; import indigo from '@material-ui/core/colors/indigo'; import pink from '@material-ui/core/colors/pink'; import red from '@material-ui/core/colors/red'; -const myTheme = createMuiTheme({ +const myTheme = merge({}, defaultTheme, { palette: { primary: indigo, secondary: pink, @@ -328,13 +331,7 @@ const myTheme = createMuiTheme({ }, typography: { // Use the system font instead of the default Roboto font. - fontFamily: [ - '-apple-system', - 'BlinkMacSystemFont', - '"Segoe UI"', - 'Arial', - 'sans-serif', - ].join(','), + fontFamily: ['-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Arial', 'sans-serif'].join(','), }, overrides: { MuiButton: { // override the styles of all instances of this component @@ -346,7 +343,7 @@ const myTheme = createMuiTheme({ }); ``` -The `myTheme` object contains the following keys: +A `theme` object can contain the following keys: * `breakpoints` * `direction` @@ -355,9 +352,9 @@ The `myTheme` object contains the following keys: * `palette` * `props` * `shadows` -* `typography` -* `transitions` * `spacing` +* `transitions` +* `typography` * `zIndex` **Tip**: Check [Material UI default theme documentation](https://material-ui.com/customization/default-theme/) to see the default values and meaning for these keys. @@ -894,6 +891,8 @@ The `MenuItemLink` component make use of the React Router [NavLink](https://reac **Tip**: If you need a multi-level menu, or a Mega Menu opening panels with custom content, check out [the `ra-navigation` module](https://marmelab.com/ra-enterprise/modules/ra-navigation) (part of the [Enterprise Edition](https://marmelab.com/ra-enterprise)) +![multi-level menu](https://marmelab.com/ra-enterprise/modules/assets/ra-multilevelmenu-item.gif) + ![MegaMenu and Breadcrumb](https://marmelab.com/ra-enterprise/modules/assets/ra-multilevelmenu-categories.gif) ## Using a Custom Login Page diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 739959ab928..4e90b12d3e2 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -472,7 +472,7 @@ import { Edit, SimpleForm, ReferenceInput, - SelectIpnut, + SelectInput, TextInput, } from 'react-admin'; @@ -534,7 +534,7 @@ import { + Create, SimpleForm, ReferenceInput, - SelectIpnut, + SelectInput, TextInput, } from 'react-admin'; diff --git a/lerna.json b/lerna.json index b60c3ffd305..6531f0a200b 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ "examples/data-generator", "packages/*" ], - "version": "3.10.4" + "version": "3.11.1" } diff --git a/packages/ra-core/package.json b/packages/ra-core/package.json index a1fcfe3c1d4..35e4c7f8ba5 100644 --- a/packages/ra-core/package.json +++ b/packages/ra-core/package.json @@ -1,6 +1,6 @@ { "name": "ra-core", - "version": "3.10.4", + "version": "3.11.1", "description": "Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React", "files": [ "*.md", diff --git a/packages/ra-core/src/controller/useListController.ts b/packages/ra-core/src/controller/useListController.ts index ea4e46597b2..38c751d1d36 100644 --- a/packages/ra-core/src/controller/useListController.ts +++ b/packages/ra-core/src/controller/useListController.ts @@ -61,6 +61,7 @@ export interface ListControllerProps { displayedFilters: any; error?: any; exporter?: Exporter | false; + filter?: FilterPayload; filterValues: any; hasCreate: boolean; hideFilter: (filterName: string) => void; @@ -250,6 +251,7 @@ const useListController = ( displayedFilters: query.displayedFilters, error, exporter, + filter, filterValues: query.filterValues, hasCreate, hideFilter: queryModifiers.hideFilter, diff --git a/packages/ra-core/src/controller/useListParams.ts b/packages/ra-core/src/controller/useListParams.ts index 0b961163dfe..e5d634c75cb 100644 --- a/packages/ra-core/src/controller/useListParams.ts +++ b/packages/ra-core/src/controller/useListParams.ts @@ -26,8 +26,6 @@ interface ListParamsOptions { sort?: SortPayload; // default value for a filter when displayed but not yet set filterDefaultValues?: FilterPayload; - // permanent filter which always overrides the user entry - filter?: FilterPayload; debounce?: number; } @@ -111,7 +109,6 @@ const useListParams = ({ resource, location, filterDefaultValues, - filter, // permanent filter sort = defaultSort, perPage = 10, debounce = 500, @@ -191,10 +188,7 @@ const useListParams = ({ requestSignature // eslint-disable-line react-hooks/exhaustive-deps ); - const filterValues = useMemo( - () => ({ ...(query.filter || emptyObject), ...filter }), - [filter, query.filter] - ); + const filterValues = query.filter || emptyObject; const displayedFilterValues = query.displayedFilters || emptyObject; const debouncedSetFilters = lodashDebounce((filter, displayedFilters) => { diff --git a/packages/ra-core/src/controller/useSelectionState.ts b/packages/ra-core/src/controller/useSelectionState.ts index 2a3df94fdaf..7701647a2d4 100644 --- a/packages/ra-core/src/controller/useSelectionState.ts +++ b/packages/ra-core/src/controller/useSelectionState.ts @@ -19,7 +19,7 @@ export interface SelectionState { * * @example * - * const { selectedIds, onSelect, onToggleItem, onUnselectItem } = useSelectionState(); + * const { selectedIds, onSelect, onToggleItem, onUnselectItems } = useSelectionState(); * */ const useSelectionState = ( diff --git a/packages/ra-core/src/dataProvider/useQueryWithStore.ts b/packages/ra-core/src/dataProvider/useQueryWithStore.ts index 109c9e4654e..0ef65e71952 100644 --- a/packages/ra-core/src/dataProvider/useQueryWithStore.ts +++ b/packages/ra-core/src/dataProvider/useQueryWithStore.ts @@ -158,6 +158,7 @@ const useQueryWithStore = ( data, total, loaded: true, + loading: false, })); } } diff --git a/packages/ra-data-json-server/package.json b/packages/ra-data-json-server/package.json index 104e5e981d2..7e5301f5240 100644 --- a/packages/ra-data-json-server/package.json +++ b/packages/ra-data-json-server/package.json @@ -1,6 +1,6 @@ { "name": "ra-data-json-server", - "version": "3.10.4", + "version": "3.11.1", "description": "JSON Server data provider for react-admin", "main": "lib/index.js", "module": "esm/index.js", @@ -26,7 +26,7 @@ }, "dependencies": { "query-string": "^5.1.1", - "ra-core": "^3.10.4" + "ra-core": "^3.11.1" }, "devDependencies": { "cross-env": "^5.2.0", diff --git a/packages/ra-i18n-polyglot/package.json b/packages/ra-i18n-polyglot/package.json index a431ee26e83..214ad1d367a 100644 --- a/packages/ra-i18n-polyglot/package.json +++ b/packages/ra-i18n-polyglot/package.json @@ -1,6 +1,6 @@ { "name": "ra-i18n-polyglot", - "version": "3.10.4", + "version": "3.11.1", "description": "Polyglot i18n provider for react-admin", "main": "lib/index.js", "module": "esm/index.js", @@ -26,7 +26,7 @@ }, "dependencies": { "node-polyglot": "^2.2.2", - "ra-core": "^3.10.4" + "ra-core": "^3.11.1" }, "devDependencies": { "cross-env": "^5.2.0", diff --git a/packages/ra-language-english/package.json b/packages/ra-language-english/package.json index 5aaa015e8d5..25cbb81d624 100644 --- a/packages/ra-language-english/package.json +++ b/packages/ra-language-english/package.json @@ -1,6 +1,6 @@ { "name": "ra-language-english", - "version": "3.10.4", + "version": "3.11.1", "description": "English messages for react-admin, the frontend framework for building admin applications on top of REST/GraphQL services", "repository": { "type": "git", @@ -22,7 +22,7 @@ "watch": "tsc --outDir esm --module es2015 --watch" }, "dependencies": { - "ra-core": "^3.10.4" + "ra-core": "^3.11.1" }, "keywords": [ "react", diff --git a/packages/ra-language-french/package.json b/packages/ra-language-french/package.json index e4e63f0c73d..9acee94f6cb 100644 --- a/packages/ra-language-french/package.json +++ b/packages/ra-language-french/package.json @@ -1,6 +1,6 @@ { "name": "ra-language-french", - "version": "3.10.4", + "version": "3.11.1", "description": "French messages for react-admin, the frontend framework for building admin applications on top of REST/GraphQL services", "repository": { "type": "git", @@ -16,7 +16,7 @@ "watch": "tsc --outDir esm --module es2015 --watch" }, "dependencies": { - "ra-core": "^3.10.4" + "ra-core": "^3.11.1" }, "keywords": [ "react", diff --git a/packages/ra-ui-materialui/package.json b/packages/ra-ui-materialui/package.json index 9b5547a558c..ffea2624cf7 100644 --- a/packages/ra-ui-materialui/package.json +++ b/packages/ra-ui-materialui/package.json @@ -1,6 +1,6 @@ { "name": "ra-ui-materialui", - "version": "3.10.4", + "version": "3.11.1", "description": "UI Components for react-admin with MaterialUI", "files": [ "*.md", @@ -38,7 +38,7 @@ "final-form": "^4.20.0", "final-form-arrays": "^3.0.1", "ignore-styles": "~5.0.1", - "ra-core": "^3.10.4", + "ra-core": "^3.11.1", "react": "^17.0.0", "react-dom": "^17.0.0", "react-final-form": "^6.5.0", diff --git a/packages/ra-ui-materialui/src/button/ExportButton.tsx b/packages/ra-ui-materialui/src/button/ExportButton.tsx index 6183de01a61..ca3e947c093 100644 --- a/packages/ra-ui-materialui/src/button/ExportButton.tsx +++ b/packages/ra-ui-materialui/src/button/ExportButton.tsx @@ -25,6 +25,7 @@ const ExportButton: FunctionComponent = props => { ...rest } = props; const { + filter, filterValues, currentSort, exporter: exporterFromContext, @@ -39,7 +40,9 @@ const ExportButton: FunctionComponent = props => { dataProvider .getList(resource, { sort: currentSort || sort, - filter: filterValues, + filter: filter + ? { ...filterValues, ...filter } + : filterValues, pagination: { page: 1, perPage: maxResults }, }) .then( @@ -64,6 +67,7 @@ const ExportButton: FunctionComponent = props => { currentSort, dataProvider, exporter, + filter, filterValues, maxResults, notify, diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx index d6e3526113f..b3ebad26d44 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx @@ -10,7 +10,7 @@ import SingleFieldList from '../list/SingleFieldList'; describe('', () => { afterEach(cleanup); - it('should render a loading indicator when related records are not yet fetched', () => { + it('should render a loading indicator when related records are not yet fetched and a second has passed', async () => { const { queryAllByRole } = render( ', () => { ); + + await new Promise(resolve => setTimeout(resolve, 1001)); expect(queryAllByRole('progressbar')).toHaveLength(1); }); diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx index 5da928cb5ab..04a06a546cf 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { Children, cloneElement, FC, memo, ReactElement } from 'react'; import PropTypes from 'prop-types'; -import { LinearProgress } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { ListContextProvider, @@ -16,6 +15,7 @@ import { import { fieldPropTypes, PublicFieldProps, InjectedFieldProps } from './types'; import { ClassesOverride } from '../types'; import sanitizeFieldRestProps from './sanitizeFieldRestProps'; +import { LinearProgress } from '../layout'; /** * A container component that fetches records from another resource specified diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx index b96098b7c41..a7e5a1fbffc 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx @@ -9,20 +9,43 @@ import TextField from './TextField'; describe('', () => { afterEach(cleanup); + const record = { id: 123, postId: 123 }; describe('Progress bar', () => { - it('should display a loader on mount if the reference is not in the store', () => { + it("should not display a loader on mount if the reference is not in the store and a second hasn't passed yet", async () => { const { queryByRole, container } = renderWithRedux( - - + + ); + await new Promise(resolve => setTimeout(resolve, 500)); + expect(queryByRole('progressbar')).toBeNull(); + const links = container.getElementsByTagName('a'); + expect(links).toHaveLength(0); + }); + it('should display a loader on mount if the reference is not in the store and a second has passed', async () => { + const { queryByRole, container } = renderWithRedux( + + + ); + await new Promise(resolve => setTimeout(resolve, 1001)); expect(queryByRole('progressbar')).not.toBeNull(); const links = container.getElementsByTagName('a'); expect(links).toHaveLength(0); @@ -32,7 +55,7 @@ describe('', () => { const { queryByRole, container } = renderWithRedux( ', () => { ', () => { // @ts-ignore-line ', () => { // @ts-ignore-line ', () => { const { container, getByText } = renderWithRedux( ', () => { ', () => { // @ts-ignore-line ', () => { const { container } = render( ', () => { it('should render no link when resourceLinkPath is not specified', () => { const { container } = render( only accepts a single child'); } - const { basePath, resource } = props; + const { basePath, resource, reference } = props; const resourceLinkPath = getResourceLinkPath({ ...props, resource, @@ -147,12 +147,13 @@ export const NonEmptyReferenceField: FC + = props => { ...rest } = props; const classes = useStyles(props); + if (!loaded) { return ; } diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx index da67916470d..2c77a3ebd2c 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx @@ -265,9 +265,13 @@ const AutocompleteInput: FunctionComponent = props => { const handleChange = useCallback( (item: any) => { + if (getChoiceValue(item) == null && filterValue) { + setFilterValue(''); + } + input.onChange(getChoiceValue(item)); }, - [getChoiceValue, input] + [filterValue, getChoiceValue, input] ); // This function ensures that the suggestion list stay aligned to the diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js index bdd0d2f9fbb..6b34638f6c0 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js +++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js @@ -15,7 +15,7 @@ describe('', () => { translate: x => `*${x}*`, }; - it('should render a progress bar if loading is true', () => { + it("should not render a progress bar if loading is true and a second hasn't passed", async () => { const MyComponent = () =>
MyComponent
; const { queryByRole, queryByText } = render( ', () => { ); + await new Promise(resolve => setTimeout(resolve, 250)); + expect(queryByRole('progressbar')).toBeNull(); + expect(queryByText('MyComponent')).toBeNull(); + }); + + it('should render a progress bar if loading is true and a second has passed', async () => { + const MyComponent = () =>
MyComponent
; + const { queryByRole, queryByText } = render( + + + + ); + await new Promise(resolve => setTimeout(resolve, 1001)); expect(queryByRole('progressbar')).not.toBeNull(); expect(queryByText('MyComponent')).toBeNull(); }); diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx index 2133fc801f8..6d3a250f9df 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx @@ -59,7 +59,7 @@ describe('', () => { afterEach(cleanup); - it('should render a LinearProgress if loading is true', () => { + it('should render a LinearProgress if loading is true and a second has passed', async () => { const { queryByRole } = render( ', () => { ); + await new Promise(resolve => setTimeout(resolve, 1001)); expect(queryByRole('progressbar')).not.toBeNull(); }); + it("should not render a LinearProgress if loading is true and a second hasn't passed", async () => { + const { queryByRole } = render( + + + + ); + + await new Promise(resolve => setTimeout(resolve, 250)); + expect(queryByRole('progressbar')).toBeNull(); + }); + it('should not render a LinearProgress if loading is false', () => { const { queryByRole } = render( ({ - contentText: { - minWidth: 400, - }, confirmPrimary: { color: theme.palette.primary.main, }, @@ -97,7 +94,7 @@ const Confirm: FC = props => { {translate(title, { _: title, ...translateOptions })} - + {translate(content, { _: content, ...translateOptions, diff --git a/packages/ra-ui-materialui/src/layout/LinearProgress.tsx b/packages/ra-ui-materialui/src/layout/LinearProgress.tsx index e990ac25640..68f51bddd00 100644 --- a/packages/ra-ui-materialui/src/layout/LinearProgress.tsx +++ b/packages/ra-ui-materialui/src/layout/LinearProgress.tsx @@ -1,8 +1,11 @@ import * as React from 'react'; -import Progress from '@material-ui/core/LinearProgress'; +import Progress, { + LinearProgressProps as ProgressProps, +} from '@material-ui/core/LinearProgress'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; +import { useTimeout } from 'ra-core'; const useStyles = makeStyles( theme => ({ @@ -24,18 +27,27 @@ const useStyles = makeStyles( * * @param {Object} classes CSS class names */ -const LinearProgress = props => { +const LinearProgress = ({ timeout = 1000, ...props }: LinearProgressProps) => { const { classes: classesOverride, className, ...rest } = props; const classes = useStyles(props); - return ( + const oneSecondHasPassed = useTimeout(timeout); + + return oneSecondHasPassed ? ( - ); + ) : null; }; + LinearProgress.propTypes = { classes: PropTypes.object, className: PropTypes.string, + timeout: PropTypes.number, }; + // wat? TypeScript looses the displayName if we don't set it explicitly LinearProgress.displayName = 'LinearProgress'; +export interface LinearProgressProps extends ProgressProps { + timeout?: number; +} + export default LinearProgress; diff --git a/packages/ra-ui-materialui/src/layout/index.ts b/packages/ra-ui-materialui/src/layout/index.ts index 10a4e673615..7ca62dcf6e8 100644 --- a/packages/ra-ui-materialui/src/layout/index.ts +++ b/packages/ra-ui-materialui/src/layout/index.ts @@ -9,7 +9,7 @@ import HideOnScroll, { HideOnScrollProps } from './HideOnScroll'; import Layout, { LayoutProps } from './Layout'; import Loading from './Loading'; import LoadingPage from './LoadingPage'; -import LinearProgress from './LinearProgress'; +import LinearProgress, { LinearProgressProps } from './LinearProgress'; import LoadingIndicator from './LoadingIndicator'; import Menu, { MenuProps } from './Menu'; import MenuItemLink, { MenuItemLinkProps } from './MenuItemLink'; @@ -57,6 +57,7 @@ export type { ErrorProps, HideOnScrollProps, LayoutProps, + LinearProgressProps, MenuItemLinkProps, MenuProps, ResponsiveProps, diff --git a/packages/ra-ui-materialui/src/list/Empty.tsx b/packages/ra-ui-materialui/src/list/Empty.tsx index 9d13594efac..91f901290fd 100644 --- a/packages/ra-ui-materialui/src/list/Empty.tsx +++ b/packages/ra-ui-materialui/src/list/Empty.tsx @@ -33,7 +33,7 @@ const useStyles = makeStyles( ); const Empty: FC = props => { - const { basePath } = useListContext(props); + const { basePath, hasCreate } = useListContext(props); const resource = useResourceContext(props); const classes = useStyles(props); const translate = useTranslate(); @@ -61,15 +61,19 @@ const Empty: FC = props => { _: emptyMessage, })} - - {translate(`resources.${resource}.invite`, { - _: inviteMessage, - })} - - -
- + {hasCreate && ( + + {translate(`resources.${resource}.invite`, { + _: inviteMessage, + })} + + )}
+ {hasCreate && ( +
+ +
+ )} ); }; diff --git a/packages/ra-ui-materialui/src/list/ListView.tsx b/packages/ra-ui-materialui/src/list/ListView.tsx index c70baa6ac98..6a7baa503ee 100644 --- a/packages/ra-ui-materialui/src/list/ListView.tsx +++ b/packages/ra-ui-materialui/src/list/ListView.tsx @@ -46,7 +46,6 @@ export const ListView = (props: ListViewProps) => { total, loaded, loading, - hasCreate, filterValues, selectedIds, } = listContext; @@ -88,11 +87,7 @@ export const ListView = (props: ListViewProps) => { ); const shouldRenderEmptyPage = - hasCreate && - loaded && - !loading && - total === 0 && - !Object.keys(filterValues).length; + loaded && !loading && total === 0 && !Object.keys(filterValues).length; return (