diff --git a/UNRELEASED.md b/UNRELEASED.md index c5c2a7515be..ecd5b8f76db 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -8,6 +8,7 @@ - Truncated long sort options in `ResourceList` ([#2957](https://github.com/Shopify/polaris-react/pull/2957) - Updated type restrictions for `Pagination` to allow its `label` prop to accept `React.ReactNode` instead of `string` ([#2972](https://github.com/Shopify/polaris-react/pull/2972)) +- Added an `emptySearchState` prop to `ResourceList` to enable the customization of the empty search state ([#2971](https://github.com/Shopify/polaris-react/pull/2971)) ### Bug fixes diff --git a/src/components/ResourceList/README.md b/src/components/ResourceList/README.md index 0da7aad05ba..cdc1e0cc7a3 100644 --- a/src/components/ResourceList/README.md +++ b/src/components/ResourceList/README.md @@ -506,10 +506,6 @@ function ResourceListWithFilteringExample() { (value) => setTaggedWith(value), [], ); - const handleQueryValueChange = useCallback( - (value) => setQueryValue(value), - [], - ); const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []); const handleQueryValueRemove = useCallback(() => setQueryValue(null), []); const handleClearAll = useCallback(() => { @@ -553,6 +549,121 @@ function ResourceListWithFilteringExample() { }, ]; + const appliedFilters = !isEmpty(taggedWith) + ? [ + { + key: 'taggedWith', + label: disambiguateLabel('taggedWith', taggedWith), + onRemove: handleTaggedWithRemove, + }, + ] + : []; + + const filterControl = ( + +
+ +
+
+ ); + + return ( + + + + ); + + function renderItem(item) { + const {id, url, name, location} = item; + const media = ; + + return ( + +

+ {name} +

+
{location}
+
+ ); + } + + function disambiguateLabel(key, value) { + switch (key) { + case 'taggedWith': + return `Tagged with ${value}`; + default: + return value; + } + } + + function isEmpty(value) { + if (Array.isArray(value)) { + return value.length === 0; + } else { + return value === '' || value == null; + } + } +} +``` + +### Resource list with a custom empty search result state + +Allows merchants to narrow the resource list to a subset of the original items. If the filters or search applied return no results, then display a custom empty search state. + +```jsx +function ResourceListWithFilteringExample() { + const [taggedWith, setTaggedWith] = useState('VIP'); + const [queryValue, setQueryValue] = useState(null); + const [items, setItems] = useState([]); + + const handleTaggedWithChange = useCallback( + (value) => setTaggedWith(value), + [], + ); + const handleQueryValueChange = useCallback((value) => { + setQueryValue(value); + setItems([]); + }, []); + const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []); + const handleQueryValueRemove = useCallback(() => setQueryValue(null), []); + const handleClearAll = useCallback(() => { + handleTaggedWithRemove(); + handleQueryValueRemove(); + }, [handleQueryValueRemove, handleTaggedWithRemove]); + + const resourceName = { + singular: 'customer', + plural: 'customers', + }; + + const filters = [ + { + key: 'taggedWith', + label: 'Tagged with', + filter: ( + + ), + shortcut: true, + }, + ]; + const appliedFilters = !isEmpty(taggedWith) ? [ { @@ -585,6 +696,7 @@ function ResourceListWithFilteringExample() { items={items} renderItem={renderItem} filterControl={filterControl} + emptySearchState={
This is a custom empty state
} /> ); diff --git a/src/components/ResourceList/ResourceList.tsx b/src/components/ResourceList/ResourceList.tsx index bcd8f7028d5..f166e2b6835 100644 --- a/src/components/ResourceList/ResourceList.tsx +++ b/src/components/ResourceList/ResourceList.tsx @@ -90,6 +90,8 @@ 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; @@ -391,6 +393,7 @@ class ResourceListInner extends React.Component { selectedItems, resourceName = this.defaultResourceName, onSortChange, + emptySearchState, polaris: {intl}, } = this.props; const {selectMode, loadingPosition, smallScreen} = this.state; @@ -476,9 +479,10 @@ class ResourceListInner extends React.Component {
) : null; - const showEmptyState = filterControl && !this.itemsExist() && !loading; + const showEmptySearchState = + filterControl && !this.itemsExist() && !loading; - const headerMarkup = !showEmptyState && + const headerMarkup = !showEmptySearchState && (showHeader || needsHeader) && this.listRef.current && (
@@ -517,11 +521,16 @@ class ResourceListInner extends React.Component {
); - const emptyStateMarkup = showEmptyState ? ( -
- -
- ) : null; + const emptySearchStateMarkup = showEmptySearchState + ? emptySearchState || ( +
+ +
+ ) + : null; const defaultTopPadding = 8; const topPadding = @@ -567,7 +576,7 @@ class ResourceListInner extends React.Component { {items.map(this.renderItem)} ) : ( - emptyStateMarkup + emptySearchStateMarkup ); const context = { diff --git a/src/components/ResourceList/tests/ResourceList.test.tsx b/src/components/ResourceList/tests/ResourceList.test.tsx index e57b5ba6e34..9409cc43f5e 100644 --- a/src/components/ResourceList/tests/ResourceList.test.tsx +++ b/src/components/ResourceList/tests/ResourceList.test.tsx @@ -525,6 +525,22 @@ describe('', () => { ); expect(resourceList.find(EmptySearchResult).exists()).toBe(false); }); + + it('renders the provided markup when emptySearchState is set', () => { + const resourceList = mountWithAppProvider( + fake filterControl
} + emptySearchState={ +
Alternate empty state
+ } + />, + ); + + expect(resourceList.find(EmptySearchResult).exists()).toBe(false); + expect(resourceList.find('div#emptySearchState').exists()).toBe(true); + }); }); describe('Sorting', () => { diff --git a/src/components/Select/Select.scss b/src/components/Select/Select.scss index f521e4c6ef8..47f576232da 100644 --- a/src/components/Select/Select.scss +++ b/src/components/Select/Select.scss @@ -159,7 +159,6 @@ $stacking-order: ( border-radius: var(--p-border-radius-base); background-color: var(--p-surface); @include focus-ring($border-width: rem(1px)); - // 'position' needs to sit below focus-ring since it will be overwritten // with relative when the focus ring style is 'base' // stylelint-disable-next-line order/properties-order