diff --git a/packages/compose/README.md b/packages/compose/README.md index 83bde196033a01..c60d202f59aacf 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -265,11 +265,11 @@ Helper hook for input fields that need to debounce the value before using it. _Parameters_ -- _defaultValue_ `any`: The default value to use. +- _defaultValue_ The default value to use. _Returns_ -- `[string, Function, string]`: The input value, the setter and the debounced input value. +- `[ string, ( value: string ) => void, string ]`: The input value, the setter and the debounced input value. ### useDisabled diff --git a/packages/compose/src/hooks/use-debounced-input/index.js b/packages/compose/src/hooks/use-debounced-input/index.ts similarity index 60% rename from packages/compose/src/hooks/use-debounced-input/index.js rename to packages/compose/src/hooks/use-debounced-input/index.ts index 91a01073fcfee8..643b0ba68026c3 100644 --- a/packages/compose/src/hooks/use-debounced-input/index.js +++ b/packages/compose/src/hooks/use-debounced-input/index.ts @@ -11,18 +11,20 @@ import useDebounce from '../use-debounce'; /** * Helper hook for input fields that need to debounce the value before using it. * - * @param {any} defaultValue The default value to use. - * @return {[string, Function, string]} The input value, the setter and the debounced input value. + * @param defaultValue The default value to use. + * @return The input value, the setter and the debounced input value. */ -export default function useDebouncedInput( defaultValue = '' ) { - const [ input, setInput ] = useState( defaultValue ); +export default function useDebouncedInput( + defaultValue = '' +): [ string, ( value: string ) => void, string ] { + const [ input, setInput ] = useState< string >( defaultValue ); const [ debouncedInput, setDebouncedState ] = useState( defaultValue ); const setDebouncedInput = useDebounce( setDebouncedState, 250 ); useEffect( () => { setDebouncedInput( input ); - }, [ input ] ); + }, [ input, setDebouncedInput ] ); return [ input, setInput, debouncedInput ]; } diff --git a/packages/dataviews/src/bulk-actions-toolbar.tsx b/packages/dataviews/src/bulk-actions-toolbar.tsx index e7c4a23ff758c9..c2b3644b88bceb 100644 --- a/packages/dataviews/src/bulk-actions-toolbar.tsx +++ b/packages/dataviews/src/bulk-actions-toolbar.tsx @@ -31,14 +31,14 @@ interface ToolbarContentProps< Item extends AnyItem > { selection: string[]; actionsToShow: Action< Item >[]; selectedItems: Item[]; - setSelection: ( selection: Item[] ) => void; + onSelectionChange: ( selection: Item[] ) => void; } interface BulkActionsToolbarProps< Item extends AnyItem > { data: Item[]; selection: string[]; actions: Action< Item >[]; - setSelection: ( selection: Item[] ) => void; + onSelectionChange: ( selection: Item[] ) => void; getItemId: ( item: Item ) => string; } @@ -123,7 +123,7 @@ function renderToolbarContent< Item extends AnyItem >( selectedItems: Item[], actionInProgress: string | null, setActionInProgress: ( actionId: string | null ) => void, - setSelection: ( selection: Item[] ) => void + onSelectionChange: ( selection: Item[] ) => void ) { return ( <> @@ -163,7 +163,7 @@ function renderToolbarContent< Item extends AnyItem >( label={ __( 'Cancel' ) } disabled={ !! actionInProgress } onClick={ () => { - setSelection( EMPTY_ARRAY ); + onSelectionChange( EMPTY_ARRAY ); } } /> @@ -175,7 +175,7 @@ function ToolbarContent< Item extends AnyItem >( { selection, actionsToShow, selectedItems, - setSelection, + onSelectionChange, }: ToolbarContentProps< Item > ) { const [ actionInProgress, setActionInProgress ] = useState< string | null >( null @@ -191,7 +191,7 @@ function ToolbarContent< Item extends AnyItem >( { selectedItems, actionInProgress, setActionInProgress, - setSelection + onSelectionChange ); } else if ( ! buttons.current ) { buttons.current = renderToolbarContent( @@ -200,7 +200,7 @@ function ToolbarContent< Item extends AnyItem >( { selectedItems, actionInProgress, setActionInProgress, - setSelection + onSelectionChange ); } return buttons.current; @@ -210,7 +210,7 @@ export default function BulkActionsToolbar< Item extends AnyItem >( { data, selection, actions = EMPTY_ARRAY, - setSelection, + onSelectionChange, getItemId, }: BulkActionsToolbarProps< Item > ) { const isReducedMotion = useReducedMotion(); @@ -258,7 +258,7 @@ export default function BulkActionsToolbar< Item extends AnyItem >( { selection={ selection } actionsToShow={ actionsToShow } selectedItems={ selectedItems } - setSelection={ setSelection } + onSelectionChange={ onSelectionChange } /> diff --git a/packages/dataviews/src/dataviews.js b/packages/dataviews/src/dataviews.tsx similarity index 73% rename from packages/dataviews/src/dataviews.js rename to packages/dataviews/src/dataviews.tsx index 75b672721f0419..875746ceb52c38 100644 --- a/packages/dataviews/src/dataviews.js +++ b/packages/dataviews/src/dataviews.tsx @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import type { ComponentType } from 'react'; + /** * WordPress dependencies */ @@ -16,21 +21,46 @@ import { VIEW_LAYOUTS } from './layouts'; import BulkActions from './bulk-actions'; import { normalizeFields } from './normalize-fields'; import BulkActionsToolbar from './bulk-actions-toolbar'; +import type { Action, AnyItem, Field, View, ViewBaseProps } from './types'; + +interface DataViewsProps< Item extends AnyItem > { + view: View; + onChangeView: ( view: View ) => void; + fields: Field< Item >[]; + search?: boolean; + searchLabel?: string; + actions?: Action< Item >[]; + data: Item[]; + getItemId?: ( item: Item ) => string; + isLoading?: boolean; + paginationInfo: { + totalItems: number; + totalPages: number; + }; + supportedLayouts: string[]; + onSelectionChange?: ( items: Item[] ) => void; +} -const defaultGetItemId = ( item ) => item.id; +const defaultGetItemId = ( item: AnyItem ) => item.id; const defaultOnSelectionChange = () => {}; -function useSomeItemHasAPossibleBulkAction( actions, data ) { +function useSomeItemHasAPossibleBulkAction< Item extends AnyItem >( + actions: Action< Item >[], + data: Item[] +) { return useMemo( () => { return data.some( ( item ) => { return actions.some( ( action ) => { - return action.supportsBulk && action.isEligible( item ); + return ( + action.supportsBulk && + ( ! action.isEligible || action.isEligible( item ) ) + ); } ); } ); }, [ actions, data ] ); } -export default function DataViews( { +export default function DataViews< Item extends AnyItem >( { view, onChangeView, fields, @@ -43,9 +73,9 @@ export default function DataViews( { paginationInfo, supportedLayouts, onSelectionChange = defaultOnSelectionChange, -} ) { - const [ selection, setSelection ] = useState( [] ); - const [ openedFilter, setOpenedFilter ] = useState( null ); +}: DataViewsProps< Item > ) { + const [ selection, setSelection ] = useState< string[] >( [] ); + const [ openedFilter, setOpenedFilter ] = useState< string | null >( null ); useEffect( () => { if ( @@ -67,16 +97,15 @@ export default function DataViews( { }, [ selection, data, getItemId, onSelectionChange ] ); const onSetSelection = useCallback( - ( items ) => { + ( items: Item[] ) => { setSelection( items.map( ( item ) => getItemId( item ) ) ); onSelectionChange( items ); }, [ setSelection, getItemId, onSelectionChange ] ); - const ViewComponent = VIEW_LAYOUTS.find( - ( v ) => v.type === view.type - ).component; + const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type ) + ?.component as ComponentType< ViewBaseProps< Item > >; const _fields = useMemo( () => normalizeFields( fields ), [ fields ] ); const hasPossibleBulkAction = useSomeItemHasAPossibleBulkAction( @@ -150,7 +179,7 @@ export default function DataViews( { data={ data } actions={ actions } selection={ selection } - setSelection={ setSelection } + onSelectionChange={ onSetSelection } getItemId={ getItemId } /> ) } diff --git a/packages/dataviews/src/filters.tsx b/packages/dataviews/src/filters.tsx index df7a13b2953f16..0cf017fce191b1 100644 --- a/packages/dataviews/src/filters.tsx +++ b/packages/dataviews/src/filters.tsx @@ -22,7 +22,7 @@ interface FiltersProps< Item extends AnyItem > { setOpenedFilter: ( openedFilter: string | null ) => void; } -const Filters = memo( function Filters< Item extends AnyItem >( { +function _Filters< Item extends AnyItem >( { fields, view, onChangeView, @@ -117,6 +117,9 @@ const Filters = memo( function Filters< Item extends AnyItem >( { { filterComponents } ); -} ); +} + +// A type assertion is used here to keep the type argument. +const Filters = memo( _Filters ) as typeof _Filters; export default Filters; diff --git a/packages/dataviews/src/index.js b/packages/dataviews/src/index.ts similarity index 100% rename from packages/dataviews/src/index.js rename to packages/dataviews/src/index.ts diff --git a/packages/dataviews/src/search.js b/packages/dataviews/src/search.tsx similarity index 65% rename from packages/dataviews/src/search.js rename to packages/dataviews/src/search.tsx index 13a828c330575a..7929a9bd0eb3cb 100644 --- a/packages/dataviews/src/search.js +++ b/packages/dataviews/src/search.tsx @@ -6,20 +6,37 @@ import { useEffect, useRef, memo } from '@wordpress/element'; import { SearchControl } from '@wordpress/components'; import { useDebouncedInput } from '@wordpress/compose'; -const Search = memo( function Search( { label, view, onChangeView } ) { +/** + * Internal dependencies + */ +import type { View } from './types'; + +interface SearchProps { + label?: string; + view: View; + onChangeView: ( view: View ) => void; +} + +const Search = memo( function Search( { + label, + view, + onChangeView, +}: SearchProps ) { const [ search, setSearch, debouncedSearch ] = useDebouncedInput( view.search ); useEffect( () => { - setSearch( view.search ); - }, [ view ] ); + setSearch( view.search ?? '' ); + }, [ view.search, setSearch ] ); const onChangeViewRef = useRef( onChangeView ); + const viewRef = useRef( view ); useEffect( () => { onChangeViewRef.current = onChangeView; - }, [ onChangeView ] ); + viewRef.current = view; + }, [ onChangeView, view ] ); useEffect( () => { onChangeViewRef.current( { - ...view, + ...viewRef.current, page: 1, search: debouncedSearch, } ); diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 58b0bffc922966..f7e6274d83928e 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -373,15 +373,35 @@ export type Action< Item extends AnyItem > = | ActionModal< Item > | ActionButton< Item >; -export interface ViewProps< Item extends AnyItem, ViewType extends ViewBase > { +export interface ViewBaseProps< Item extends AnyItem > { actions: Action< Item >[]; data: Item[]; fields: NormalizedField< Item >[]; getItemId: ( item: Item ) => string; isLoading?: boolean; - onChangeView( view: ViewType ): void; + onChangeView( view: View ): void; onSelectionChange: ( items: Item[] ) => void; selection: string[]; setOpenedFilter: ( fieldId: string ) => void; - view: ViewType; + view: View; } + +export interface ViewTableProps< Item extends AnyItem > + extends ViewBaseProps< Item > { + view: ViewTable; +} + +export interface ViewListProps< Item extends AnyItem > + extends ViewBaseProps< Item > { + view: ViewList; +} + +export interface ViewGridProps< Item extends AnyItem > + extends ViewBaseProps< Item > { + view: ViewGrid; +} + +export type ViewProps< Item extends AnyItem > = + | ViewTableProps< Item > + | ViewGridProps< Item > + | ViewListProps< Item >; diff --git a/packages/dataviews/src/view-actions.tsx b/packages/dataviews/src/view-actions.tsx index 0e041cc070c525..90098417575315 100644 --- a/packages/dataviews/src/view-actions.tsx +++ b/packages/dataviews/src/view-actions.tsx @@ -303,7 +303,7 @@ function SortMenu< Item extends AnyItem >( { ); } -const ViewActions = memo( function ViewActions< Item extends AnyItem >( { +function _ViewActions< Item extends AnyItem >( { fields, view, onChangeView, @@ -339,6 +339,9 @@ const ViewActions = memo( function ViewActions< Item extends AnyItem >( { ); -} ); +} + +// A type assertion is used here to keep the type argument. +const ViewActions = memo( _ViewActions ) as typeof _ViewActions; export default ViewActions; diff --git a/packages/dataviews/src/view-grid.tsx b/packages/dataviews/src/view-grid.tsx index f42ec215be9d23..5c6b8ba5bc3b50 100644 --- a/packages/dataviews/src/view-grid.tsx +++ b/packages/dataviews/src/view-grid.tsx @@ -22,16 +22,7 @@ import { __ } from '@wordpress/i18n'; import ItemActions from './item-actions'; import SingleSelectionCheckbox from './single-selection-checkbox'; import { useHasAPossibleBulkAction } from './bulk-actions'; -import type { - Action, - AnyItem, - NormalizedField, - ViewGrid as ViewGridType, - ViewProps, -} from './types'; - -interface ViewGridProps< Item extends AnyItem > - extends ViewProps< Item, ViewGridType > {} +import type { Action, AnyItem, NormalizedField, ViewGridProps } from './types'; interface GridItemProps< Item extends AnyItem > { selection: string[]; diff --git a/packages/dataviews/src/view-list.tsx b/packages/dataviews/src/view-list.tsx index f829b505454b30..1e7a558bbad5eb 100644 --- a/packages/dataviews/src/view-list.tsx +++ b/packages/dataviews/src/view-list.tsx @@ -32,19 +32,10 @@ import { moreVertical } from '@wordpress/icons'; * Internal dependencies */ import { unlock } from './lock-unlock'; -import type { - Action, - AnyItem, - NormalizedField, - ViewList as ViewListType, - ViewProps, -} from './types'; +import type { Action, AnyItem, NormalizedField, ViewListProps } from './types'; import { ActionsDropdownMenuGroup, ActionModal } from './item-actions'; -interface ViewListProps< Item extends AnyItem > - extends ViewProps< Item, ViewListType > {} - interface ListViewItemProps< Item extends AnyItem > { actions: Action< Item >[]; id?: string; diff --git a/packages/dataviews/src/view-table.tsx b/packages/dataviews/src/view-table.tsx index 9ecfa9c9c0770d..dcfb8a67bf3636 100644 --- a/packages/dataviews/src/view-table.tsx +++ b/packages/dataviews/src/view-table.tsx @@ -50,8 +50,8 @@ import type { AnyItem, NormalizedField, SortDirection, - ViewProps, ViewTable as ViewTableType, + ViewTableProps, } from './types'; const { @@ -91,9 +91,6 @@ interface TableRowProps< Item extends AnyItem > { data: Item[]; } -interface ViewTableProps< Item extends AnyItem > - extends ViewProps< Item, ViewTableType > {} - function WithDropDownMenuSeparators( { children }: { children: ReactNode } ) { return Children.toArray( children ) .filter( Boolean )