Skip to content
This repository was archived by the owner on Sep 30, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/serious-vans-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/polaris': minor
---

[FiltersBar] Fixed bug where filters would disappear from the FiltersBar when clicking the Clear all button
201 changes: 201 additions & 0 deletions polaris-react/src/components/Filters/Filters.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1516,3 +1516,204 @@ export function WithFilterBarHidden() {
</div>
);
}

export function WithAllFiltersPinned() {
const [accountStatus, setAccountStatus] = useState(null);
const [moneySpent, setMoneySpent] = useState(null);
const [taggedWith, setTaggedWith] = useState('');
const [queryValue, setQueryValue] = useState('');

const handleAccountStatusChange = useCallback(
(value) => setAccountStatus(value),
[],
);
const handleMoneySpentChange = useCallback(
(value) => setMoneySpent(value),
[],
);
const handleTaggedWithChange = useCallback(
(value) => setTaggedWith(value),
[],
);
const handleFiltersQueryChange = useCallback(
(value) => setQueryValue(value),
[],
);
const handleAccountStatusRemove = useCallback(
() => setAccountStatus(null),
[],
);
const handleMoneySpentRemove = useCallback(() => setMoneySpent(null), []);
const handleTaggedWithRemove = useCallback(() => setTaggedWith(''), []);
const handleQueryValueRemove = useCallback(() => setQueryValue(''), []);
const handleFiltersClearAll = useCallback(() => {
handleAccountStatusRemove();
handleMoneySpentRemove();
handleTaggedWithRemove();
handleQueryValueRemove();
}, [
handleAccountStatusRemove,
handleMoneySpentRemove,
handleQueryValueRemove,
handleTaggedWithRemove,
]);

const filters = [
{
key: 'accountStatus',
label: 'Account status',
filter: (
<ChoiceList
title="Account status"
titleHidden
choices={[
{label: 'Enabled', value: 'enabled'},
{label: 'Not invited', value: 'not invited'},
{label: 'Invited', value: 'invited'},
{label: 'Declined', value: 'declined'},
]}
selected={accountStatus || []}
onChange={handleAccountStatusChange}
allowMultiple
/>
),
shortcut: true,
pinned: true,
},
{
key: 'taggedWith',
label: 'Tagged with',
filter: (
<TextField
label="Tagged with"
value={taggedWith}
onChange={handleTaggedWithChange}
autoComplete="off"
labelHidden
/>
),
shortcut: true,
pinned: true,
},
{
key: 'moneySpent',
label: 'Money spent',
filter: (
<RangeSlider
label="Money spent is between"
labelHidden
value={moneySpent || [0, 500]}
prefix="$"
output
min={0}
max={2000}
step={1}
onChange={handleMoneySpentChange}
/>
),
shortcut: true,
pinned: true,
},
];

const appliedFilters: FiltersProps['appliedFilters'] = [];
if (!isEmpty(accountStatus)) {
const key = 'accountStatus';
appliedFilters.push({
key,
label: disambiguateLabel(key, accountStatus),
onRemove: handleAccountStatusRemove,
});
}
if (!isEmpty(moneySpent)) {
const key = 'moneySpent';
appliedFilters.push({
key,
label: disambiguateLabel(key, moneySpent),
onRemove: handleMoneySpentRemove,
});
}
if (!isEmpty(taggedWith)) {
const key = 'taggedWith';
appliedFilters.push({
key,
label: disambiguateLabel(key, taggedWith),
onRemove: handleTaggedWithRemove,
});
}

return (
<div style={{height: '568px'}}>
<LegacyCard>
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
filterControl={
<Filters
queryValue={queryValue}
queryPlaceholder="Searching in all"
filters={filters}
appliedFilters={appliedFilters}
onQueryChange={handleFiltersQueryChange}
onQueryClear={handleQueryValueRemove}
onClearAll={handleFiltersClearAll}
/>
}
flushFilters
items={[
{
id: '341',
url: '#',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: '256',
url: '#',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
]}
renderItem={(item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="md" name={name} />;

return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<Text as="h3" fontWeight="bold">
{name}
</Text>
<div>{location}</div>
</ResourceList.Item>
);
}}
/>
</LegacyCard>
</div>
);

function disambiguateLabel(key, value) {
switch (key) {
case 'moneySpent':
return `Money spent is between $${value[0]} and $${value[1]}`;
case 'taggedWith':
return `Tagged with ${value}`;
case 'accountStatus':
return value.map((val) => `Customer ${val}`).join(', ');
default:
return value;
}
}

function isEmpty(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else {
return value === '' || value == null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export function FiltersBar({
};
const appliedFilterKeys = appliedFilters?.map(({key}) => key);

const pinnedFromPropsKeys = filters
.filter(({pinned}) => pinned)
.map(({key}) => key);

const pinnedFiltersFromPropsAndAppliedFilters = filters.filter(
({pinned, key}) => {
const isPinnedOrApplied =
Expand Down Expand Up @@ -182,17 +186,23 @@ export function FiltersBar({
);

const handleClearAllFilters = () => {
setLocalPinnedFilters([]);
setLocalPinnedFilters(pinnedFromPropsKeys);
onClearAll?.();
};
const shouldShowAddButton = filters.some((filter) => !filter.pinned);
const shouldShowAddButton =
filters.some((filter) => !filter.pinned) ||
filters.length !== localPinnedFilters.length;

const pinnedFiltersMarkup = pinnedFilters.map(
({key: filterKey, ...pinnedFilter}) => {
const appliedFilter = appliedFilters?.find(({key}) => key === filterKey);
const handleFilterPillRemove = () => {
setLocalPinnedFilters((currentLocalPinnedFilters) =>
currentLocalPinnedFilters.filter((key) => key !== filterKey),
currentLocalPinnedFilters.filter((key) => {
const isMatchedFilters = key === filterKey;
const isPinnedFilterFromProps = pinnedFromPropsKeys.includes(key);
return !isMatchedFilters || isPinnedFilterFromProps;
}),
);
appliedFilter?.onRemove(filterKey);
};
Expand Down Expand Up @@ -236,26 +246,25 @@ export function FiltersBar({
</div>
) : null;

const clearAllMarkup =
appliedFilters?.length || localPinnedFilters.length ? (
<div
className={classNames(
styles.ClearAll,
hasOneOrMorePinnedFilters &&
shouldShowAddButton &&
styles.MultiplePinnedFilterClearAll,
)}
const clearAllMarkup = appliedFilters?.length ? (
<div
className={classNames(
styles.ClearAll,
hasOneOrMorePinnedFilters &&
shouldShowAddButton &&
styles.MultiplePinnedFilterClearAll,
)}
>
<Button
size="micro"
onClick={handleClearAllFilters}
removeUnderline
variant="monochromePlain"
>
<Button
size="micro"
onClick={handleClearAllFilters}
removeUnderline
variant="monochromePlain"
>
{i18n.translate('Polaris.Filters.clearFilters')}
</Button>
</div>
) : null;
{i18n.translate('Polaris.Filters.clearFilters')}
</Button>
</div>
) : null;

return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {ActionList} from '../../../../ActionList';
import {FiltersBar} from '../FiltersBar';
import type {FiltersBarProps} from '../FiltersBar';
import {FilterPill} from '../../FilterPill';
import {Button} from '../../../../Button';

describe('<FiltersBar />', () => {
let originalScroll: any;
Expand Down Expand Up @@ -388,4 +389,58 @@ describe('<FiltersBar />', () => {
],
});
});

it('will keep a pinned filter from props pinned when clearing', () => {
const appliedFilters = [
{
...defaultProps.filters[1],
label: 'Bar 2',
value: ['Bar 2'],
onRemove: jest.fn(),
},
];
const scrollSpy = jest.fn();
HTMLElement.prototype.scroll = scrollSpy;
const wrapper = mountWithApp(
<FiltersBar {...defaultProps} appliedFilters={appliedFilters} />,
);

wrapper
.find(FilterPill, {
label: 'Bar 2',
})!
.trigger('onRemove');

expect(wrapper).toContainReactComponentTimes(FilterPill, 1);
});

it('will keep a pinned filter from props pinned when clearing all', () => {
const appliedFilters = [
{
...defaultProps.filters[0],
label: 'Bar 2',
value: ['Bar 2'],
onRemove: jest.fn(),
},
{
...defaultProps.filters[2],
label: 'Bar 2',
value: ['Bar 2'],
onRemove: jest.fn(),
},
];
const scrollSpy = jest.fn();
HTMLElement.prototype.scroll = scrollSpy;
const wrapper = mountWithApp(
<FiltersBar {...defaultProps} appliedFilters={appliedFilters} />,
);

wrapper
.find(Button, {
children: 'Clear all',
})!
.trigger('onClick');

expect(wrapper).toContainReactComponentTimes(FilterPill, 1);
});
});