From b1f2fcdf041f86d9cbaa492ed39aec70d0b6af0f Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Fri, 15 Sep 2023 11:59:29 +0100 Subject: [PATCH 1/8] chore: use context provider for index filters state --- .../components/AppProvider/AppProvider.tsx | 3 +- .../IndexFilters/IndexFilters.stories.tsx | 15 +++++++-- .../components/IndexFilters/IndexFilters.tsx | 2 +- .../useIsSticky/tests/useIsSticky.test.tsx | 2 +- .../hooks/useIsSticky/useIsSticky.ts | 2 +- .../useSetIndexFiltersMode.tsx | 9 +++--- .../IndexFilters/tests/IndexFilters.test.tsx | 3 +- .../src/components/IndexFilters/types.ts | 6 ---- .../IndexFiltersManager.tsx | 31 +++++++++++++++++++ .../components/IndexFiltersManager/index.ts | 1 + polaris-react/src/components/index.ts | 1 - polaris-react/src/index.ts | 12 ++++--- .../src/utilities/index-filters/context.tsx | 12 +++++++ .../src/utilities/index-filters/hooks.ts | 28 +++++++++++++++++ .../src/utilities/index-filters/index.ts | 3 ++ .../src/utilities/index-filters/types.ts | 5 +++ 16 files changed, 112 insertions(+), 23 deletions(-) create mode 100644 polaris-react/src/components/IndexFiltersManager/IndexFiltersManager.tsx create mode 100644 polaris-react/src/components/IndexFiltersManager/index.ts delete mode 100644 polaris-react/src/components/index.ts create mode 100644 polaris-react/src/utilities/index-filters/context.tsx create mode 100644 polaris-react/src/utilities/index-filters/hooks.ts create mode 100644 polaris-react/src/utilities/index-filters/index.ts create mode 100644 polaris-react/src/utilities/index-filters/types.ts diff --git a/polaris-react/src/components/AppProvider/AppProvider.tsx b/polaris-react/src/components/AppProvider/AppProvider.tsx index 17a3d3d142b..21e3884b5b3 100644 --- a/polaris-react/src/components/AppProvider/AppProvider.tsx +++ b/polaris-react/src/components/AppProvider/AppProvider.tsx @@ -3,6 +3,7 @@ import type {ThemeName} from '@shopify/polaris-tokens'; import {themeNameDefault} from '@shopify/polaris-tokens'; import {EphemeralPresenceManager} from '../EphemeralPresenceManager'; +import {IndexFiltersManager} from '../IndexFiltersManager'; import {MediaQueryProvider} from '../MediaQueryProvider'; import {FocusManager} from '../FocusManager'; import {PortalsManager} from '../PortalsManager'; @@ -179,7 +180,7 @@ export class AppProvider extends Component { - {children} + {children} diff --git a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx index cd49d87b73f..8f9a9cf0a02 100644 --- a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx +++ b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx @@ -7,6 +7,7 @@ import { useIndexResourceState, IndexTable, IndexFilters, + IndexFiltersMode, RangeSlider, TextField, Card, @@ -95,7 +96,11 @@ function Table() { ); } -function BasicExample(props?: Partial) { +function BasicExample( + props?: Partial & { + withFilteringByDefault?: boolean; + }, +) { const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const [itemStrings, setItemStrings] = useState([ @@ -184,7 +189,9 @@ function BasicExample(props?: Partial) { {label: 'Total', value: 'total desc', directionLabel: 'Descending'}, ]; const [sortSelected, setSortSelected] = useState(['order asc']); - const {mode, setMode} = useSetIndexFiltersMode(); + const {mode, setMode} = useSetIndexFiltersMode( + props?.withFilteringByDefault ? IndexFiltersMode.Filtering : undefined, + ); const onHandleCancel = () => {}; const onHandleSave = async () => { @@ -385,6 +392,10 @@ export function Default() { return ; } +export function WithFilteringByDefault() { + return ; +} + export function WithoutKeyboardShortcuts() { return ; } diff --git a/polaris-react/src/components/IndexFilters/IndexFilters.tsx b/polaris-react/src/components/IndexFilters/IndexFilters.tsx index b91ab5d2008..557a4e0aa84 100644 --- a/polaris-react/src/components/IndexFilters/IndexFilters.tsx +++ b/polaris-react/src/components/IndexFilters/IndexFilters.tsx @@ -14,6 +14,7 @@ import {Tabs} from '../Tabs'; import type {TabsProps} from '../Tabs'; import {useBreakpoints} from '../../utilities/breakpoints'; import {useFeatures} from '../../utilities/features'; +import {IndexFiltersMode} from '../../utilities/index-filters'; import {useIsSticky} from './hooks'; import { @@ -27,7 +28,6 @@ import type { IndexFiltersCancelAction, SortButtonChoice, } from './types'; -import {IndexFiltersMode} from './types'; import styles from './IndexFilters.scss'; const DEFAULT_IGNORED_TAGS = ['INPUT', 'SELECT', 'TEXTAREA']; diff --git a/polaris-react/src/components/IndexFilters/hooks/useIsSticky/tests/useIsSticky.test.tsx b/polaris-react/src/components/IndexFilters/hooks/useIsSticky/tests/useIsSticky.test.tsx index 3903f388d49..b1d2211b70e 100644 --- a/polaris-react/src/components/IndexFilters/hooks/useIsSticky/tests/useIsSticky.test.tsx +++ b/polaris-react/src/components/IndexFilters/hooks/useIsSticky/tests/useIsSticky.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {intersectionObserver} from '@shopify/jest-dom-mocks'; import {mountWithApp} from 'tests/utilities'; -import {IndexFiltersMode} from '../../../types'; +import {IndexFiltersMode} from '../../../../../utilities/index-filters'; import {useIsSticky} from '..'; interface Props { diff --git a/polaris-react/src/components/IndexFilters/hooks/useIsSticky/useIsSticky.ts b/polaris-react/src/components/IndexFilters/hooks/useIsSticky/useIsSticky.ts index 22fc3165db1..8e07d4c3ab0 100644 --- a/polaris-react/src/components/IndexFilters/hooks/useIsSticky/useIsSticky.ts +++ b/polaris-react/src/components/IndexFilters/hooks/useIsSticky/useIsSticky.ts @@ -2,7 +2,7 @@ import {useEffect, useRef, useState} from 'react'; import type {RefObject} from 'react'; import {debounce} from '../../../../utilities/debounce'; -import type {IndexFiltersMode} from '../../types'; +import type {IndexFiltersMode} from '../../../../utilities/index-filters'; const DEBOUNCE_PERIOD = 250; diff --git a/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx b/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx index d3aaf23ec1b..24b3fcf781f 100644 --- a/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx +++ b/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx @@ -1,11 +1,12 @@ -import {useState} from 'react'; - -import {IndexFiltersMode} from '../../types'; +import { + useIndexFiltersManager, + IndexFiltersMode, +} from '../../../../utilities/index-filters'; export function useSetIndexFiltersMode( defaultMode: IndexFiltersMode = IndexFiltersMode.Default, ) { - const [mode, setMode] = useState(defaultMode); + const {mode, setMode} = useIndexFiltersManager(defaultMode); return {mode, setMode}; } diff --git a/polaris-react/src/components/IndexFilters/tests/IndexFilters.test.tsx b/polaris-react/src/components/IndexFilters/tests/IndexFilters.test.tsx index db9a05c422b..1eb9e2b6373 100644 --- a/polaris-react/src/components/IndexFilters/tests/IndexFilters.test.tsx +++ b/polaris-react/src/components/IndexFilters/tests/IndexFilters.test.tsx @@ -5,7 +5,8 @@ import {matchMedia} from '@shopify/jest-dom-mocks'; import {Tabs} from '../../Tabs'; import {Filters} from '../../Filters'; -import {IndexFilters, IndexFiltersMode} from '..'; +import {IndexFilters} from '..'; +import {IndexFiltersMode} from '../../../utilities/index-filters'; import type {IndexFiltersProps} from '../IndexFilters'; import {SearchFilterButton, SortButton, UpdateButtons} from '../components'; diff --git a/polaris-react/src/components/IndexFilters/types.ts b/polaris-react/src/components/IndexFilters/types.ts index 7ef04c71daa..d1cd35e299b 100644 --- a/polaris-react/src/components/IndexFilters/types.ts +++ b/polaris-react/src/components/IndexFilters/types.ts @@ -19,9 +19,3 @@ export interface IndexFiltersCancelAction { disabled?: boolean; loading?: boolean; } - -export enum IndexFiltersMode { - Default = 'DEFAULT', - Filtering = 'FILTERING', - EditingColumns = 'EDITING_COLUMNS', -} diff --git a/polaris-react/src/components/IndexFiltersManager/IndexFiltersManager.tsx b/polaris-react/src/components/IndexFiltersManager/IndexFiltersManager.tsx new file mode 100644 index 00000000000..7b210c8cd7d --- /dev/null +++ b/polaris-react/src/components/IndexFiltersManager/IndexFiltersManager.tsx @@ -0,0 +1,31 @@ +import type {ContextType} from 'react'; +import React, {useMemo, useState} from 'react'; + +import { + IndexFiltersContext, + IndexFiltersMode, +} from '../../utilities/index-filters'; + +export interface IndexFiltersManagerProps { + children?: React.ReactNode; +} + +type Context = NonNullable>; + +export function IndexFiltersManager({children}: IndexFiltersManagerProps) { + const [mode, setMode] = useState(IndexFiltersMode.Default); + + const value = useMemo( + () => ({ + mode, + setMode, + }), + [mode, setMode], + ); + + return ( + + {children} + + ); +} diff --git a/polaris-react/src/components/IndexFiltersManager/index.ts b/polaris-react/src/components/IndexFiltersManager/index.ts new file mode 100644 index 00000000000..9379e76c59d --- /dev/null +++ b/polaris-react/src/components/IndexFiltersManager/index.ts @@ -0,0 +1 @@ +export {IndexFiltersManager} from './IndexFiltersManager'; diff --git a/polaris-react/src/components/index.ts b/polaris-react/src/components/index.ts deleted file mode 100644 index 1eb3969feed..00000000000 --- a/polaris-react/src/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {EphemeralPresenceManager} from './EphemeralPresenceManager'; diff --git a/polaris-react/src/index.ts b/polaris-react/src/index.ts index 0fdb30948db..bf612fe9c08 100644 --- a/polaris-react/src/index.ts +++ b/polaris-react/src/index.ts @@ -188,16 +188,14 @@ export type {IconProps} from './components/Icon'; export {Image} from './components/Image'; export type {ImageProps} from './components/Image'; -export { - IndexFilters, - useSetIndexFiltersMode, - IndexFiltersMode, -} from './components/IndexFilters'; +export {IndexFilters, useSetIndexFiltersMode} from './components/IndexFilters'; export type { IndexFiltersProps, SortButtonChoice, } from './components/IndexFilters'; +export {IndexFiltersManager} from './components/IndexFiltersManager'; + export {IndexTable} from './components/IndexTable'; export type {IndexTableProps} from './components/IndexTable'; @@ -425,6 +423,10 @@ export {WithinContentContext as _SECRET_INTERNAL_WITHIN_CONTENT_CONTEXT} from '. export {useEventListener} from './utilities/use-event-listener'; export {useTheme} from './utilities/use-theme'; export {useIndexResourceState} from './utilities/use-index-resource-state'; +export { + useIndexFiltersManager, + IndexFiltersMode, +} from './utilities/index-filters'; export { useRowHovered as useIndexTableRowHovered, useRowSelected as useIndexTableRowSelected, diff --git a/polaris-react/src/utilities/index-filters/context.tsx b/polaris-react/src/utilities/index-filters/context.tsx new file mode 100644 index 00000000000..5b6dea6e732 --- /dev/null +++ b/polaris-react/src/utilities/index-filters/context.tsx @@ -0,0 +1,12 @@ +import {createContext} from 'react'; + +import type {IndexFiltersMode} from './types'; + +export interface IndexFiltersContextType { + mode: IndexFiltersMode; + setMode: (mode: IndexFiltersMode) => void; +} + +export const IndexFiltersContext = createContext< + IndexFiltersContextType | undefined +>(undefined); diff --git a/polaris-react/src/utilities/index-filters/hooks.ts b/polaris-react/src/utilities/index-filters/hooks.ts new file mode 100644 index 00000000000..e4551610817 --- /dev/null +++ b/polaris-react/src/utilities/index-filters/hooks.ts @@ -0,0 +1,28 @@ +import {useContext, useEffect} from 'react'; + +import {IndexFiltersContext} from './context'; +import {IndexFiltersMode} from './types'; + +export function useIndexFiltersManager( + defaultMode: IndexFiltersMode = IndexFiltersMode.Default, +) { + const indexFiltersManager = useContext(IndexFiltersContext); + + if (!indexFiltersManager) { + throw new Error( + 'No index filters manager was provided. Your application must be wrapped in an component. See https://polaris.shopify.com/components/app-provider for implementation instructions.', + ); + } + + const {mode, setMode} = indexFiltersManager; + + useEffect(() => { + setMode(defaultMode); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultMode]); + + return { + mode, + setMode, + }; +} diff --git a/polaris-react/src/utilities/index-filters/index.ts b/polaris-react/src/utilities/index-filters/index.ts new file mode 100644 index 00000000000..a16d669ed30 --- /dev/null +++ b/polaris-react/src/utilities/index-filters/index.ts @@ -0,0 +1,3 @@ +export * from './hooks'; +export * from './context'; +export * from './types'; diff --git a/polaris-react/src/utilities/index-filters/types.ts b/polaris-react/src/utilities/index-filters/types.ts new file mode 100644 index 00000000000..9cde25ffa08 --- /dev/null +++ b/polaris-react/src/utilities/index-filters/types.ts @@ -0,0 +1,5 @@ +export enum IndexFiltersMode { + Default = 'DEFAULT', + Filtering = 'FILTERING', + EditingColumns = 'EDITING_COLUMNS', +} From a5d2132515e99aac41df498b9b3bb5827fe5f9bb Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Fri, 15 Sep 2023 14:11:39 +0100 Subject: [PATCH 2/8] chore: automatically disable page header actions --- .../IndexFilters/IndexFilters.stories.tsx | 296 ++++++++++++------ .../Page/components/Header/Header.tsx | 30 +- 2 files changed, 234 insertions(+), 92 deletions(-) diff --git a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx index 8f9a9cf0a02..09a3d2ea639 100644 --- a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx +++ b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx @@ -11,8 +11,18 @@ import { RangeSlider, TextField, Card, + Page, + Badge, + Button, + useIndexFiltersManager, + Frame, IndexFiltersMode, } from '@shopify/polaris'; +import { + ViewMinor, + DeleteMinor, + MobileVerticalDotsMajor, +} from '@shopify/polaris-icons'; import {useSetIndexFiltersMode} from './hooks'; import type {IndexFiltersProps} from './IndexFilters'; @@ -334,36 +344,38 @@ function BasicExample( } return ( - - setQueryValue('')} - onSort={setSortSelected} - primaryAction={primaryAction} - cancelAction={{ - onAction: onHandleCancel, - disabled: false, - loading: false, - }} - tabs={tabs} - selected={selected} - onSelect={setSelected} - canCreateNewView - onCreateNewView={onCreateNewView} - filters={filters} - appliedFilters={appliedFilters} - onClearAll={handleFiltersClearAll} - mode={mode} - setMode={setMode} - /> - - + + + setQueryValue('')} + onSort={setSortSelected} + primaryAction={primaryAction} + cancelAction={{ + onAction: onHandleCancel, + disabled: false, + loading: false, + }} + tabs={tabs} + selected={selected} + onSelect={setSelected} + canCreateNewView + onCreateNewView={onCreateNewView} + filters={filters} + appliedFilters={appliedFilters} + onClearAll={handleFiltersClearAll} + mode={mode} + setMode={setMode} + /> +
+ + ); function disambiguateLabel(key, value) { @@ -1204,35 +1216,37 @@ export function Disabled() { } return ( - - {}} - onSort={setSortSelected} - primaryAction={primaryAction} - cancelAction={{ - onAction: onHandleCancel, - disabled: false, - loading: false, - }} - tabs={tabs} - selected={selected} - onSelect={setSelected} - canCreateNewView - onCreateNewView={onCreateNewView} - filters={filters} - appliedFilters={appliedFilters} - onClearAll={handleFiltersClearAll} - mode={mode} - setMode={setMode} - disabled - /> -
- + + + {}} + onSort={setSortSelected} + primaryAction={primaryAction} + cancelAction={{ + onAction: onHandleCancel, + disabled: false, + loading: false, + }} + tabs={tabs} + selected={selected} + onSelect={setSelected} + canCreateNewView + onCreateNewView={onCreateNewView} + filters={filters} + appliedFilters={appliedFilters} + onClearAll={handleFiltersClearAll} + mode={mode} + setMode={setMode} + disabled + /> +
+ + ); function disambiguateLabel(key, value) { @@ -1370,34 +1384,140 @@ export function WithQueryFieldAndFiltersHidden() { }; return ( - - {}} - onQueryClear={() => {}} - onSort={setSortSelected} - primaryAction={primaryAction} - cancelAction={{ - onAction: onHandleCancel, - disabled: false, - loading: false, - }} - tabs={tabs} - selected={selected} - onSelect={setSelected} - canCreateNewView - onCreateNewView={onCreateNewView} - filters={[]} - onClearAll={() => {}} - mode={mode} - setMode={setMode} - hideQueryField - hideFilters - /> -
- + + + {}} + onQueryClear={() => {}} + onSort={setSortSelected} + primaryAction={primaryAction} + cancelAction={{ + onAction: onHandleCancel, + disabled: false, + loading: false, + }} + tabs={tabs} + selected={selected} + onSelect={setSelected} + canCreateNewView + onCreateNewView={onCreateNewView} + filters={[]} + onClearAll={() => {}} + mode={mode} + setMode={setMode} + hideQueryField + hideFilters + /> +
+ + + ); +} + +export function WrappedInAPage() { + return ( + Paid} + subtitle="Perfect for any pet" + compactTitle + primaryAction={{content: 'Save'}} + secondaryActions={[ + { + content: 'Delete', + destructive: true, + icon: DeleteMinor, + accessibilityLabel: 'Delete action label', + onAction: () => console.log('Delete action'), + }, + { + content: 'View on your store', + icon: ViewMinor, + onAction: () => console.log('View on your store action'), + }, + ]} + actionGroups={[ + { + title: 'Promote', + icon: MobileVerticalDotsMajor, + actions: [ + { + content: 'Share on Facebook', + accessibilityLabel: 'Individual action label', + onAction: () => console.log('Share on Facebook action'), + }, + ], + }, + ]} + pagination={{ + hasPrevious: true, + hasNext: true, + }} + > + + + ); +} + +export function WrappedInAPageWithCustomActions() { + const {mode} = useIndexFiltersManager(); + const shouldDisableAction = mode !== IndexFiltersMode.Default; + return ( + Paid} + subtitle="Perfect for any pet" + compactTitle + primaryAction={ + + } + secondaryActions={ + + } + actionGroups={[ + { + title: 'Promote', + icon: MobileVerticalDotsMajor, + actions: [ + { + content: 'Share on Facebook', + accessibilityLabel: 'Individual action label', + onAction: () => console.log('Share on Facebook action'), + }, + ], + }, + ]} + pagination={{ + hasPrevious: true, + hasNext: true, + }} + > + + ); } diff --git a/polaris-react/src/components/Page/components/Header/Header.tsx b/polaris-react/src/components/Page/components/Header/Header.tsx index ffba84660e0..c2ea1e1e4ed 100644 --- a/polaris-react/src/components/Page/components/Header/Header.tsx +++ b/polaris-react/src/components/Page/components/Header/Header.tsx @@ -26,6 +26,10 @@ import type {PaginationProps} from '../../../Pagination'; import {ActionMenu, hasGroupsWithActions} from '../../../ActionMenu'; import {isInterface} from '../../../../utilities/is-interface'; import {isReactElement} from '../../../../utilities/is-react-element'; +import { + IndexFiltersMode, + useIndexFiltersManager, +} from '../../../../utilities/index-filters'; import {Box} from '../../../Box'; import {HorizontalStack} from '../../../HorizontalStack'; import {useFeatures} from '../../../../utilities/features'; @@ -93,6 +97,8 @@ export function Header({ const i18n = useI18n(); const {polarisSummerEditions2023} = useFeatures(); const {isNavigationCollapsed} = useMediaQuery(); + const {mode} = useIndexFiltersManager(); + const shouldDisableActions = mode !== IndexFiltersMode.Default; if (additionalNavigation && process.env.NODE_ENV === 'development') { // eslint-disable-next-line no-console @@ -124,7 +130,11 @@ export function Header({ pagination && !isNavigationCollapsed ? (
- +
) : null; @@ -147,7 +157,10 @@ export function Header({ ); const primaryActionMarkup = primaryAction ? ( - + ) : null; let actionMenuMarkup: MaybeJSX = null; @@ -157,8 +170,14 @@ export function Header({ ) { actionMenuMarkup = ( ({ + ...secondaryAction, + disabled: shouldDisableActions || secondaryAction.disabled, + }))} + groups={actionGroups.map((actionGroup) => ({ + ...actionGroup, + disabled: shouldDisableActions || actionGroup.disabled, + }))} rollup={isNavigationCollapsed} rollupActionsLabel={ title @@ -271,8 +290,10 @@ export function Header({ function PrimaryActionMarkup({ primaryAction, + shouldOverrideDisableAction, }: { primaryAction: PrimaryAction | React.ReactNode; + shouldOverrideDisableAction: boolean; }) { const {isNavigationCollapsed} = useMediaQuery(); @@ -284,6 +305,7 @@ function PrimaryActionMarkup({ shouldShowIconOnly(isNavigationCollapsed, primaryAction), { primary, + disabled: shouldOverrideDisableAction ?? primaryAction.disabled, }, ); From 7b3f2fa9ce5da15e8c29071578556341bf8cf623 Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Fri, 15 Sep 2023 14:12:39 +0100 Subject: [PATCH 3/8] chore: add changeset --- .changeset/lucky-wombats-push.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lucky-wombats-push.md diff --git a/.changeset/lucky-wombats-push.md b/.changeset/lucky-wombats-push.md new file mode 100644 index 00000000000..ca2b7fddbce --- /dev/null +++ b/.changeset/lucky-wombats-push.md @@ -0,0 +1,5 @@ +--- +'@shopify/polaris': minor +--- + +Add new `IndexFiltersManager` for allowing disabling of Page Header actions when in Filtering or EditingColumns mode From 16535d44719a1aecb09d7a0c1c68586e37891209 Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Fri, 15 Sep 2023 16:22:36 +0100 Subject: [PATCH 4/8] chore: more exploration --- .../tests/IndexFiltersManager.test.tsx | 41 +++++++++++++ .../components/Header/tests/Header.test.tsx | 60 +++++++++++++++++++ .../PolarisTestProvider.tsx | 21 ++++++- .../index-filters/tests/hooks.test.tsx | 32 ++++++++++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx create mode 100644 polaris-react/src/utilities/index-filters/tests/hooks.test.tsx diff --git a/polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx b/polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx new file mode 100644 index 00000000000..cbde26521ac --- /dev/null +++ b/polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import {mountWithApp} from 'tests/utilities'; + +import { + useIndexFiltersManager, + IndexFiltersMode, +} from '../../../utilities/index-filters'; + +function Component() { + const {mode, setMode} = useIndexFiltersManager(); + + return ( + <> + + + +
{mode}
+ + ); +} + +describe('', () => { + it('returns a value of Default on mount', () => { + const wrapper = mountWithApp(); + expect(wrapper).toContainReactComponent('div', { + children: IndexFiltersMode.Default, + }); + }); +}); diff --git a/polaris-react/src/components/Page/components/Header/tests/Header.test.tsx b/polaris-react/src/components/Page/components/Header/tests/Header.test.tsx index 6eba736a8c1..b69084d7ae7 100644 --- a/polaris-react/src/components/Page/components/Header/tests/Header.test.tsx +++ b/polaris-react/src/components/Page/components/Header/tests/Header.test.tsx @@ -12,6 +12,7 @@ import {Tooltip} from '../../../../Tooltip'; import type {LinkAction, MenuActionDescriptor} from '../../../../../types'; import {Header} from '../Header'; import type {HeaderProps} from '../Header'; +import {IndexFiltersMode} from '../../../../../utilities/index-filters'; describe('
', () => { const mockProps: HeaderProps = { @@ -115,6 +116,26 @@ describe('
', () => { }); }); + it('renders a disabled button when a non-default IndexFiltersMode is set', () => { + const primaryAction: HeaderProps['primaryAction'] = { + content: buttonContent, + }; + + const header = mountWithApp( +
, + { + indexFilters: { + mode: IndexFiltersMode.Filtering, + }, + }, + ); + + expect(header).toContainReactComponent(Button, { + disabled: true, + children: buttonContent, + }); + }); + it('renders a `ReactNode`', () => { const PrimaryAction = () => null; @@ -153,6 +174,27 @@ describe('
', () => { hasNext: true, }); }); + + it('adds false values for hasNext and hasPrevious when a non-default IndexFiltersMode is set', () => { + const pagination = { + hasNext: true, + hasPrevious: true, + }; + + const header = mountWithApp( +
, + { + indexFilters: { + mode: IndexFiltersMode.Filtering, + }, + }, + ); + + expect(header).toContainReactComponent(Pagination, { + hasNext: false, + hasPrevious: false, + }); + }); }); describe('actionGroups', () => { @@ -180,6 +222,24 @@ describe('
', () => { groups: mockActionGroups, }); }); + + it('disables actions within the actionGroups when a non-default IndexFiltersMode is set', () => { + const wrapper = mountWithApp( +
, + { + indexFilters: { + mode: IndexFiltersMode.Filtering, + }, + }, + ); + + expect(wrapper).toContainReactComponent(ActionMenu, { + groups: mockActionGroups.map((actionGroup) => ({ + ...actionGroup, + disabled: true, + })), + }); + }); }); describe('additionalNavigation', () => { diff --git a/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx b/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx index 3078d25240b..0ac83c5189c 100644 --- a/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx +++ b/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx @@ -19,6 +19,11 @@ import type {LinkLikeComponent} from '../../utilities/link'; import {FeaturesContext} from '../../utilities/features'; import type {FeaturesConfig} from '../../utilities/features'; import {EphemeralPresenceManager} from '../EphemeralPresenceManager'; +import { + IndexFiltersMode, + IndexFiltersContext, +} from '../../utilities/index-filters'; +import type {IndexFiltersContextType} from '../../utilities/index-filters'; type FrameContextType = NonNullable>; type MediaQueryContextType = NonNullable< @@ -38,6 +43,8 @@ export interface WithPolarisTestProviderOptions { features?: FeaturesConfig; // Contexts provided by Frame frame?: Partial; + // Contexts provided by IndexFilters + indexFilters?: Partial; } export interface PolarisTestProviderProps @@ -50,6 +57,11 @@ const defaultMediaQuery: MediaQueryContextType = { isNavigationCollapsed: false, }; +const defaultIndexFilters: IndexFiltersContextType = { + mode: IndexFiltersMode.Default, + setMode: noop, +}; + export function PolarisTestProvider({ strict, children, @@ -58,6 +70,7 @@ export function PolarisTestProvider({ mediaQuery, features, frame, + indexFilters, }: PolarisTestProviderProps) { const Wrapper = strict ? StrictMode : Fragment; const intl = useMemo(() => new I18n(i18n || {}), [i18n]); @@ -77,6 +90,8 @@ export function PolarisTestProvider({ const mergedMediaQuery = merge(defaultMediaQuery, mediaQuery); + const mergedIndexFilters = merge(defaultIndexFilters, indexFilters); + return ( @@ -89,7 +104,11 @@ export function PolarisTestProvider({ - {children} + + {children} + diff --git a/polaris-react/src/utilities/index-filters/tests/hooks.test.tsx b/polaris-react/src/utilities/index-filters/tests/hooks.test.tsx new file mode 100644 index 00000000000..5774e2f6cd8 --- /dev/null +++ b/polaris-react/src/utilities/index-filters/tests/hooks.test.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import {mountWithApp} from 'tests/utilities'; + +import {useIndexFiltersManager} from '../hooks'; +import {IndexFiltersContext} from '../context'; +import {IndexFiltersMode} from '../types'; + +describe('useIndexFiltersManager', () => { + it('returns mode from the provider', () => { + const spy = jest.fn(); + + function MockComponent() { + const value = useIndexFiltersManager(); + spy(value); + return null; + } + + mountWithApp( + + + , + ); + + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + mode: IndexFiltersMode.Default, + }), + ); + }); +}); From 189e35798a63be47802f7eeb4222560a3a6f46e9 Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Mon, 18 Sep 2023 18:01:30 +0100 Subject: [PATCH 5/8] chore: refactor --- .../components/AppProvider/AppProvider.tsx | 2 +- .../tests/useSetIndexFiltersMode.test.tsx | 38 ++++++++++ .../useSetIndexFiltersMode.tsx | 9 ++- .../components/IndexFiltersManager/index.ts | 1 - .../tests/IndexFiltersManager.test.tsx | 41 ---------- polaris-react/src/index.ts | 3 +- .../index-filters}/IndexFiltersManager.tsx | 6 +- .../src/utilities/index-filters/hooks.ts | 12 +-- .../src/utilities/index-filters/index.ts | 1 + .../tests/IndexFiltersManager.test.tsx | 75 +++++++++++++++++++ 10 files changed, 128 insertions(+), 60 deletions(-) create mode 100644 polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/tests/useSetIndexFiltersMode.test.tsx delete mode 100644 polaris-react/src/components/IndexFiltersManager/index.ts delete mode 100644 polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx rename polaris-react/src/{components/IndexFiltersManager => utilities/index-filters}/IndexFiltersManager.tsx (86%) create mode 100644 polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx diff --git a/polaris-react/src/components/AppProvider/AppProvider.tsx b/polaris-react/src/components/AppProvider/AppProvider.tsx index 21e3884b5b3..bc5a13ff884 100644 --- a/polaris-react/src/components/AppProvider/AppProvider.tsx +++ b/polaris-react/src/components/AppProvider/AppProvider.tsx @@ -3,7 +3,6 @@ import type {ThemeName} from '@shopify/polaris-tokens'; import {themeNameDefault} from '@shopify/polaris-tokens'; import {EphemeralPresenceManager} from '../EphemeralPresenceManager'; -import {IndexFiltersManager} from '../IndexFiltersManager'; import {MediaQueryProvider} from '../MediaQueryProvider'; import {FocusManager} from '../FocusManager'; import {PortalsManager} from '../PortalsManager'; @@ -13,6 +12,7 @@ import { ScrollLockManager, ScrollLockManagerContext, } from '../../utilities/scroll-lock-manager'; +import {IndexFiltersManager} from '../../utilities/index-filters'; import { StickyManager, StickyManagerContext, diff --git a/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/tests/useSetIndexFiltersMode.test.tsx b/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/tests/useSetIndexFiltersMode.test.tsx new file mode 100644 index 00000000000..a4ba0cef45a --- /dev/null +++ b/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/tests/useSetIndexFiltersMode.test.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import {mountWithApp} from 'tests/utilities'; + +import {useSetIndexFiltersMode} from '../useSetIndexFiltersMode'; +import { + IndexFiltersMode, + useIndexFiltersManager, +} from '../../../../../utilities/index-filters'; + +jest.mock('../../../../../utilities/index-filters', () => { + return { + ...jest.requireActual('../../../../../utilities/index-filters'), + useIndexFiltersManager: jest.fn().mockReturnValue({ + mode: 'Default', + setMode: jest.fn(), + }), + }; +}); + +describe('useSetIndexFiltersMode', () => { + it('calls setMode with defaultMode inside useEffect', () => { + const defaultMode = IndexFiltersMode.EditingColumns; + const setModeMock = jest.fn(); + (useIndexFiltersManager as jest.Mock).mockReturnValueOnce({ + mode: 'Default', + setMode: setModeMock, + }); + + function MockComponent() { + useSetIndexFiltersMode(defaultMode); + return null; + } + mountWithApp(); + + expect(setModeMock).toHaveBeenCalledTimes(1); + expect(setModeMock).toHaveBeenCalledWith(defaultMode); + }); +}); diff --git a/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx b/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx index 24b3fcf781f..ebec996885b 100644 --- a/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx +++ b/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx @@ -1,3 +1,5 @@ +import {useEffect} from 'react'; + import { useIndexFiltersManager, IndexFiltersMode, @@ -6,7 +8,12 @@ import { export function useSetIndexFiltersMode( defaultMode: IndexFiltersMode = IndexFiltersMode.Default, ) { - const {mode, setMode} = useIndexFiltersManager(defaultMode); + const {mode, setMode} = useIndexFiltersManager(); + + useEffect(() => { + setMode(defaultMode); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultMode]); return {mode, setMode}; } diff --git a/polaris-react/src/components/IndexFiltersManager/index.ts b/polaris-react/src/components/IndexFiltersManager/index.ts deleted file mode 100644 index 9379e76c59d..00000000000 --- a/polaris-react/src/components/IndexFiltersManager/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {IndexFiltersManager} from './IndexFiltersManager'; diff --git a/polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx b/polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx deleted file mode 100644 index cbde26521ac..00000000000 --- a/polaris-react/src/components/IndexFiltersManager/tests/IndexFiltersManager.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import {mountWithApp} from 'tests/utilities'; - -import { - useIndexFiltersManager, - IndexFiltersMode, -} from '../../../utilities/index-filters'; - -function Component() { - const {mode, setMode} = useIndexFiltersManager(); - - return ( - <> - - - -
{mode}
- - ); -} - -describe('', () => { - it('returns a value of Default on mount', () => { - const wrapper = mountWithApp(); - expect(wrapper).toContainReactComponent('div', { - children: IndexFiltersMode.Default, - }); - }); -}); diff --git a/polaris-react/src/index.ts b/polaris-react/src/index.ts index bf612fe9c08..3c5e6c8ea50 100644 --- a/polaris-react/src/index.ts +++ b/polaris-react/src/index.ts @@ -194,8 +194,6 @@ export type { SortButtonChoice, } from './components/IndexFilters'; -export {IndexFiltersManager} from './components/IndexFiltersManager'; - export {IndexTable} from './components/IndexTable'; export type {IndexTableProps} from './components/IndexTable'; @@ -426,6 +424,7 @@ export {useIndexResourceState} from './utilities/use-index-resource-state'; export { useIndexFiltersManager, IndexFiltersMode, + IndexFiltersManager, } from './utilities/index-filters'; export { useRowHovered as useIndexTableRowHovered, diff --git a/polaris-react/src/components/IndexFiltersManager/IndexFiltersManager.tsx b/polaris-react/src/utilities/index-filters/IndexFiltersManager.tsx similarity index 86% rename from polaris-react/src/components/IndexFiltersManager/IndexFiltersManager.tsx rename to polaris-react/src/utilities/index-filters/IndexFiltersManager.tsx index 7b210c8cd7d..9f2f1ca98ec 100644 --- a/polaris-react/src/components/IndexFiltersManager/IndexFiltersManager.tsx +++ b/polaris-react/src/utilities/index-filters/IndexFiltersManager.tsx @@ -1,10 +1,8 @@ import type {ContextType} from 'react'; import React, {useMemo, useState} from 'react'; -import { - IndexFiltersContext, - IndexFiltersMode, -} from '../../utilities/index-filters'; +import {IndexFiltersContext} from './context'; +import {IndexFiltersMode} from './types'; export interface IndexFiltersManagerProps { children?: React.ReactNode; diff --git a/polaris-react/src/utilities/index-filters/hooks.ts b/polaris-react/src/utilities/index-filters/hooks.ts index e4551610817..447e9637c89 100644 --- a/polaris-react/src/utilities/index-filters/hooks.ts +++ b/polaris-react/src/utilities/index-filters/hooks.ts @@ -1,11 +1,8 @@ -import {useContext, useEffect} from 'react'; +import {useContext} from 'react'; import {IndexFiltersContext} from './context'; -import {IndexFiltersMode} from './types'; -export function useIndexFiltersManager( - defaultMode: IndexFiltersMode = IndexFiltersMode.Default, -) { +export function useIndexFiltersManager() { const indexFiltersManager = useContext(IndexFiltersContext); if (!indexFiltersManager) { @@ -16,11 +13,6 @@ export function useIndexFiltersManager( const {mode, setMode} = indexFiltersManager; - useEffect(() => { - setMode(defaultMode); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultMode]); - return { mode, setMode, diff --git a/polaris-react/src/utilities/index-filters/index.ts b/polaris-react/src/utilities/index-filters/index.ts index a16d669ed30..31b84d9d2b9 100644 --- a/polaris-react/src/utilities/index-filters/index.ts +++ b/polaris-react/src/utilities/index-filters/index.ts @@ -1,3 +1,4 @@ export * from './hooks'; export * from './context'; export * from './types'; +export * from './IndexFiltersManager'; diff --git a/polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx b/polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx new file mode 100644 index 00000000000..cf1bea52c0c --- /dev/null +++ b/polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import {mountWithApp} from 'tests/utilities'; + +import {IndexFiltersMode} from '../types'; +import {IndexFiltersManager} from '../IndexFiltersManager'; +import {useIndexFiltersManager} from '../hooks'; + +describe('', () => { + it('renders children with the context value', () => { + const ChildComponent = () => { + const {mode, setMode} = useIndexFiltersManager(); + return ( +
+ {mode} + + + +
+ ); + }; + + const wrapper = mountWithApp( + + + , + ); + + const modeElement = wrapper.find('span', { + id: 'mode', + }); + const defaultButton = wrapper.find('button', { + id: 'default', + }); + const filteringButton = wrapper.find('button', { + id: 'filtering', + }); + const editingButton = wrapper.find('button', { + id: 'editing-columns', + }); + expect(modeElement?.text()).toBe(IndexFiltersMode.Default); + + wrapper.act(() => { + filteringButton?.trigger('onClick'); + }); + + expect(modeElement?.text()).toBe(IndexFiltersMode.Filtering); + + wrapper.act(() => { + editingButton?.trigger('onClick'); + }); + + expect(modeElement?.text()).toBe(IndexFiltersMode.EditingColumns); + + wrapper.act(() => { + defaultButton?.trigger('onClick'); + }); + + expect(modeElement?.text()).toBe(IndexFiltersMode.Default); + }); +}); From 3af9a351ba04ccc87324126d6c6abf662530795a Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Mon, 18 Sep 2023 18:05:22 +0100 Subject: [PATCH 6/8] chore: remove redundant frame --- .../IndexFilters/IndexFilters.stories.tsx | 424 +++--------------- .../src/utilities/index-filters/hooks.ts | 7 +- 2 files changed, 66 insertions(+), 365 deletions(-) diff --git a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx index 09a3d2ea639..e52660adcfd 100644 --- a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx +++ b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx @@ -7,7 +7,6 @@ import { useIndexResourceState, IndexTable, IndexFilters, - IndexFiltersMode, RangeSlider, TextField, Card, @@ -15,7 +14,6 @@ import { Badge, Button, useIndexFiltersManager, - Frame, IndexFiltersMode, } from '@shopify/polaris'; import { @@ -344,38 +342,36 @@ function BasicExample( } return ( - - - setQueryValue('')} - onSort={setSortSelected} - primaryAction={primaryAction} - cancelAction={{ - onAction: onHandleCancel, - disabled: false, - loading: false, - }} - tabs={tabs} - selected={selected} - onSelect={setSelected} - canCreateNewView - onCreateNewView={onCreateNewView} - filters={filters} - appliedFilters={appliedFilters} - onClearAll={handleFiltersClearAll} - mode={mode} - setMode={setMode} - /> -
- - + + setQueryValue('')} + onSort={setSortSelected} + primaryAction={primaryAction} + cancelAction={{ + onAction: onHandleCancel, + disabled: false, + loading: false, + }} + tabs={tabs} + selected={selected} + onSelect={setSelected} + canCreateNewView + onCreateNewView={onCreateNewView} + filters={filters} + appliedFilters={appliedFilters} + onClearAll={handleFiltersClearAll} + mode={mode} + setMode={setMode} + /> +
+ ); function disambiguateLabel(key, value) { @@ -698,7 +694,7 @@ export function WithPinnedFilters() { } } -export function WithNoPinnedAndPrefilledFilters() { +export function Disabled() { const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const [itemStrings, setItemStrings] = useState([ @@ -787,7 +783,7 @@ export function WithNoPinnedAndPrefilledFilters() { {label: 'Total', value: 'total desc', directionLabel: 'Descending'}, ]; const [sortSelected, setSortSelected] = useState(['order asc']); - const {mode, setMode} = useSetIndexFiltersMode(IndexFiltersMode.Filtering); + const {mode, setMode} = useSetIndexFiltersMode(); const onHandleCancel = () => {}; const onHandleSave = async () => { @@ -809,11 +805,9 @@ export function WithNoPinnedAndPrefilledFilters() { disabled: false, loading: false, }; - const [accountStatus, setAccountStatus] = useState([ - 'enabled', - ]); + const [accountStatus, setAccountStatus] = useState(null); const [moneySpent, setMoneySpent] = useState(null); - const [taggedWith, setTaggedWith] = useState('Returning customer'); + const [taggedWith, setTaggedWith] = useState(''); const [queryValue, setQueryValue] = useState(''); const handleAccountStatusChange = useCallback( @@ -870,7 +864,7 @@ export function WithNoPinnedAndPrefilledFilters() { allowMultiple /> ), - pinned: false, + shortcut: true, }, { key: 'taggedWith', @@ -884,7 +878,7 @@ export function WithNoPinnedAndPrefilledFilters() { labelHidden /> ), - pinned: false, + shortcut: true, }, { key: 'moneySpent', @@ -957,6 +951,7 @@ export function WithNoPinnedAndPrefilledFilters() { onClearAll={handleFiltersClearAll} mode={mode} setMode={setMode} + disabled />
@@ -984,7 +979,7 @@ export function WithNoPinnedAndPrefilledFilters() { } } -export function Disabled() { +export function WithQueryFieldAndFiltersHidden() { const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const [itemStrings, setItemStrings] = useState([ @@ -1095,326 +1090,37 @@ export function Disabled() { disabled: false, loading: false, }; - 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: ( - - ), - shortcut: true, - }, - { - key: 'taggedWith', - label: 'Tagged with', - filter: ( - - ), - shortcut: true, - }, - { - key: 'moneySpent', - label: 'Money spent', - filter: ( - - ), - }, - ]; - - const appliedFilters: IndexFiltersProps['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 ( - - - {}} - onSort={setSortSelected} - primaryAction={primaryAction} - cancelAction={{ - onAction: onHandleCancel, - disabled: false, - loading: false, - }} - tabs={tabs} - selected={selected} - onSelect={setSelected} - canCreateNewView - onCreateNewView={onCreateNewView} - filters={filters} - appliedFilters={appliedFilters} - onClearAll={handleFiltersClearAll} - mode={mode} - setMode={setMode} - disabled - /> -
- - - ); - - 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; - } - } -} - -export function WithQueryFieldAndFiltersHidden() { - const sleep = (ms: number) => - new Promise((resolve) => setTimeout(resolve, ms)); - const [itemStrings, setItemStrings] = useState([ - 'All', - 'Unpaid', - 'Open', - 'Closed', - 'Local delivery', - 'Local pickup', - ]); - const deleteView = (index: number) => { - const newItemStrings = [...itemStrings]; - newItemStrings.splice(index, 1); - setItemStrings(newItemStrings); - setSelected(0); - }; - - const duplicateView = async (name: string) => { - setItemStrings([...itemStrings, name]); - setSelected(itemStrings.length); - await sleep(1); - return true; - }; - - const tabs: TabProps[] = itemStrings.map((item, index) => ({ - content: item, - index, - onAction: () => {}, - id: `${item}-${index}`, - isLocked: index === 0, - actions: - index === 0 - ? [] - : [ - { - type: 'rename', - onAction: () => {}, - onPrimaryAction: async (value: string) => { - const newItemsStrings = tabs.map((item, idx) => { - if (idx === index) { - return value; - } - return item.content; - }); - await sleep(1); - setItemStrings(newItemsStrings); - return true; - }, - }, - { - type: 'duplicate', - onPrimaryAction: async (name) => { - await sleep(1); - duplicateView(name); - return true; - }, - }, - { - type: 'edit', - }, - { - type: 'delete', - onPrimaryAction: async (id: string) => { - await sleep(1); - deleteView(index); - return true; - }, - }, - ], - })); - const [selected, setSelected] = useState(0); - const onCreateNewView = async (value: string) => { - await sleep(500); - setItemStrings([...itemStrings, value]); - setSelected(itemStrings.length); - return true; - }; - const sortOptions: IndexFiltersProps['sortOptions'] = [ - {label: 'Order', value: 'order asc', directionLabel: 'Ascending'}, - {label: 'Order', value: 'order desc', directionLabel: 'Descending'}, - {label: 'Customer', value: 'customer asc', directionLabel: 'A-Z'}, - {label: 'Customer', value: 'customer desc', directionLabel: 'Z-A'}, - {label: 'Date', value: 'date asc', directionLabel: 'A-Z'}, - {label: 'Date', value: 'date desc', directionLabel: 'Z-A'}, - {label: 'Total', value: 'total asc', directionLabel: 'Ascending'}, - {label: 'Total', value: 'total desc', directionLabel: 'Descending'}, - ]; - const [sortSelected, setSortSelected] = useState(['order asc']); - const {mode, setMode} = useSetIndexFiltersMode(); - const onHandleCancel = () => {}; - - const onHandleSave = async () => { - await sleep(1); - return true; - }; - - const primaryAction: IndexFiltersProps['primaryAction'] = - selected === 0 - ? { - type: 'save-as', - onAction: onCreateNewView, - disabled: false, - loading: false, - } - : { - type: 'save', - onAction: onHandleSave, + + {}} + onQueryClear={() => {}} + onSort={setSortSelected} + primaryAction={primaryAction} + cancelAction={{ + onAction: onHandleCancel, disabled: false, loading: false, - }; - - return ( - - - {}} - onQueryClear={() => {}} - onSort={setSortSelected} - primaryAction={primaryAction} - cancelAction={{ - onAction: onHandleCancel, - disabled: false, - loading: false, - }} - tabs={tabs} - selected={selected} - onSelect={setSelected} - canCreateNewView - onCreateNewView={onCreateNewView} - filters={[]} - onClearAll={() => {}} - mode={mode} - setMode={setMode} - hideQueryField - hideFilters - /> -
- - + }} + tabs={tabs} + selected={selected} + onSelect={setSelected} + canCreateNewView + onCreateNewView={onCreateNewView} + filters={[]} + onClearAll={() => {}} + mode={mode} + setMode={setMode} + hideQueryField + hideFilters + /> +
+ ); } diff --git a/polaris-react/src/utilities/index-filters/hooks.ts b/polaris-react/src/utilities/index-filters/hooks.ts index 447e9637c89..fe039d82955 100644 --- a/polaris-react/src/utilities/index-filters/hooks.ts +++ b/polaris-react/src/utilities/index-filters/hooks.ts @@ -11,10 +11,5 @@ export function useIndexFiltersManager() { ); } - const {mode, setMode} = indexFiltersManager; - - return { - mode, - setMode, - }; + return indexFiltersManager; } From bf4fa71e2936c9329ea2da0e0e3e001df901a366 Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Wed, 20 Sep 2023 15:34:17 +0100 Subject: [PATCH 7/8] chore: refactor useSetIndexFiltersMode --- .../IndexFilters/IndexFilters.stories.tsx | 5 +-- .../components/IndexFilters/hooks/index.ts | 1 - .../hooks/useSetIndexFiltersMode/index.ts | 1 - .../tests/useSetIndexFiltersMode.test.tsx | 38 ------------------ .../useSetIndexFiltersMode.tsx | 19 --------- .../Page/components/Header/Header.tsx | 22 +++++----- .../PolarisTestProvider.tsx | 12 +++--- polaris-react/src/index.ts | 4 +- .../index-filters/IndexFiltersManager.tsx | 8 ++-- .../src/utilities/index-filters/context.tsx | 6 +-- .../src/utilities/index-filters/hooks.ts | 25 +++++++++--- .../tests/IndexFiltersManager.test.tsx | 4 +- .../index-filters/tests/hooks.test.tsx | 40 +++++++++++++------ 13 files changed, 77 insertions(+), 108 deletions(-) delete mode 100644 polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/index.ts delete mode 100644 polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/tests/useSetIndexFiltersMode.test.tsx delete mode 100644 polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx diff --git a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx index e52660adcfd..f30b136577e 100644 --- a/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx +++ b/polaris-react/src/components/IndexFilters/IndexFilters.stories.tsx @@ -13,7 +13,7 @@ import { Page, Badge, Button, - useIndexFiltersManager, + useSetIndexFiltersMode, IndexFiltersMode, } from '@shopify/polaris'; import { @@ -22,7 +22,6 @@ import { MobileVerticalDotsMajor, } from '@shopify/polaris-icons'; -import {useSetIndexFiltersMode} from './hooks'; import type {IndexFiltersProps} from './IndexFilters'; export default { @@ -1171,7 +1170,7 @@ export function WrappedInAPage() { } export function WrappedInAPageWithCustomActions() { - const {mode} = useIndexFiltersManager(); + const {mode} = useSetIndexFiltersMode(); const shouldDisableAction = mode !== IndexFiltersMode.Default; return ( { - return { - ...jest.requireActual('../../../../../utilities/index-filters'), - useIndexFiltersManager: jest.fn().mockReturnValue({ - mode: 'Default', - setMode: jest.fn(), - }), - }; -}); - -describe('useSetIndexFiltersMode', () => { - it('calls setMode with defaultMode inside useEffect', () => { - const defaultMode = IndexFiltersMode.EditingColumns; - const setModeMock = jest.fn(); - (useIndexFiltersManager as jest.Mock).mockReturnValueOnce({ - mode: 'Default', - setMode: setModeMock, - }); - - function MockComponent() { - useSetIndexFiltersMode(defaultMode); - return null; - } - mountWithApp(); - - expect(setModeMock).toHaveBeenCalledTimes(1); - expect(setModeMock).toHaveBeenCalledWith(defaultMode); - }); -}); diff --git a/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx b/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx deleted file mode 100644 index ebec996885b..00000000000 --- a/polaris-react/src/components/IndexFilters/hooks/useSetIndexFiltersMode/useSetIndexFiltersMode.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import {useEffect} from 'react'; - -import { - useIndexFiltersManager, - IndexFiltersMode, -} from '../../../../utilities/index-filters'; - -export function useSetIndexFiltersMode( - defaultMode: IndexFiltersMode = IndexFiltersMode.Default, -) { - const {mode, setMode} = useIndexFiltersManager(); - - useEffect(() => { - setMode(defaultMode); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultMode]); - - return {mode, setMode}; -} diff --git a/polaris-react/src/components/Page/components/Header/Header.tsx b/polaris-react/src/components/Page/components/Header/Header.tsx index c2ea1e1e4ed..8fbf00182a5 100644 --- a/polaris-react/src/components/Page/components/Header/Header.tsx +++ b/polaris-react/src/components/Page/components/Header/Header.tsx @@ -28,7 +28,7 @@ import {isInterface} from '../../../../utilities/is-interface'; import {isReactElement} from '../../../../utilities/is-react-element'; import { IndexFiltersMode, - useIndexFiltersManager, + useSetIndexFiltersMode, } from '../../../../utilities/index-filters'; import {Box} from '../../../Box'; import {HorizontalStack} from '../../../HorizontalStack'; @@ -97,8 +97,8 @@ export function Header({ const i18n = useI18n(); const {polarisSummerEditions2023} = useFeatures(); const {isNavigationCollapsed} = useMediaQuery(); - const {mode} = useIndexFiltersManager(); - const shouldDisableActions = mode !== IndexFiltersMode.Default; + const {mode} = useSetIndexFiltersMode(); + const disableActions = mode !== IndexFiltersMode.Default; if (additionalNavigation && process.env.NODE_ENV === 'development') { // eslint-disable-next-line no-console @@ -132,8 +132,8 @@ export function Header({ @@ -159,7 +159,7 @@ export function Header({ const primaryActionMarkup = primaryAction ? ( ) : null; @@ -172,11 +172,11 @@ export function Header({ ({ ...secondaryAction, - disabled: shouldDisableActions || secondaryAction.disabled, + disabled: disableActions || secondaryAction.disabled, }))} groups={actionGroups.map((actionGroup) => ({ ...actionGroup, - disabled: shouldDisableActions || actionGroup.disabled, + disabled: disableActions || actionGroup.disabled, }))} rollup={isNavigationCollapsed} rollupActionsLabel={ @@ -290,10 +290,10 @@ export function Header({ function PrimaryActionMarkup({ primaryAction, - shouldOverrideDisableAction, + disabled, }: { primaryAction: PrimaryAction | React.ReactNode; - shouldOverrideDisableAction: boolean; + disabled: boolean; }) { const {isNavigationCollapsed} = useMediaQuery(); @@ -305,7 +305,7 @@ function PrimaryActionMarkup({ shouldShowIconOnly(isNavigationCollapsed, primaryAction), { primary, - disabled: shouldOverrideDisableAction ?? primaryAction.disabled, + disabled: disabled || primaryAction.disabled, }, ); diff --git a/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx b/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx index 0ac83c5189c..2e2f8c121a9 100644 --- a/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx +++ b/polaris-react/src/components/PolarisTestProvider/PolarisTestProvider.tsx @@ -21,9 +21,9 @@ import type {FeaturesConfig} from '../../utilities/features'; import {EphemeralPresenceManager} from '../EphemeralPresenceManager'; import { IndexFiltersMode, - IndexFiltersContext, + IndexFiltersModeContext, } from '../../utilities/index-filters'; -import type {IndexFiltersContextType} from '../../utilities/index-filters'; +import type {IndexFiltersModeContextType} from '../../utilities/index-filters'; type FrameContextType = NonNullable>; type MediaQueryContextType = NonNullable< @@ -44,7 +44,7 @@ export interface WithPolarisTestProviderOptions { // Contexts provided by Frame frame?: Partial; // Contexts provided by IndexFilters - indexFilters?: Partial; + indexFilters?: Partial; } export interface PolarisTestProviderProps @@ -57,7 +57,7 @@ const defaultMediaQuery: MediaQueryContextType = { isNavigationCollapsed: false, }; -const defaultIndexFilters: IndexFiltersContextType = { +const defaultIndexFilters: IndexFiltersModeContextType = { mode: IndexFiltersMode.Default, setMode: noop, }; @@ -104,11 +104,11 @@ export function PolarisTestProvider({ - {children} - + diff --git a/polaris-react/src/index.ts b/polaris-react/src/index.ts index 3c5e6c8ea50..30f8e87d59b 100644 --- a/polaris-react/src/index.ts +++ b/polaris-react/src/index.ts @@ -188,7 +188,7 @@ export type {IconProps} from './components/Icon'; export {Image} from './components/Image'; export type {ImageProps} from './components/Image'; -export {IndexFilters, useSetIndexFiltersMode} from './components/IndexFilters'; +export {IndexFilters} from './components/IndexFilters'; export type { IndexFiltersProps, SortButtonChoice, @@ -422,7 +422,7 @@ export {useEventListener} from './utilities/use-event-listener'; export {useTheme} from './utilities/use-theme'; export {useIndexResourceState} from './utilities/use-index-resource-state'; export { - useIndexFiltersManager, + useSetIndexFiltersMode, IndexFiltersMode, IndexFiltersManager, } from './utilities/index-filters'; diff --git a/polaris-react/src/utilities/index-filters/IndexFiltersManager.tsx b/polaris-react/src/utilities/index-filters/IndexFiltersManager.tsx index 9f2f1ca98ec..30969ffa9ff 100644 --- a/polaris-react/src/utilities/index-filters/IndexFiltersManager.tsx +++ b/polaris-react/src/utilities/index-filters/IndexFiltersManager.tsx @@ -1,14 +1,14 @@ import type {ContextType} from 'react'; import React, {useMemo, useState} from 'react'; -import {IndexFiltersContext} from './context'; +import {IndexFiltersModeContext} from './context'; import {IndexFiltersMode} from './types'; export interface IndexFiltersManagerProps { children?: React.ReactNode; } -type Context = NonNullable>; +type Context = NonNullable>; export function IndexFiltersManager({children}: IndexFiltersManagerProps) { const [mode, setMode] = useState(IndexFiltersMode.Default); @@ -22,8 +22,8 @@ export function IndexFiltersManager({children}: IndexFiltersManagerProps) { ); return ( - + {children} - + ); } diff --git a/polaris-react/src/utilities/index-filters/context.tsx b/polaris-react/src/utilities/index-filters/context.tsx index 5b6dea6e732..84c975c3457 100644 --- a/polaris-react/src/utilities/index-filters/context.tsx +++ b/polaris-react/src/utilities/index-filters/context.tsx @@ -2,11 +2,11 @@ import {createContext} from 'react'; import type {IndexFiltersMode} from './types'; -export interface IndexFiltersContextType { +export interface IndexFiltersModeContextType { mode: IndexFiltersMode; setMode: (mode: IndexFiltersMode) => void; } -export const IndexFiltersContext = createContext< - IndexFiltersContextType | undefined +export const IndexFiltersModeContext = createContext< + IndexFiltersModeContextType | undefined >(undefined); diff --git a/polaris-react/src/utilities/index-filters/hooks.ts b/polaris-react/src/utilities/index-filters/hooks.ts index fe039d82955..e640873822a 100644 --- a/polaris-react/src/utilities/index-filters/hooks.ts +++ b/polaris-react/src/utilities/index-filters/hooks.ts @@ -1,15 +1,28 @@ -import {useContext} from 'react'; +import {useContext, useEffect} from 'react'; -import {IndexFiltersContext} from './context'; +import {IndexFiltersModeContext} from './context'; +import type {IndexFiltersMode} from './types'; -export function useIndexFiltersManager() { - const indexFiltersManager = useContext(IndexFiltersContext); +export function useSetIndexFiltersMode(initialValue?: IndexFiltersMode) { + const indexFiltersMode = useContext(IndexFiltersModeContext); - if (!indexFiltersManager) { + if (!indexFiltersMode) { throw new Error( 'No index filters manager was provided. Your application must be wrapped in an component. See https://polaris.shopify.com/components/app-provider for implementation instructions.', ); } - return indexFiltersManager; + const {mode, setMode} = indexFiltersMode; + + useEffect(() => { + if (initialValue) { + setMode(initialValue); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { + mode, + setMode, + }; } diff --git a/polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx b/polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx index cf1bea52c0c..009d5fe31bc 100644 --- a/polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx +++ b/polaris-react/src/utilities/index-filters/tests/IndexFiltersManager.test.tsx @@ -3,12 +3,12 @@ import {mountWithApp} from 'tests/utilities'; import {IndexFiltersMode} from '../types'; import {IndexFiltersManager} from '../IndexFiltersManager'; -import {useIndexFiltersManager} from '../hooks'; +import {useSetIndexFiltersMode} from '../hooks'; describe('', () => { it('renders children with the context value', () => { const ChildComponent = () => { - const {mode, setMode} = useIndexFiltersManager(); + const {mode, setMode} = useSetIndexFiltersMode(); return (
{mode} diff --git a/polaris-react/src/utilities/index-filters/tests/hooks.test.tsx b/polaris-react/src/utilities/index-filters/tests/hooks.test.tsx index 5774e2f6cd8..02508f021ff 100644 --- a/polaris-react/src/utilities/index-filters/tests/hooks.test.tsx +++ b/polaris-react/src/utilities/index-filters/tests/hooks.test.tsx @@ -1,32 +1,48 @@ import React from 'react'; import {mountWithApp} from 'tests/utilities'; -import {useIndexFiltersManager} from '../hooks'; -import {IndexFiltersContext} from '../context'; +import * as hooks from '../hooks'; import {IndexFiltersMode} from '../types'; -describe('useIndexFiltersManager', () => { +describe('useSetIndexFiltersMode', () => { it('returns mode from the provider', () => { const spy = jest.fn(); function MockComponent() { - const value = useIndexFiltersManager(); + const value = hooks.useSetIndexFiltersMode(); spy(value); return null; } - mountWithApp( - - - , - ); + mountWithApp(, { + indexFilters: { + mode: IndexFiltersMode.Filtering, + }, + }); expect(spy).toHaveBeenCalledWith( expect.objectContaining({ - mode: IndexFiltersMode.Default, + mode: IndexFiltersMode.Filtering, }), ); }); + + describe('with a mocked value', () => { + it('calls setMode with defaultMode inside useEffect', () => { + const initialMode = IndexFiltersMode.EditingColumns; + const useSetIndexFiltersModeMock = jest.spyOn( + hooks, + 'useSetIndexFiltersMode', + ); + + function MockComponent() { + hooks.useSetIndexFiltersMode(initialMode); + return null; + } + mountWithApp(); + + expect(useSetIndexFiltersModeMock).toHaveBeenCalledTimes(1); + expect(useSetIndexFiltersModeMock).toHaveBeenCalledWith(initialMode); + }); + }); }); From 89a5efbbbbd3453214b39944710f1dbb225fc7df Mon Sep 17 00:00:00 2001 From: Marc Thomas Date: Wed, 20 Sep 2023 17:54:18 +0100 Subject: [PATCH 8/8] chore: use a mounted ref --- polaris-react/src/utilities/index-filters/hooks.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/polaris-react/src/utilities/index-filters/hooks.ts b/polaris-react/src/utilities/index-filters/hooks.ts index e640873822a..8ad2d5c74be 100644 --- a/polaris-react/src/utilities/index-filters/hooks.ts +++ b/polaris-react/src/utilities/index-filters/hooks.ts @@ -1,4 +1,4 @@ -import {useContext, useEffect} from 'react'; +import {useContext, useRef} from 'react'; import {IndexFiltersModeContext} from './context'; import type {IndexFiltersMode} from './types'; @@ -14,12 +14,14 @@ export function useSetIndexFiltersMode(initialValue?: IndexFiltersMode) { const {mode, setMode} = indexFiltersMode; - useEffect(() => { + const hasMounted = useRef(false); + + if (!hasMounted.current) { if (initialValue) { setMode(initialValue); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + hasMounted.current = true; + } return { mode,