diff --git a/.changeset/lazy-hornets-train.md b/.changeset/lazy-hornets-train.md new file mode 100644 index 00000000000..f2103c69573 --- /dev/null +++ b/.changeset/lazy-hornets-train.md @@ -0,0 +1,5 @@ +--- +'@shopify/polaris': minor +--- + +Added `allowFiltering` prop on `ActionList`, and `filterActions` prop on Page Header diff --git a/polaris-react/src/components/ActionList/ActionList.stories.tsx b/polaris-react/src/components/ActionList/ActionList.stories.tsx index c8c59bff71d..27aed5951dc 100644 --- a/polaris-react/src/components/ActionList/ActionList.stories.tsx +++ b/polaris-react/src/components/ActionList/ActionList.stories.tsx @@ -390,6 +390,7 @@ export function WithFiltering() {
({ content: `Item #${index + 1}`, }))} diff --git a/polaris-react/src/components/ActionList/ActionList.tsx b/polaris-react/src/components/ActionList/ActionList.tsx index 5ea8a908d46..cfb20301610 100644 --- a/polaris-react/src/components/ActionList/ActionList.tsx +++ b/polaris-react/src/components/ActionList/ActionList.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useRef, useState} from 'react'; +import React, {useContext, useMemo, useRef, useState} from 'react'; import type {ActionListItemDescriptor, ActionListSection} from '../../types'; import {Key} from '../../types'; @@ -6,12 +6,13 @@ import { wrapFocusNextFocusableMenuItem, wrapFocusPreviousFocusableMenuItem, } from '../../utilities/focus'; +import {useI18n} from '../../utilities/i18n'; import {Box} from '../Box'; import {KeypressListener} from '../KeypressListener'; -import {useI18n} from '../../utilities/i18n'; +import {FilterActionsContext} from '../FilterActionsProvider'; -import {SearchField, Item, Section} from './components'; import type {ItemProps} from './components'; +import {Item, SearchField, Section} from './components'; export interface ActionListProps { /** Collection of actions for list */ @@ -20,20 +21,25 @@ export interface ActionListProps { sections?: readonly ActionListSection[]; /** Defines a specific role attribute for each action in the list */ actionRole?: 'menuitem' | string; + /** Allow users to filter items in the list. Will only show if more than 8 items in the list. The item content of every items must be a string for this to work */ + allowFiltering?: boolean; /** Callback when any item is clicked or keypressed */ onActionAnyItem?: ActionListItemDescriptor['onAction']; } +const FILTER_ACTIONS_THRESHOLD = 8; + export type ActionListItemProps = ItemProps; export function ActionList({ items, sections = [], actionRole, + allowFiltering, onActionAnyItem, }: ActionListProps) { const i18n = useI18n(); - + const filterActions = useContext(FilterActionsContext); let finalSections: readonly ActionListSection[] = []; const actionListRef = useRef(null); const [searchText, setSeachText] = useState(''); @@ -116,12 +122,6 @@ export function ActionList({ ) : null; - const totalActions = - finalSections?.reduce( - (acc: number, section) => acc + section.items.length, - 0, - ) || 0; - const totalFilteredActions = useMemo(() => { const totalSectionItems = filteredSections?.reduce( @@ -132,11 +132,17 @@ export function ActionList({ return totalSectionItems; }, [filteredSections]); - const showSearch = totalActions >= 8; + const totalActions = + finalSections?.reduce( + (acc: number, section) => acc + section.items.length, + 0, + ) || 0; + + const hasManyActions = totalActions >= FILTER_ACTIONS_THRESHOLD; return ( <> - {showSearch && isFilterable && ( + {(allowFiltering || filterActions) && hasManyActions && isFilterable && ( 0 ? '0' : '2'}> ', () => { it('does not render search with 7 or less items', () => { const actionList = mountWithApp( ', () => { it('renders search with 8 or more items', () => { const actionList = mountWithApp( ', () => { expect(actionList).toContainReactComponentTimes(SearchField, 1); }); - it('renders search with 10 or more items or section items', () => { + it('does not renders search with 8 and no allowFiltering', () => { + const actionList = mountWithApp( + , + ); + + expect(actionList).not.toContainReactComponentTimes(SearchField, 1); + }); + + it('renders search with 8 or more items or section items', () => { const actionList = mountWithApp( ', () => { const actionList = mountWithApp( (false); + +type FilterActionsProviderProps = PropsWithChildren<{ + filterActions: boolean; +}>; + +export function FilterActionsProvider({ + children, + filterActions, +}: FilterActionsProviderProps) { + return ( + + {children} + + ); +} diff --git a/polaris-react/src/components/FilterActionsProvider/index.ts b/polaris-react/src/components/FilterActionsProvider/index.ts new file mode 100644 index 00000000000..d5f2b313195 --- /dev/null +++ b/polaris-react/src/components/FilterActionsProvider/index.ts @@ -0,0 +1 @@ +export * from './FilterActionsProvider'; diff --git a/polaris-react/src/components/Page/Page.tsx b/polaris-react/src/components/Page/Page.tsx index 71d7e62c13b..f5a552faf6f 100644 --- a/polaris-react/src/components/Page/Page.tsx +++ b/polaris-react/src/components/Page/Page.tsx @@ -48,7 +48,9 @@ export function Page({ divider && hasHeaderContent && styles.divider, ); - const headerMarkup = hasHeaderContent ?
: null; + const headerMarkup = hasHeaderContent ? ( +
+ ) : null; return (
diff --git a/polaris-react/src/components/Page/components/Header/Header.tsx b/polaris-react/src/components/Page/components/Header/Header.tsx index 720c7941bea..ffba84660e0 100644 --- a/polaris-react/src/components/Page/components/Header/Header.tsx +++ b/polaris-react/src/components/Page/components/Header/Header.tsx @@ -29,6 +29,7 @@ import {isReactElement} from '../../../../utilities/is-react-element'; import {Box} from '../../../Box'; import {HorizontalStack} from '../../../HorizontalStack'; import {useFeatures} from '../../../../utilities/features'; +import {FilterActionsProvider} from '../../../FilterActionsProvider'; import {Title} from './components'; import type {TitleProps} from './components'; @@ -49,6 +50,8 @@ interface PrimaryAction export interface HeaderProps extends TitleProps { /** Visually hide the title */ titleHidden?: boolean; + /** Enables filtering action list items */ + filterActions?: boolean; /** Primary page-level action */ primaryAction?: PrimaryAction | React.ReactNode; /** Page-level pagination */ @@ -79,6 +82,7 @@ export function Header({ titleHidden = false, primaryAction, pagination, + filterActions, additionalNavigation, backAction, secondaryActions = [], @@ -229,35 +233,37 @@ export function Header({ visuallyHidden={titleHidden} >
- -
- {slot1} - {slot2} - -
- ( -
{children}
- )} - > - {slot3} - {slot4} -
-
-
-
-
- -
- {slot5} - -
{slot6}
-
-
-
+ + +
+ {slot1} + {slot2} + +
+ ( +
{children}
+ )} + > + {slot3} + {slot4} +
+
+
+
+
+ +
+ {slot5} + +
{slot6}
+
+
+
+
);