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