Skip to content

Commit

Permalink
Revert "Revert "Merge pull request #2160 from Shopify/resourcelist-em…
Browse files Browse the repository at this point in the history
…ptystate""

This reverts commit 6ec053f.
  • Loading branch information
chloerice committed May 13, 2020
1 parent 3105307 commit 1633938
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 8 deletions.
48 changes: 47 additions & 1 deletion src/components/ResourceList/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,53 @@ A resource list with simple items and no bulk actions, sorting, or filtering.
</Card>
```

### 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: <TextField label="File type" value="" labelHidden />,
shortcut: true,
},
];

const filterControl = (
<Filters disabled queryValue="" filters={filters} appliedFilters={[]} />
);

const emptyState = (
<EmptyState
heading="Upload a file to get started"
action={{content: 'Upload files'}}
image="https://cdn.shopify.com/s/files/1/2376/3301/products/file-upload-empty-state.png"
>
<p>
You can use the Files section to upload images, videos, and other
documents
</p>
</EmptyState>
);

return (
<Card>
<ResourceList
emptyState={emptyState}
items={[]}
renderItem={() => {}}
filterControl={filterControl}
resourceName={{singular: 'file', plural: 'files'}}
/>
</Card>
);
}
```

### Resource list with selection and no bulk actions

A resource list with simple items and selection.

Expand Down
18 changes: 13 additions & 5 deletions src/components/ResourceList/ResourceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export interface ResourceListProps {
/** Item data; each item is passed to renderItem */
items: Items;
filterControl?: React.ReactNode;
/** The markup to display when no resources exist yet. Renders when set. */
emptyState?: React.ReactNode;
/** The markup to display when `filterControl` is set and no results are returned on search or filter of the list. Renders when set. */
emptySearchState?: React.ReactNode;
/** Name of the resource, such as customers or products */
resourceName?: {
singular: string;
Expand Down Expand Up @@ -90,8 +94,6 @@ export interface ResourceListProps {
idForItem?(item: any, index: number): string;
/** Function to resolve an id from a item */
resolveItemId?(item: any): string;
/** React node to display when `filterControl` is set and no results are returned on search or filter of the list. */
emptySearchState?: React.ReactNode;
}

type CombinedProps = ResourceListProps & WithAppProviderProps;
Expand Down Expand Up @@ -385,6 +387,7 @@ class ResourceListInner extends React.Component<CombinedProps, State> {
promotedBulkActions,
bulkActions,
filterControl,
emptyState,
loading,
showHeader = false,
sortOptions,
Expand Down Expand Up @@ -482,7 +485,10 @@ class ResourceListInner extends React.Component<CombinedProps, State> {
const showEmptySearchState =
filterControl && !this.itemsExist() && !loading;

const showEmptyState = emptyState && !this.itemsExist();

const headerMarkup = !showEmptySearchState &&
!showEmptyState &&
(showHeader || needsHeader) &&
this.listRef.current && (
<div className={styles.HeaderOuterWrapper}>
Expand Down Expand Up @@ -532,6 +538,8 @@ class ResourceListInner extends React.Component<CombinedProps, State> {
)
: null;

const emptyStateMarkup = showEmptyState ? emptyState : null;

const defaultTopPadding = 8;
const topPadding =
loadingPosition > 0 ? loadingPosition : defaultTopPadding;
Expand Down Expand Up @@ -575,9 +583,7 @@ class ResourceListInner extends React.Component<CombinedProps, State> {
{loadingOverlay}
{items.map(this.renderItem)}
</ul>
) : (
emptySearchStateMarkup
);
) : null;

const context = {
selectable: this.selectable(),
Expand All @@ -595,6 +601,8 @@ class ResourceListInner extends React.Component<CombinedProps, State> {
{filterControlMarkup}
{headerMarkup}
{listMarkup}
{emptySearchStateMarkup}
{emptyStateMarkup}
{loadingWithoutItemsMarkup}
</div>
</ResourceListContext.Provider>
Expand Down
86 changes: 84 additions & 2 deletions src/components/ResourceList/tests/ResourceList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ResourceItem,
EventListener,
Button,
EmptyState,
} from 'components';
// eslint-disable-next-line no-restricted-imports
import {
Expand Down Expand Up @@ -371,10 +372,11 @@ describe('<ResourceList />', () => {
);
});

it('does not render when items is empty', () => {
it('does not render when items is empty and hasMoreItems is unset', () => {
const resourceList = mountWithAppProvider(
<ResourceList items={[]} renderItem={renderItem} />,
);

expect(findByTestID(resourceList, 'ResourceList-Header').exists()).toBe(
false,
);
Expand Down Expand Up @@ -460,6 +462,7 @@ describe('<ResourceList />', () => {
it('does not render when EmptySearchResult exists', () => {
const resourceList = mountWithAppProvider(
<ResourceList
hasMoreItems
items={[]}
renderItem={renderItem}
filterControl={<div>fake filterControl</div>}
Expand All @@ -472,7 +475,7 @@ describe('<ResourceList />', () => {
});

describe('filterControl', () => {
it('renders when exist', () => {
it('renders when exists', () => {
const resourceList = mountWithAppProvider(
<ResourceList
items={itemsNoID}
Expand All @@ -484,10 +487,89 @@ describe('<ResourceList />', () => {
});
});

describe('emptyState', () => {
it('renders when no items are provided and hasMoreItems is unset', () => {
const emptyState = (
<EmptyState
heading="Upload a file to get started"
action={{content: 'Upload files'}}
image="https://cdn.shopify.com/s/files/1/2376/3301/products/file-upload-empty-state.png"
>
<p>
You can use the Files section to upload images, videos, and other
documents
</p>
</EmptyState>
);

const resourceList = mountWithAppProvider(
<ResourceList
items={[]}
renderItem={renderItem}
emptyState={emptyState}
/>,
);

expect(resourceList.find(EmptyState)).toHaveLength(1);
});

it('does not render when exists but items are provided', () => {
const emptyState = (
<EmptyState
heading="Upload a file to get started"
action={{content: 'Upload files'}}
image="https://cdn.shopify.com/s/files/1/2376/3301/products/file-upload-empty-state.png"
>
<p>
You can use the Files section to upload images, videos, and other
documents
</p>
</EmptyState>
);

const resourceList = mountWithAppProvider(
<ResourceList
items={itemsNoID}
renderItem={renderItem}
emptyState={emptyState}
/>,
);

expect(resourceList.find(EmptyState)).toHaveLength(0);
});

it('does not render when exists but hasMoreItems is true', () => {
const emptyState = (
<EmptyState
heading="Upload a file to get started"
action={{content: 'Upload files'}}
image="https://cdn.shopify.com/s/files/1/2376/3301/products/file-upload-empty-state.png"
>
<p>
You can use the Files section to upload images, videos, and other
documents
</p>
</EmptyState>
);

const resourceList = mountWithAppProvider(
<ResourceList
hasMoreItems
items={[]}
renderItem={renderItem}
emptyState={emptyState}
/>,
);

expect(resourceList.find(EmptyState)).toHaveLength(0);
});
});

describe('emptySearchResult', () => {
it('renders when filterControl exists and items is empty', () => {
const resourceList = mountWithAppProvider(
<ResourceList
hasMoreItems
items={[]}
renderItem={renderItem}
filterControl={<div>fake filterControl</div>}
Expand Down

0 comments on commit 1633938

Please sign in to comment.