diff --git a/UNRELEASED.md b/UNRELEASED.md index 00aff651a02..3bdefd5e802 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -6,6 +6,8 @@ ### Enhancements +- Updated `Filters` to only show the "More filters" button if necessary ([#2856](https://github.com/Shopify/polaris-react/pull/2856)). + ### Bug fixes ### Documentation diff --git a/src/components/Filters/Filters.tsx b/src/components/Filters/Filters.tsx index 62bef8411c2..3f08fb817b8 100644 --- a/src/components/Filters/Filters.tsx +++ b/src/components/Filters/Filters.tsx @@ -243,12 +243,15 @@ class FiltersInner extends React.Component { plural: intl.translate('Polaris.ResourceList.defaultItemPlural'), }; + const transformedFilters = this.transformFilters(filters); + const filtersControlMarkup = ( transformedFilters.length} > { } } - private transformFilters( - filters: FilterInterface[], - ): ConnectedFilterControlProps['rightPopoverableActions'] | null { - const transformedActions: ConnectedFilterControlProps['rightPopoverableActions'] = []; + private transformFilters(filters: FilterInterface[]) { + const transformedActions: Required< + ConnectedFilterControlProps['rightPopoverableActions'] + > = []; getShortcutFilters(filters).forEach((filter) => { const {key, label, disabled} = filter; diff --git a/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.scss b/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.scss index 2bf617701c8..0f7795516fa 100644 --- a/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.scss +++ b/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.scss @@ -76,6 +76,19 @@ $stacking-order: ( } } +.RightContainerWithoutMoreFilters { + .Item:last-child > * > * { + border-top-right-radius: var( + --p-border-radius-base, + border-radius() + ) !important; + border-bottom-right-radius: var( + --p-border-radius-base, + border-radius() + ) !important; + } +} + .newDesignLanguage .RightContainer { .Item:first-of-type > * > * { border-top-left-radius: var(--p-border-radius-base) !important; diff --git a/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.tsx b/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.tsx index 2183744e128..fe074973593 100644 --- a/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.tsx +++ b/src/components/Filters/components/ConnectedFilterControl/ConnectedFilterControl.tsx @@ -25,6 +25,7 @@ export interface ConnectedFilterControlProps { rightAction?: React.ReactNode; auxiliary?: React.ReactNode; disabled?: boolean; + forceShowMorefiltersButton?: boolean; } interface ComputedProperty { @@ -74,6 +75,7 @@ export class ConnectedFilterControl extends React.Component< rightPopoverableActions, rightAction, auxiliary, + forceShowMorefiltersButton = true, } = this.props; const actionsToRender = @@ -87,11 +89,25 @@ export class ConnectedFilterControl extends React.Component< newDesignLanguage && styles.newDesignLanguage, ); - const rightMarkup = rightPopoverableActions ? ( -
- {this.popoverFrom(actionsToRender)} -
- ) : null; + const shouldRenderMoreFiltersButton = + forceShowMorefiltersButton || + (rightPopoverableActions && + rightPopoverableActions.length !== actionsToRender.length); + + const RightContainerClassName = classNames( + styles.RightContainer, + !shouldRenderMoreFiltersButton && styles.RightContainerWithoutMoreFilters, + ); + + const rightMarkup = + actionsToRender.length > 0 ? ( +
+ {this.popoverFrom(actionsToRender)} +
+ ) : null; const moreFiltersButtonContainerClassname = classNames( styles.MoreFiltersButtonContainer, @@ -105,7 +121,7 @@ export class ConnectedFilterControl extends React.Component< ref={this.moreFiltersButtonContainer} className={moreFiltersButtonContainerClassname} > - {rightAction} + {shouldRenderMoreFiltersButton && {rightAction}} ) : null; @@ -197,6 +213,10 @@ export class ConnectedFilterControl extends React.Component< if (actionWidth <= remainingWidth) { actionsToReturn.push(action); remainingWidth -= actionWidth; + } else { + // When we can't fit an action, we break the loop. + // The ones that didn't fit will be accessible through the "More filters" button + break; } } return actionsToReturn; diff --git a/src/components/Filters/components/ConnectedFilterControl/tests/ConnectedFilterControl.test.tsx b/src/components/Filters/components/ConnectedFilterControl/tests/ConnectedFilterControl.test.tsx index 39ab0592bc3..abec099a343 100644 --- a/src/components/Filters/components/ConnectedFilterControl/tests/ConnectedFilterControl.test.tsx +++ b/src/components/Filters/components/ConnectedFilterControl/tests/ConnectedFilterControl.test.tsx @@ -66,9 +66,26 @@ describe('', () => { expect(connectedFilterControl.find(Popover).exists()).toBe(false); }); - it('does render a button with a right action', () => { + it('always render a RightAction if forceShowMorefiltersButton is true', () => { const connectedFilterControl = mountWithAppProvider( - + + + , + ); + + expect(connectedFilterControl.find(Button).exists()).toBe(true); + }); + + it('renders a RightAction if forceShowMorefiltersButton is false and rightPopoverableActions do not fit on the "right action" container', () => { + const connectedFilterControl = mountWithAppProvider( + , ); @@ -76,6 +93,19 @@ describe('', () => { expect(connectedFilterControl.find(Button).exists()).toBe(true); }); + it('does not render a RightAction there are no actions hidden', () => { + const connectedFilterControl = mountWithAppProvider( + + + , + ); + + expect(connectedFilterControl.find(Button).exists()).toBe(false); + }); + it('does render a button with a popoverable action', () => { const connectedFilterControl = mountWithAppProvider( ', () => { expect(connectedFilterControl.find(Button)).toHaveLength(3); }); + it('hides an action if it does not fit', () => { + const connectedFilterControl = mountWithAppProvider( + + + , + ); + + connectedFilterControl.setState({availableWidth: 100}); + + expect(findActions(connectedFilterControl)).toHaveLength(2); + }); + it('renders auxiliary content', () => { const connectedFilterControl = mountWithAppProvider( }> @@ -171,3 +219,8 @@ function noop() {} function findById(wrapper: ReactWrapper, id: string) { return wrapper.find(`#${id}`).first(); } + +function findActions(wrapper: ReactWrapper) { + // this omits the invisible proxy actions used for measuring width + return wrapper.find('.Wrapper Button'); +} diff --git a/src/components/Filters/tests/Filters.test.tsx b/src/components/Filters/tests/Filters.test.tsx index 6732a38e8f9..349e68a5b41 100644 --- a/src/components/Filters/tests/Filters.test.tsx +++ b/src/components/Filters/tests/Filters.test.tsx @@ -218,6 +218,46 @@ describe('', () => { ).toHaveLength(2); }); + it('forces showing the "More Filters" button if there are filters without shortcuts', () => { + const resourceFilters = mountWithAppProvider( + , + ); + + expect( + resourceFilters.find(ConnectedFilterControl).props() + .forceShowMorefiltersButton, + ).toBe(true); + }); + + it('does not force showing the "More Filters" button if all the filters have shorcuts', () => { + const mockPropsWithShortcuts: FiltersProps = { + onQueryChange: noop, + onQueryClear: noop, + onClearAll: noop, + filters: [ + { + key: 'filterOne', + label: 'Filter One', + filter: , + shortcut: true, + }, + { + key: 'filterTwo', + label: 'Filter Two', + filter: , + shortcut: true, + }, + ], + }; + const resourceFilters = mountWithAppProvider( + , + ); + expect( + resourceFilters.find(ConnectedFilterControl).props() + .forceShowMorefiltersButton, + ).toBe(false); + }); + it('receives shortcut filters with popoverOpen set to false on mount', () => { const resourceFilters = mountWithAppProvider( , diff --git a/src/components/MediaCard/MediaCard.tsx b/src/components/MediaCard/MediaCard.tsx index e60ec8ad674..827acc5113b 100644 --- a/src/components/MediaCard/MediaCard.tsx +++ b/src/components/MediaCard/MediaCard.tsx @@ -4,7 +4,7 @@ import {HorizontalDotsMinor} from '@shopify/polaris-icons'; import {useToggle} from '../../utilities/use-toggle'; import {classNames} from '../../utilities/css'; import {useI18n} from '../../utilities/i18n'; -import {Action, ActionListItemDescriptor} from '../../types'; +import type {Action, ActionListItemDescriptor} from '../../types'; import {Card} from '../Card'; import {Button, buttonFrom} from '../Button'; import {Heading} from '../Heading';