Skip to content

Commit 7e46d7e

Browse files
authored
[IndexFilters] Support sort-only mode (#8876)
### WHY are these changes introduced? We want to support the use case of not having any searching or filtering functionality, but still retaining the Tabs and the sorting functionality. We already support both the `hideQueryField` and `hideFilters` props, but hadn't correctly handled the use-case when both are true. We now will remove the toggle button to switch to the filter mode, so that the UI is only the Tabs and the Sort button. Whilst I was working around this component, I also fixed a bug around the disabling of disabled filter pills. The desired effect is to render the component, but return `null` from the component, rather than render nothing at all. This prevents a bug where if a filter was previously disabled and then becomes enabled, the Popover would automatically open without any user interaction. ### 🎩 checklist - [x] Tested on [mobile](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md#cross-browser-testing) - [x] Tested on [multiple browsers](https://help.shopify.com/en/manual/shopify-admin/supported-browsers) - [x] Tested for [accessibility](https://github.com/Shopify/polaris/blob/main/documentation/Accessibility%20testing.md) - [x] Updated the component's `README.md` with documentation changes - [x] [Tophatted documentation](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting%20documentation.md) changes in the style guide
1 parent 6d46f59 commit 7e46d7e

19 files changed

+464
-59
lines changed

.changeset/chilled-jobs-fetch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@shopify/polaris': minor
3+
'polaris.shopify.com': minor
4+
---
5+
6+
Updated `IndexFilters` to support hiding both filters and search field

polaris-react/src/components/AlphaFilters/AlphaFilters.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,6 @@ export function AlphaFilters({
129129
const [localPinnedFilters, setLocalPinnedFilters] = useState<string[]>([]);
130130
const hasMounted = useRef(false);
131131

132-
const enabledFilters = filters.filter((filter) => !filter.disabled);
133-
134132
useEffect(() => {
135133
hasMounted.current = true;
136134
});
@@ -143,14 +141,14 @@ export function AlphaFilters({
143141
};
144142
const appliedFilterKeys = appliedFilters?.map(({key}) => key);
145143

146-
const pinnedFiltersFromPropsAndAppliedFilters = enabledFilters.filter(
144+
const pinnedFiltersFromPropsAndAppliedFilters = filters.filter(
147145
({pinned, key}) =>
148146
(Boolean(pinned) || appliedFilterKeys?.includes(key)) &&
149147
// Filters that are pinned in local state display at the end of our list
150148
!localPinnedFilters.find((filterKey) => filterKey === key),
151149
);
152150
const pinnedFiltersFromLocalState = localPinnedFilters
153-
.map((key) => enabledFilters.find((filter) => filter.key === key))
151+
.map((key) => filters.find((filter) => filter.key === key))
154152
.reduce<FilterInterface[]>(
155153
(acc, filter) => (filter ? [...acc, filter] : acc),
156154
[],
@@ -161,7 +159,7 @@ export function AlphaFilters({
161159
...pinnedFiltersFromLocalState,
162160
];
163161

164-
const additionalFilters = enabledFilters
162+
const additionalFilters = filters
165163
.filter((filter) => !pinnedFilters.find(({key}) => key === filter.key))
166164
.map((filter) => ({
167165
content: filter.label,
@@ -204,7 +202,7 @@ export function AlphaFilters({
204202
onClearAll?.();
205203
};
206204

207-
const shouldShowAddButton = enabledFilters.some((filter) => !filter.pinned);
205+
const shouldShowAddButton = filters.some((filter) => !filter.pinned);
208206

209207
const additionalContent = useMemo(() => {
210208
return (

polaris-react/src/components/AlphaFilters/components/FilterPill/FilterPill.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ export function FilterPill({
162162
</Button>
163163
);
164164

165+
if (disabled) {
166+
return null;
167+
}
168+
165169
return (
166170
<div ref={elementRef}>
167171
<Popover

polaris-react/src/components/AlphaFilters/components/FilterPill/tests/FilterPill.test.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,9 @@ describe('<Filters />', () => {
6868
});
6969
});
7070

71-
it('will pass the disabled prop to the activator', () => {
71+
it('will return null if disabled', () => {
7272
const wrapper = mountWithApp(<FilterPill {...defaultProps} disabled />);
73-
expect(wrapper).toContainReactComponent(UnstyledButton, {
74-
disabled: true,
75-
});
73+
expect(wrapper!.domNode).toBeNull();
7674
});
7775

7876
it('will invoked the onClick prop when clicked, if present', () => {

polaris-react/src/components/AlphaFilters/tests/AlphaFilters.test.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,15 @@ describe('<AlphaFilters />', () => {
140140
});
141141
});
142142

143-
it('will not render a disabled filter', () => {
143+
it('will not render a disabled filter if pinned', () => {
144144
const scrollSpy = jest.fn();
145145
HTMLElement.prototype.scroll = scrollSpy;
146146
const filters = [
147147
...defaultProps.filters,
148148
{
149149
key: 'disabled',
150150
label: 'Disabled',
151-
pinned: false,
151+
pinned: true,
152152
disabled: true,
153153
filter: <div>Filter</div>,
154154
},
@@ -158,6 +158,8 @@ describe('<AlphaFilters />', () => {
158158
<AlphaFilters {...defaultProps} filters={filters} />,
159159
);
160160

161+
expect(wrapper).toContainReactComponentTimes(FilterPill, 2);
162+
161163
wrapper.act(() => {
162164
wrapper
163165
.find('button', {
@@ -173,10 +175,6 @@ describe('<AlphaFilters />', () => {
173175
],
174176
});
175177

176-
expect(wrapper).not.toContainReactComponent(ActionList, {
177-
items: expect.arrayContaining([
178-
expect.objectContaining({content: 'Disabled'}),
179-
]),
180-
});
178+
expect(wrapper.findAll(FilterPill)[1].domNode).toBeNull();
181179
});
182180
});

polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,3 +948,148 @@ export function Disabled() {
948948
}
949949
}
950950
}
951+
952+
export function WithQueryFieldAndFiltersHidden() {
953+
const sleep = (ms: number) =>
954+
new Promise((resolve) => setTimeout(resolve, ms));
955+
const [itemStrings, setItemStrings] = useState([
956+
'All',
957+
'Unpaid',
958+
'Open',
959+
'Closed',
960+
'Local delivery',
961+
'Local pickup',
962+
]);
963+
const deleteView = (index: number) => {
964+
const newItemStrings = [...itemStrings];
965+
newItemStrings.splice(index, 1);
966+
setItemStrings(newItemStrings);
967+
setSelected(0);
968+
};
969+
970+
const duplicateView = async (name: string) => {
971+
setItemStrings([...itemStrings, name]);
972+
setSelected(itemStrings.length);
973+
await sleep(1);
974+
return true;
975+
};
976+
977+
const tabs: AlphaTabProps[] = itemStrings.map((item, index) => ({
978+
content: item,
979+
index,
980+
onAction: () => {},
981+
id: `${item}-${index}`,
982+
isLocked: index === 0,
983+
actions:
984+
index === 0
985+
? []
986+
: [
987+
{
988+
type: 'rename',
989+
onAction: () => {},
990+
onPrimaryAction: async (value: string) => {
991+
const newItemsStrings = tabs.map((item, idx) => {
992+
if (idx === index) {
993+
return value;
994+
}
995+
return item.content;
996+
});
997+
await sleep(1);
998+
setItemStrings(newItemsStrings);
999+
return true;
1000+
},
1001+
},
1002+
{
1003+
type: 'duplicate',
1004+
onPrimaryAction: async (name) => {
1005+
await sleep(1);
1006+
duplicateView(name);
1007+
return true;
1008+
},
1009+
},
1010+
{
1011+
type: 'edit',
1012+
},
1013+
{
1014+
type: 'delete',
1015+
onPrimaryAction: async (id: string) => {
1016+
await sleep(1);
1017+
deleteView(index);
1018+
return true;
1019+
},
1020+
},
1021+
],
1022+
}));
1023+
const [selected, setSelected] = useState(0);
1024+
const onCreateNewView = async (value: string) => {
1025+
await sleep(500);
1026+
setItemStrings([...itemStrings, value]);
1027+
setSelected(itemStrings.length);
1028+
return true;
1029+
};
1030+
const sortOptions: IndexFiltersProps['sortOptions'] = [
1031+
{label: 'Order', value: 'order asc', directionLabel: 'Ascending'},
1032+
{label: 'Order', value: 'order desc', directionLabel: 'Descending'},
1033+
{label: 'Customer', value: 'customer asc', directionLabel: 'A-Z'},
1034+
{label: 'Customer', value: 'customer desc', directionLabel: 'Z-A'},
1035+
{label: 'Date', value: 'date asc', directionLabel: 'A-Z'},
1036+
{label: 'Date', value: 'date desc', directionLabel: 'Z-A'},
1037+
{label: 'Total', value: 'total asc', directionLabel: 'Ascending'},
1038+
{label: 'Total', value: 'total desc', directionLabel: 'Descending'},
1039+
];
1040+
const [sortSelected, setSortSelected] = useState(['order asc']);
1041+
const {mode, setMode} = useSetIndexFiltersMode();
1042+
const onHandleCancel = () => {};
1043+
1044+
const onHandleSave = async () => {
1045+
await sleep(1);
1046+
return true;
1047+
};
1048+
1049+
const primaryAction: IndexFiltersProps['primaryAction'] =
1050+
selected === 0
1051+
? {
1052+
type: 'save-as',
1053+
onAction: onCreateNewView,
1054+
disabled: false,
1055+
loading: false,
1056+
}
1057+
: {
1058+
type: 'save',
1059+
onAction: onHandleSave,
1060+
disabled: false,
1061+
loading: false,
1062+
};
1063+
1064+
return (
1065+
<Card>
1066+
<IndexFilters
1067+
sortOptions={sortOptions}
1068+
sortSelected={sortSelected}
1069+
queryValue=""
1070+
queryPlaceholder="Searching in all"
1071+
onQueryChange={() => {}}
1072+
onQueryClear={() => {}}
1073+
onSort={setSortSelected}
1074+
primaryAction={primaryAction}
1075+
cancelAction={{
1076+
onAction: onHandleCancel,
1077+
disabled: false,
1078+
loading: false,
1079+
}}
1080+
tabs={tabs}
1081+
selected={selected}
1082+
onSelect={setSelected}
1083+
canCreateNewView
1084+
onCreateNewView={onCreateNewView}
1085+
filters={[]}
1086+
onClearAll={() => {}}
1087+
mode={mode}
1088+
setMode={setMode}
1089+
hideQueryField
1090+
hideFilters
1091+
/>
1092+
<Table />
1093+
</Card>
1094+
);
1095+
}

polaris-react/src/components/IndexFilters/IndexFilters.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -363,18 +363,20 @@ export function IndexFilters({
363363
{isLoading && !mdDown && <Spinner size="small" />}
364364
{mode === IndexFiltersMode.Default ? (
365365
<>
366-
<SearchFilterButton
367-
onClick={handleClickFilterButton}
368-
aria-label={searchFilterAriaLabel}
369-
tooltipContent={searchFilterTooltip}
370-
disabled={disabled}
371-
hideFilters={hideFilters}
372-
hideQueryField={hideQueryField}
373-
style={{
374-
...defaultStyle,
375-
...transitionStyles[state],
376-
}}
377-
/>
366+
{hideFilters && hideQueryField ? null : (
367+
<SearchFilterButton
368+
onClick={handleClickFilterButton}
369+
aria-label={searchFilterAriaLabel}
370+
tooltipContent={searchFilterTooltip}
371+
disabled={disabled}
372+
hideFilters={hideFilters}
373+
hideQueryField={hideQueryField}
374+
style={{
375+
...defaultStyle,
376+
...transitionStyles[state],
377+
}}
378+
/>
379+
)}
378380
{sortMarkup}
379381
</>
380382
) : null}

polaris.shopify.com/content/components/selection-and-input/index-filters.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ examples:
2626
- fileName: index-filters-with-no-filters.tsx
2727
title: With no filters
2828
description: An IndexFilters component with only view management, search, and sorting.
29+
- fileName: index-filters-with-no-search-or-filters.tsx
30+
title: With no search or filters
31+
description: An IndexFilters component with only view management and sorting.
2932
---
3033

3134
Merchants use filters to:

polaris.shopify.com/pages/examples/index-filters-default.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function IndexFiltersDefault() {
5252
{
5353
type: 'rename',
5454
onAction: () => {},
55-
onPrimaryAction: async (value: string) => {
55+
onPrimaryAction: async (value: string): Promise<boolean> => {
5656
const newItemsStrings = tabs.map((item, idx) => {
5757
if (idx === index) {
5858
return value;
@@ -66,9 +66,9 @@ function IndexFiltersDefault() {
6666
},
6767
{
6868
type: 'duplicate',
69-
onPrimaryAction: async (name) => {
69+
onPrimaryAction: async (value: string): Promise<boolean> => {
7070
await sleep(1);
71-
duplicateView(name);
71+
duplicateView(value);
7272
return true;
7373
},
7474
},

polaris.shopify.com/pages/examples/index-filters-disabled.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function IndexFiltersDisabled() {
5252
{
5353
type: 'rename',
5454
onAction: () => {},
55-
onPrimaryAction: async (value: string) => {
55+
onPrimaryAction: async (value: string): Promise<boolean> => {
5656
const newItemsStrings = tabs.map((item, idx) => {
5757
if (idx === index) {
5858
return value;
@@ -66,9 +66,9 @@ function IndexFiltersDisabled() {
6666
},
6767
{
6868
type: 'duplicate',
69-
onPrimaryAction: async (name) => {
69+
onPrimaryAction: async (value: string): Promise<boolean> => {
7070
await sleep(1);
71-
duplicateView(name);
71+
duplicateView(value);
7272
return true;
7373
},
7474
},

0 commit comments

Comments
 (0)