Skip to content

Commit

Permalink
fix: improve dataview types (#61586)
Browse files Browse the repository at this point in the history
This commit improves the types in the dataviews package.

This is helpful by typing the `item` or `items` provided as arguments
to the functions of the fields and actions of the `DataViews`.

This commit does not implement requiring these types in the `DataView`
component, but helps to improve the types of the functions that are
used in the `DataView` package. It also allows exporting the types
to be used in consumers of the package.

- Add generic item type to all dataview types that operate
on `item` or lists of `items`.

- Update the usage in the dataviews package to incorporate
the use of the generic item type.

Co-authored-by: johnhooks <[email protected]>
Co-authored-by: youknowriad <[email protected]>
Co-authored-by: sirreal <[email protected]>
  • Loading branch information
4 people authored May 15, 2024
1 parent 58de7d3 commit 44bca55
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 105 deletions.
55 changes: 29 additions & 26 deletions packages/dataviews/src/bulk-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useMemo, useState, useCallback, useEffect } from '@wordpress/element';
* Internal dependencies
*/
import { unlock } from './lock-unlock';
import type { Action, ActionModal, Data, Item } from './types';
import type { Action, ActionModal, AnyItem } from './types';

const {
DropdownMenuV2: DropdownMenu,
Expand All @@ -22,34 +22,37 @@ const {
DropdownMenuSeparatorV2: DropdownMenuSeparator,
} = unlock( componentsPrivateApis );

interface ActionWithModalProps {
action: ActionModal;
interface ActionWithModalProps< Item extends AnyItem > {
action: ActionModal< Item >;
selectedItems: Item[];
setActionWithModal: ( action?: ActionModal ) => void;
setActionWithModal: ( action?: ActionModal< Item > ) => void;
onMenuOpenChange: ( isOpen: boolean ) => void;
}

interface BulkActionsItemProps {
action: Action;
interface BulkActionsItemProps< Item extends AnyItem > {
action: Action< Item >;
selectedItems: Item[];
setActionWithModal: ( action?: ActionModal ) => void;
setActionWithModal: ( action?: ActionModal< Item > ) => void;
}

interface ActionsMenuGroupProps {
actions: Action[];
interface ActionsMenuGroupProps< Item extends AnyItem > {
actions: Action< Item >[];
selectedItems: Item[];
setActionWithModal: ( action?: ActionModal ) => void;
setActionWithModal: ( action?: ActionModal< Item > ) => void;
}

interface BulkActionsProps {
data: Data;
actions: Action[];
interface BulkActionsProps< Item extends AnyItem > {
data: Item[];
actions: Action< Item >[];
selection: string[];
onSelectionChange: ( selection: Item[] ) => void;
getItemId: ( item: Item ) => string;
}

export function useHasAPossibleBulkAction( actions: Action[], item: Item ) {
export function useHasAPossibleBulkAction< Item extends AnyItem >(
actions: Action< Item >[],
item: Item
) {
return useMemo( () => {
return actions.some( ( action ) => {
return (
Expand All @@ -60,9 +63,9 @@ export function useHasAPossibleBulkAction( actions: Action[], item: Item ) {
}, [ actions, item ] );
}

export function useSomeItemHasAPossibleBulkAction(
actions: Action[],
data: Data
export function useSomeItemHasAPossibleBulkAction< Item extends AnyItem >(
actions: Action< Item >[],
data: Item[]
) {
return useMemo( () => {
return data.some( ( item ) => {
Expand All @@ -76,12 +79,12 @@ export function useSomeItemHasAPossibleBulkAction(
}, [ actions, data ] );
}

function ActionWithModal( {
function ActionWithModal< Item extends AnyItem >( {
action,
selectedItems,
setActionWithModal,
onMenuOpenChange,
}: ActionWithModalProps ) {
}: ActionWithModalProps< Item > ) {
const eligibleItems = useMemo( () => {
return selectedItems.filter(
( item ) => ! action.isEligible || action.isEligible( item )
Expand All @@ -107,11 +110,11 @@ function ActionWithModal( {
);
}

function BulkActionItem( {
function BulkActionItem< Item extends AnyItem >( {
action,
selectedItems,
setActionWithModal,
}: BulkActionsItemProps ) {
}: BulkActionsItemProps< Item > ) {
const eligibleItems = useMemo( () => {
return selectedItems.filter(
( item ) => ! action.isEligible || action.isEligible( item )
Expand Down Expand Up @@ -141,11 +144,11 @@ function BulkActionItem( {
);
}

function ActionsMenuGroup( {
function ActionsMenuGroup< Item extends AnyItem >( {
actions,
selectedItems,
setActionWithModal,
}: ActionsMenuGroupProps ) {
}: ActionsMenuGroupProps< Item > ) {
return (
<>
<DropdownMenuGroup>
Expand All @@ -163,20 +166,20 @@ function ActionsMenuGroup( {
);
}

export default function BulkActions( {
export default function BulkActions< Item extends AnyItem >( {
data,
actions,
selection,
onSelectionChange,
getItemId,
}: BulkActionsProps ) {
}: BulkActionsProps< Item > ) {
const bulkActions = useMemo(
() => actions.filter( ( action ) => action.supportsBulk ),
[ actions ]
);
const [ isMenuOpen, onMenuOpenChange ] = useState( false );
const [ actionWithModal, setActionWithModal ] = useState<
ActionModal | undefined
ActionModal< Item > | undefined
>();
const selectableItems = useMemo( () => {
return data.filter( ( item ) => {
Expand Down
21 changes: 13 additions & 8 deletions packages/dataviews/src/filter-and-sort-data-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import {
OPERATOR_IS_NOT_ALL,
} from './constants';
import { normalizeFields } from './normalize-fields';
import type { Data, Field, View } from './types';
import type { Field, AnyItem, View } from './types';

function normalizeSearchInput( input = '' ) {
return removeAccents( input.trim().toLowerCase() );
}

const EMPTY_ARRAY: Data = [];
const EMPTY_ARRAY: [] = [];

/**
* Applies the filtering, sorting and pagination to the raw data based on the view configuration.
Expand All @@ -32,11 +32,14 @@ const EMPTY_ARRAY: Data = [];
*
* @return Filtered, sorted and paginated data.
*/
export function filterSortAndPaginate(
data: Data,
export function filterSortAndPaginate< Item extends AnyItem >(
data: Item[],
view: View,
fields: Field[]
): { data: Data; paginationInfo: { totalItems: number; totalPages: number } } {
fields: Field< Item >[]
): {
data: Item[];
paginationInfo: { totalItems: number; totalPages: number };
} {
if ( ! data ) {
return {
data: EMPTY_ARRAY,
Expand Down Expand Up @@ -100,7 +103,9 @@ export function filterSortAndPaginate(
) {
filteredData = filteredData.filter( ( item ) => {
return filter.value.every( ( value: any ) => {
return field.getValue( { item } ).includes( value );
return field
.getValue( { item } )
?.includes( value );
} );
} );
} else if (
Expand All @@ -111,7 +116,7 @@ export function filterSortAndPaginate(
return filter.value.every( ( value: any ) => {
return ! field
.getValue( { item } )
.includes( value );
?.includes( value );
} );
} );
} else if ( filter.operator === OPERATOR_IS ) {
Expand Down
63 changes: 37 additions & 26 deletions packages/dataviews/src/item-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { moreVertical } from '@wordpress/icons';
* Internal dependencies
*/
import { unlock } from './lock-unlock';
import type { Action, ActionModal as ActionModalType, Item } from './types';
import type { Action, ActionModal as ActionModalType, AnyItem } from './types';

const {
DropdownMenuV2: DropdownMenu,
Expand All @@ -30,46 +30,50 @@ const {
kebabCase,
} = unlock( componentsPrivateApis );

interface ButtonTriggerProps {
action: Action;
interface ButtonTriggerProps< Item extends AnyItem > {
action: Action< Item >;
onClick: MouseEventHandler;
}

interface DropdownMenuItemTriggerProps {
action: Action;
interface DropdownMenuItemTriggerProps< Item extends AnyItem > {
action: Action< Item >;
onClick: MouseEventHandler;
}

interface ActionModalProps {
action: ActionModalType;
interface ActionModalProps< Item extends AnyItem > {
action: ActionModalType< Item >;
items: Item[];
closeModal?: () => void;
}

interface ActionWithModalProps extends ActionModalProps {
interface ActionWithModalProps< Item extends AnyItem >
extends ActionModalProps< Item > {
ActionTrigger: (
props: ButtonTriggerProps | DropdownMenuItemTriggerProps
props: ButtonTriggerProps< Item > | DropdownMenuItemTriggerProps< Item >
) => ReactElement;
isBusy?: boolean;
}

interface ActionsDropdownMenuGroupProps {
actions: Action[];
interface ActionsDropdownMenuGroupProps< Item extends AnyItem > {
actions: Action< Item >[];
item: Item;
}

interface ItemActionsProps {
interface ItemActionsProps< Item extends AnyItem > {
item: Item;
actions: Action[];
actions: Action< Item >[];
isCompact: boolean;
}

interface CompactItemActionsProps {
interface CompactItemActionsProps< Item extends AnyItem > {
item: Item;
actions: Action[];
actions: Action< Item >[];
}

function ButtonTrigger( { action, onClick }: ButtonTriggerProps ) {
function ButtonTrigger< Item extends AnyItem >( {
action,
onClick,
}: ButtonTriggerProps< Item > ) {
return (
<Button
label={ action.label }
Expand All @@ -81,10 +85,10 @@ function ButtonTrigger( { action, onClick }: ButtonTriggerProps ) {
);
}

function DropdownMenuItemTrigger( {
function DropdownMenuItemTrigger< Item extends AnyItem >( {
action,
onClick,
}: DropdownMenuItemTriggerProps ) {
}: DropdownMenuItemTriggerProps< Item > ) {
return (
<DropdownMenuItem
onClick={ onClick }
Expand All @@ -95,7 +99,11 @@ function DropdownMenuItemTrigger( {
);
}

export function ActionModal( { action, items, closeModal }: ActionModalProps ) {
export function ActionModal< Item extends AnyItem >( {
action,
items,
closeModal,
}: ActionModalProps< Item > ) {
return (
<Modal
title={ action.modalHeader || action.label }
Expand All @@ -115,12 +123,12 @@ export function ActionModal( { action, items, closeModal }: ActionModalProps ) {
);
}

export function ActionWithModal( {
export function ActionWithModal< Item extends AnyItem >( {
action,
items,
ActionTrigger,
isBusy,
}: ActionWithModalProps ) {
}: ActionWithModalProps< Item > ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const actionTriggerProps = {
action,
Expand All @@ -144,10 +152,10 @@ export function ActionWithModal( {
);
}

export function ActionsDropdownMenuGroup( {
export function ActionsDropdownMenuGroup< Item extends AnyItem >( {
actions,
item,
}: ActionsDropdownMenuGroupProps ) {
}: ActionsDropdownMenuGroupProps< Item > ) {
return (
<DropdownMenuGroup>
{ actions.map( ( action ) => {
Expand All @@ -173,11 +181,11 @@ export function ActionsDropdownMenuGroup( {
);
}

export default function ItemActions( {
export default function ItemActions< Item extends AnyItem >( {
item,
actions,
isCompact,
}: ItemActionsProps ) {
}: ItemActionsProps< Item > ) {
const { primaryActions, eligibleActions } = useMemo( () => {
// If an action is eligible for all items, doesn't need
// to provide the `isEligible` function.
Expand Down Expand Up @@ -230,7 +238,10 @@ export default function ItemActions( {
);
}

function CompactItemActions( { item, actions }: CompactItemActionsProps ) {
function CompactItemActions< Item extends AnyItem >( {
item,
actions,
}: CompactItemActionsProps< Item > ) {
return (
<DropdownMenu
trigger={
Expand Down
6 changes: 4 additions & 2 deletions packages/dataviews/src/normalize-fields.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
/**
* Internal dependencies
*/
import type { Field, NormalizedField } from './types';
import type { Field, AnyItem, NormalizedField } from './types';

/**
* Apply default values and normalize the fields config.
*
* @param fields Fields config.
* @return Normalized fields config.
*/
export function normalizeFields( fields: Field[] ): NormalizedField[] {
export function normalizeFields< Item extends AnyItem >(
fields: Field< Item >[]
): NormalizedField< Item >[] {
return fields.map( ( field ) => {
const getValue = field.getValue || ( ( { item } ) => item[ field.id ] );

Expand Down
Loading

0 comments on commit 44bca55

Please sign in to comment.