Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataViews: Full type the dataviews package #61854

Merged
merged 6 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/compose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ];
}
18 changes: 9 additions & 9 deletions packages/dataviews/src/bulk-actions-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 (
<>
Expand Down Expand Up @@ -163,7 +163,7 @@ function renderToolbarContent< Item extends AnyItem >(
label={ __( 'Cancel' ) }
disabled={ !! actionInProgress }
onClick={ () => {
setSelection( EMPTY_ARRAY );
onSelectionChange( EMPTY_ARRAY );
} }
/>
</ToolbarGroup>
Expand All @@ -175,7 +175,7 @@ function ToolbarContent< Item extends AnyItem >( {
selection,
actionsToShow,
selectedItems,
setSelection,
onSelectionChange,
}: ToolbarContentProps< Item > ) {
const [ actionInProgress, setActionInProgress ] = useState< string | null >(
null
Expand All @@ -191,7 +191,7 @@ function ToolbarContent< Item extends AnyItem >( {
selectedItems,
actionInProgress,
setActionInProgress,
setSelection
onSelectionChange
);
} else if ( ! buttons.current ) {
buttons.current = renderToolbarContent(
Expand All @@ -200,7 +200,7 @@ function ToolbarContent< Item extends AnyItem >( {
selectedItems,
actionInProgress,
setActionInProgress,
setSelection
onSelectionChange
);
}
return buttons.current;
Expand All @@ -210,7 +210,7 @@ export default function BulkActionsToolbar< Item extends AnyItem >( {
data,
selection,
actions = EMPTY_ARRAY,
setSelection,
onSelectionChange,
getItemId,
}: BulkActionsToolbarProps< Item > ) {
const isReducedMotion = useReducedMotion();
Expand Down Expand Up @@ -258,7 +258,7 @@ export default function BulkActionsToolbar< Item extends AnyItem >( {
selection={ selection }
actionsToShow={ actionsToShow }
selectedItems={ selectedItems }
setSelection={ setSelection }
onSelectionChange={ onSelectionChange }
/>
</div>
</Toolbar>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import type { ComponentType } from 'react';

/**
* WordPress dependencies
*/
Expand All @@ -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,
Expand All @@ -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 (
Expand All @@ -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(
Expand Down Expand Up @@ -150,7 +179,7 @@ export default function DataViews( {
data={ data }
actions={ actions }
selection={ selection }
setSelection={ setSelection }
onSelectionChange={ onSetSelection }
getItemId={ getItemId }
/>
) }
Expand Down
7 changes: 5 additions & 2 deletions packages/dataviews/src/filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -117,6 +117,9 @@ const Filters = memo( function Filters< Item extends AnyItem >( {
{ filterComponents }
</HStack>
);
} );
}

// A type assertion is used here to keep the type argument.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why React doesn't do this by default using generics.

const Filters = memo( _Filters ) as typeof _Filters;

export default Filters;
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
} );
Expand Down
26 changes: 23 additions & 3 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 >;
7 changes: 5 additions & 2 deletions packages/dataviews/src/view-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -339,6 +339,9 @@ const ViewActions = memo( function ViewActions< Item extends AnyItem >( {
</DropdownMenuGroup>
</DropdownMenu>
);
} );
}

// A type assertion is used here to keep the type argument.
const ViewActions = memo( _ViewActions ) as typeof _ViewActions;

export default ViewActions;
11 changes: 1 addition & 10 deletions packages/dataviews/src/view-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
11 changes: 1 addition & 10 deletions packages/dataviews/src/view-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading