From d7ad6d750b9499b2bc021cffa4f2381fb7037f67 Mon Sep 17 00:00:00 2001 From: Chloe Rice Date: Wed, 4 Dec 2019 22:58:02 -0500 Subject: [PATCH] Revert "Revert "Merge pull request #2160 from Shopify/resourcelist-emptystate"" This reverts commit 6ec053f8dd30a227e29989e2baf7c9a5020812d1. --- src/components/ResourceList/README.md | 48 ++++++++++- src/components/ResourceList/ResourceList.tsx | 23 +++-- .../ResourceList/tests/ResourceList.test.tsx | 86 ++++++++++++++++++- 3 files changed, 148 insertions(+), 9 deletions(-) diff --git a/src/components/ResourceList/README.md b/src/components/ResourceList/README.md index 0da7aad05ba..40881ce9474 100644 --- a/src/components/ResourceList/README.md +++ b/src/components/ResourceList/README.md @@ -84,7 +84,53 @@ A resource list with simple items and no bulk actions, sorting, or filtering. ``` -### Resource List with selection and no bulk actions +### Resource list with empty state + +Use to explain the purpose of a list of resources when no resources exist yet. This allows a smooth transition from a list in a loading state to a list where zero, one, or many resources exist. + +```jsx +function ResourceListWithEmptyStateExample() { + const filters = [ + { + key: 'fileType', + label: 'File type', + filter: , + shortcut: true, + }, + ]; + + const filterControl = ( + + ); + + const emptyState = ( + +

+ You can use the Files section to upload images, videos, and other + documents +

+
+ ); + + return ( + + {}} + filterControl={filterControl} + resourceName={{singular: 'file', plural: 'files'}} + /> + + ); +} +``` + +### Resource list with selection and no bulk actions A resource list with simple items and selection. diff --git a/src/components/ResourceList/ResourceList.tsx b/src/components/ResourceList/ResourceList.tsx index d643c4be698..f2735612f66 100644 --- a/src/components/ResourceList/ResourceList.tsx +++ b/src/components/ResourceList/ResourceList.tsx @@ -51,6 +51,8 @@ export interface ResourceListProps { /** Item data; each item is passed to renderItem */ items: Items; filterControl?: React.ReactNode; + /** The markup to render when no resources exist yet */ + emptyState?: React.ReactNode; /** Name of the resource, such as customers or products */ resourceName?: { singular: string; @@ -378,7 +380,9 @@ class ResourceList extends React.Component { promotedBulkActions, bulkActions, filterControl, + emptyState, loading, + hasMoreItems, showHeader = false, sortOptions, sortValue, @@ -471,9 +475,14 @@ class ResourceList extends React.Component {
) : null; - const showEmptyState = filterControl && !this.itemsExist() && !loading; + const showNoResults = + filterControl && !this.itemsExist() && hasMoreItems && !loading; + + const showEmptyState = + emptyState !== undefined && !this.itemsExist() && !hasMoreItems; - const headerMarkup = !showEmptyState && + const headerMarkup = !showNoResults && + !showEmptyState && (showHeader || needsHeader) && this.listRef.current && (
@@ -512,12 +521,14 @@ class ResourceList extends React.Component {
); - const emptyStateMarkup = showEmptyState ? ( + const noResultsMarkup = showNoResults ? (
) : null; + const emptyStateMarkup = showEmptyState ? emptyState : null; + const defaultTopPadding = 8; const topPadding = loadingPosition > 0 ? loadingPosition : defaultTopPadding; @@ -561,9 +572,7 @@ class ResourceList extends React.Component { {loadingOverlay} {items.map(this.renderItem)} - ) : ( - emptyStateMarkup - ); + ) : null; const context = { selectable: this.selectable, @@ -581,6 +590,8 @@ class ResourceList extends React.Component { {filterControlMarkup} {headerMarkup} {listMarkup} + {noResultsMarkup} + {emptyStateMarkup} {loadingWithoutItemsMarkup}
diff --git a/src/components/ResourceList/tests/ResourceList.test.tsx b/src/components/ResourceList/tests/ResourceList.test.tsx index 5257a8eb3dc..3d9db12145a 100644 --- a/src/components/ResourceList/tests/ResourceList.test.tsx +++ b/src/components/ResourceList/tests/ResourceList.test.tsx @@ -8,6 +8,7 @@ import { ResourceItem, EventListener, Button, + EmptyState, } from 'components'; // eslint-disable-next-line no-restricted-imports import { @@ -388,10 +389,11 @@ describe('', () => { ); }); - it('does not render when items is empty', () => { + it('does not render when items is empty and hasMoreItems is unset', () => { const resourceList = mountWithAppProvider( , ); + expect(findByTestID(resourceList, 'ResourceList-Header').exists()).toBe( false, ); @@ -477,6 +479,7 @@ describe('', () => { it('does not render when EmptySearchResult exists', () => { const resourceList = mountWithAppProvider( fake filterControl} @@ -489,7 +492,7 @@ describe('', () => { }); describe('filterControl', () => { - it('renders when exist', () => { + it('renders when exists', () => { const resourceList = mountWithAppProvider( ', () => { }); }); + describe('emptyState', () => { + it('renders when no items are provided and hasMoreItems is unset', () => { + const emptyState = ( + +

+ You can use the Files section to upload images, videos, and other + documents +

+
+ ); + + const resourceList = mountWithAppProvider( + , + ); + + expect(resourceList.find(EmptyState)).toHaveLength(1); + }); + + it('does not render when exists but items are provided', () => { + const emptyState = ( + +

+ You can use the Files section to upload images, videos, and other + documents +

+
+ ); + + const resourceList = mountWithAppProvider( + , + ); + + expect(resourceList.find(EmptyState)).toHaveLength(0); + }); + + it('does not render when exists but hasMoreItems is true', () => { + const emptyState = ( + +

+ You can use the Files section to upload images, videos, and other + documents +

+
+ ); + + const resourceList = mountWithAppProvider( + , + ); + + expect(resourceList.find(EmptyState)).toHaveLength(0); + }); + }); + describe('emptySearchResult', () => { it('renders when filterControl exists and items is empty', () => { const resourceList = mountWithAppProvider( fake filterControl}