Skip to content

Commit 2c486bc

Browse files
rickitantmlayton
andauthored
[Filters] only show the "More filters" button if necessary (#2856)
Co-authored-by: Tim Layton <tmlayton@users.noreply.github.com>
1 parent 69ec09d commit 2c486bc

File tree

7 files changed

+145
-14
lines changed

7 files changed

+145
-14
lines changed

UNRELEASED.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
### Enhancements
88

9+
- Updated `Filters` to only show the "More filters" button if necessary ([#2856](https://github.com/Shopify/polaris-react/pull/2856)).
10+
911
### Bug fixes
1012

1113
### Documentation

src/components/Filters/Filters.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,15 @@ class FiltersInner extends React.Component<ComposedProps, State> {
243243
plural: intl.translate('Polaris.ResourceList.defaultItemPlural'),
244244
};
245245

246+
const transformedFilters = this.transformFilters(filters);
247+
246248
const filtersControlMarkup = (
247249
<ConnectedFilterControl
248-
rightPopoverableActions={this.transformFilters(filters)}
250+
rightPopoverableActions={transformedFilters}
249251
rightAction={rightActionMarkup}
250252
auxiliary={children}
251253
disabled={disabled}
254+
forceShowMorefiltersButton={filters.length > transformedFilters.length}
252255
>
253256
<TextField
254257
placeholder={
@@ -513,10 +516,10 @@ class FiltersInner extends React.Component<ComposedProps, State> {
513516
}
514517
}
515518

516-
private transformFilters(
517-
filters: FilterInterface[],
518-
): ConnectedFilterControlProps['rightPopoverableActions'] | null {
519-
const transformedActions: ConnectedFilterControlProps['rightPopoverableActions'] = [];
519+
private transformFilters(filters: FilterInterface[]) {
520+
const transformedActions: Required<
521+
ConnectedFilterControlProps['rightPopoverableActions']
522+
> = [];
520523

521524
getShortcutFilters(filters).forEach((filter) => {
522525
const {key, label, disabled} = filter;

src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ $stacking-order: (
7676
}
7777
}
7878

79+
.RightContainerWithoutMoreFilters {
80+
.Item:last-child > * > * {
81+
border-top-right-radius: var(
82+
--p-border-radius-base,
83+
border-radius()
84+
) !important;
85+
border-bottom-right-radius: var(
86+
--p-border-radius-base,
87+
border-radius()
88+
) !important;
89+
}
90+
}
91+
7992
.newDesignLanguage .RightContainer {
8093
.Item:first-of-type > * > * {
8194
border-top-left-radius: var(--p-border-radius-base) !important;

src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface ConnectedFilterControlProps {
2525
rightAction?: React.ReactNode;
2626
auxiliary?: React.ReactNode;
2727
disabled?: boolean;
28+
forceShowMorefiltersButton?: boolean;
2829
}
2930

3031
interface ComputedProperty {
@@ -74,6 +75,7 @@ export class ConnectedFilterControl extends React.Component<
7475
rightPopoverableActions,
7576
rightAction,
7677
auxiliary,
78+
forceShowMorefiltersButton = true,
7779
} = this.props;
7880

7981
const actionsToRender =
@@ -87,11 +89,25 @@ export class ConnectedFilterControl extends React.Component<
8789
newDesignLanguage && styles.newDesignLanguage,
8890
);
8991

90-
const rightMarkup = rightPopoverableActions ? (
91-
<div className={styles.RightContainer} testID="FilterShortcutContainer">
92-
{this.popoverFrom(actionsToRender)}
93-
</div>
94-
) : null;
92+
const shouldRenderMoreFiltersButton =
93+
forceShowMorefiltersButton ||
94+
(rightPopoverableActions &&
95+
rightPopoverableActions.length !== actionsToRender.length);
96+
97+
const RightContainerClassName = classNames(
98+
styles.RightContainer,
99+
!shouldRenderMoreFiltersButton && styles.RightContainerWithoutMoreFilters,
100+
);
101+
102+
const rightMarkup =
103+
actionsToRender.length > 0 ? (
104+
<div
105+
className={RightContainerClassName}
106+
testID="FilterShortcutContainer"
107+
>
108+
{this.popoverFrom(actionsToRender)}
109+
</div>
110+
) : null;
95111

96112
const moreFiltersButtonContainerClassname = classNames(
97113
styles.MoreFiltersButtonContainer,
@@ -105,7 +121,7 @@ export class ConnectedFilterControl extends React.Component<
105121
ref={this.moreFiltersButtonContainer}
106122
className={moreFiltersButtonContainerClassname}
107123
>
108-
<Item>{rightAction}</Item>
124+
{shouldRenderMoreFiltersButton && <Item>{rightAction}</Item>}
109125
</div>
110126
) : null;
111127

@@ -197,6 +213,10 @@ export class ConnectedFilterControl extends React.Component<
197213
if (actionWidth <= remainingWidth) {
198214
actionsToReturn.push(action);
199215
remainingWidth -= actionWidth;
216+
} else {
217+
// When we can't fit an action, we break the loop.
218+
// The ones that didn't fit will be accessible through the "More filters" button
219+
break;
200220
}
201221
}
202222
return actionsToReturn;

src/components/Filters/components/ConnectedFilterControl/tests/ConnectedFilterControl.test.tsx

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,46 @@ describe('<ConnectedFilterControl />', () => {
6666
expect(connectedFilterControl.find(Popover).exists()).toBe(false);
6767
});
6868

69-
it('does render a button with a right action', () => {
69+
it('always render a RightAction if forceShowMorefiltersButton is true', () => {
7070
const connectedFilterControl = mountWithAppProvider(
71-
<ConnectedFilterControl rightAction={mockRightAction}>
71+
<ConnectedFilterControl
72+
rightAction={mockRightAction}
73+
forceShowMorefiltersButton
74+
>
75+
<MockChild />
76+
</ConnectedFilterControl>,
77+
);
78+
79+
expect(connectedFilterControl.find(Button).exists()).toBe(true);
80+
});
81+
82+
it('renders a RightAction if forceShowMorefiltersButton is false and rightPopoverableActions do not fit on the "right action" container', () => {
83+
const connectedFilterControl = mountWithAppProvider(
84+
<ConnectedFilterControl
85+
rightAction={mockRightAction}
86+
rightPopoverableActions={[mockRightOpenPopoverableAction]}
87+
forceShowMorefiltersButton={false}
88+
>
7289
<MockChild />
7390
</ConnectedFilterControl>,
7491
);
7592

7693
expect(connectedFilterControl.find(Button).exists()).toBe(true);
7794
});
7895

96+
it('does not render a RightAction there are no actions hidden', () => {
97+
const connectedFilterControl = mountWithAppProvider(
98+
<ConnectedFilterControl
99+
rightAction={mockRightAction}
100+
forceShowMorefiltersButton={false}
101+
>
102+
<MockChild />
103+
</ConnectedFilterControl>,
104+
);
105+
106+
expect(connectedFilterControl.find(Button).exists()).toBe(false);
107+
});
108+
79109
it('does render a button with a popoverable action', () => {
80110
const connectedFilterControl = mountWithAppProvider(
81111
<ConnectedFilterControl
@@ -104,6 +134,24 @@ describe('<ConnectedFilterControl />', () => {
104134
expect(connectedFilterControl.find(Button)).toHaveLength(3);
105135
});
106136

137+
it('hides an action if it does not fit', () => {
138+
const connectedFilterControl = mountWithAppProvider(
139+
<ConnectedFilterControl
140+
rightPopoverableActions={[
141+
mockRightOpenPopoverableAction,
142+
mockRightClosedPopoverableAction,
143+
]}
144+
rightAction={mockRightAction}
145+
>
146+
<MockChild />
147+
</ConnectedFilterControl>,
148+
);
149+
150+
connectedFilterControl.setState({availableWidth: 100});
151+
152+
expect(findActions(connectedFilterControl)).toHaveLength(2);
153+
});
154+
107155
it('renders auxiliary content', () => {
108156
const connectedFilterControl = mountWithAppProvider(
109157
<ConnectedFilterControl auxiliary={<MockAux />}>
@@ -171,3 +219,8 @@ function noop() {}
171219
function findById(wrapper: ReactWrapper, id: string) {
172220
return wrapper.find(`#${id}`).first();
173221
}
222+
223+
function findActions(wrapper: ReactWrapper) {
224+
// this omits the invisible proxy actions used for measuring width
225+
return wrapper.find('.Wrapper Button');
226+
}

src/components/Filters/tests/Filters.test.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,46 @@ describe('<Filters />', () => {
218218
).toHaveLength(2);
219219
});
220220

221+
it('forces showing the "More Filters" button if there are filters without shortcuts', () => {
222+
const resourceFilters = mountWithAppProvider(
223+
<Filters {...mockPropsWithShortcuts} />,
224+
);
225+
226+
expect(
227+
resourceFilters.find(ConnectedFilterControl).props()
228+
.forceShowMorefiltersButton,
229+
).toBe(true);
230+
});
231+
232+
it('does not force showing the "More Filters" button if all the filters have shorcuts', () => {
233+
const mockPropsWithShortcuts: FiltersProps = {
234+
onQueryChange: noop,
235+
onQueryClear: noop,
236+
onClearAll: noop,
237+
filters: [
238+
{
239+
key: 'filterOne',
240+
label: 'Filter One',
241+
filter: <MockFilter id="filterOne" />,
242+
shortcut: true,
243+
},
244+
{
245+
key: 'filterTwo',
246+
label: 'Filter Two',
247+
filter: <MockFilter id="filterTwo" />,
248+
shortcut: true,
249+
},
250+
],
251+
};
252+
const resourceFilters = mountWithAppProvider(
253+
<Filters {...mockPropsWithShortcuts} />,
254+
);
255+
expect(
256+
resourceFilters.find(ConnectedFilterControl).props()
257+
.forceShowMorefiltersButton,
258+
).toBe(false);
259+
});
260+
221261
it('receives shortcut filters with popoverOpen set to false on mount', () => {
222262
const resourceFilters = mountWithAppProvider(
223263
<Filters {...mockPropsWithShortcuts} />,

src/components/MediaCard/MediaCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {HorizontalDotsMinor} from '@shopify/polaris-icons';
44
import {useToggle} from '../../utilities/use-toggle';
55
import {classNames} from '../../utilities/css';
66
import {useI18n} from '../../utilities/i18n';
7-
import {Action, ActionListItemDescriptor} from '../../types';
7+
import type {Action, ActionListItemDescriptor} from '../../types';
88
import {Card} from '../Card';
99
import {Button, buttonFrom} from '../Button';
1010
import {Heading} from '../Heading';

0 commit comments

Comments
 (0)