Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import type { ReactElement } from 'react';
import type { ReactElement, ReactNode } from 'react';

/**
* WordPress dependencies
Expand Down Expand Up @@ -169,7 +169,13 @@ function ActionTrigger< Item >( {
<Button
disabled={ isBusy }
accessibleWhenDisabled
label={ label }
label={ action.isTextButton ? undefined : label }
children={
action.isTextButton ? ( label as ReactNode ) : undefined
}
variant={
action.isPrimary && action.isTextButton ? 'primary' : undefined
}
Comment on lines +172 to +178
Copy link
Member

Choose a reason for hiding this comment

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

I don't have a lot of DataViews fu, so take these questions with lots of salt.

My first question was "what is isTextButton"? The name suggests it's about what type of button this is, but it's actually about how the button should be displayed.

Are the Button component's in-built props not flexible enough to render a text button vs an icon button? E.g., text , children and label?

Could we rather open up the Button's props and allow passing a variant?

I'm not sure, I know you've probably thought about this anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is just how the Button works afaik. label is just for the aria-label or tooltip text, and mostly used for icon buttons.

For a text button children is used. I suppose I could change it to <Button>{ label }</Button> instead of <Button children={ label } />. 😄

Copy link
Member

Choose a reason for hiding this comment

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

I'll take your word for it!

Or return different Button components? I dunno, I'm just saying random stuff.

	if (hasIcon && hasChildren) {
		// Show both icon and text
		return (
			<Button
				{...buttonProps}
				icon={action.icon}
				label={label}
			>
				{children}
			</Button>
		);
	} else if (hasIcon) {
		// Show icon with tooltip
		return (
			<Button
				{...buttonProps}
				icon={action.icon}
				label={label}
				showTooltip
			/>
		);
	} else {
...

icon={ action.icon }
isDestructive={ action.isDestructive }
size="compact"
Expand Down Expand Up @@ -336,7 +342,7 @@ function FooterContent< Item >( {
actions.filter( ( action ) => {
return (
action.supportsBulk &&
action.icon &&
( action.icon || action.isTextButton ) &&
selectedItems.some(
( item ) =>
! action.isEligible || action.isEligible( item )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ export default function ItemActions< Item >( {
// If an action is eligible for all items, doesn't need
// to provide the `isEligible` function.
const _eligibleActions = actions.filter(
( action ) => ! action.isEligible || action.isEligible( item )
( action ) =>
! action.hideItemAction &&
( ! action.isEligible || action.isEligible( item ) )
);
const _primaryActions = _eligibleActions.filter(
( action ) => action.isPrimary && !! action.icon
Expand Down
110 changes: 110 additions & 0 deletions packages/dataviews/src/components/dataviews/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Meta } from '@storybook/react';
import {
useState,
useMemo,
useCallback,
createInterpolateElement,
} from '@wordpress/element';
import {
Expand Down Expand Up @@ -326,3 +327,112 @@ export const CustomPerPageSizes = () => {
/>
);
};

const Selection = ( {
multiple,
onSelectionComplete,
}: {
multiple?: boolean;
onSelectionComplete: ( items: SpaceObject[] ) => void;
} ) => {
const [ selection, setSelection ] = useState< string[] >( [] );
const [ view, setView ] = useState< View >( {
...DEFAULT_VIEW,
fields: [ 'categories' ],
titleField: 'title',
descriptionField: 'description',
mediaField: 'image',
} );

const { data: shownData, paginationInfo } = useMemo( () => {
return filterSortAndPaginate( data, view, fields );
}, [ view ] );

const selectionActions = useMemo(
() =>
multiple
? [
{
id: 'select',
label: __( 'Select' ),
isPrimary: true,
isTextButton: true,
hideItemAction: true,
isEligible() {
return Boolean( multiple );
},
callback( items: SpaceObject[] ) {
onSelectionComplete( items );
},
supportsBulk: Boolean( multiple ),
},
]
: [],
[ multiple, onSelectionComplete ]
);

const onClickItem = useCallback(
( item: SpaceObject ) => {
if ( multiple ) {
if ( selection.includes( item.id.toString() ) ) {
setSelection(
selection.filter( ( id ) => id !== item.id.toString() )
);
} else {
setSelection( [ item.id.toString(), ...selection ] );
}
} else {
onSelectionComplete( [ item ] );
}
},
[ multiple, selection, onSelectionComplete ]
);

return (
<DataViews
getItemId={ ( item ) => item.id.toString() }
paginationInfo={ paginationInfo }
data={ shownData }
view={ view }
fields={ fields }
onChangeView={ setView }
actions={ selectionActions }
selection={ selection }
onChangeSelection={ setSelection }
onClickItem={ onClickItem }
isItemClickable={ () => true }
defaultLayouts={ defaultLayouts }
/>
);
};

export const SingleSelection = () => {
return (
<Selection
onSelectionComplete={ ( items: SpaceObject[] ) => {
// eslint-disable-next-line no-alert
alert(
`Selected: ${ items
.map( ( item ) => item.title )
.join( ', ' ) }`
);
} }
/>
);
};

export const MultiSelection = () => {
return (
<Selection
multiple
onSelectionComplete={ ( items: SpaceObject[] ) => {
// eslint-disable-next-line no-alert
alert(
`Selected: ${ items
.map( ( item ) => item.title )
.join( ', ' ) }`
);
} }
/>
);
};
4 changes: 3 additions & 1 deletion packages/dataviews/src/dataviews-layouts/list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ function ListItem< Item >( {
// If an action is eligible for all items, doesn't need
// to provide the `isEligible` function.
const _eligibleActions = actions.filter(
( action ) => ! action.isEligible || action.isEligible( item )
( action ) =>
! action.hideItemAction &&
( ! action.isEligible || action.isEligible( item ) )
);
const _primaryActions = _eligibleActions.filter(
( action ) => action.isPrimary && !! action.icon
Expand Down
22 changes: 20 additions & 2 deletions packages/dataviews/src/dataviews-layouts/table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Spinner } from '@wordpress/components';
import {
useContext,
useEffect,
useMemo,
useId,
useRef,
useState,
Expand Down Expand Up @@ -95,6 +96,14 @@ function TableColumnField< Item >( {
);
}

function getHasActionsColumn< Item >( actions: Action< Item >[], item: Item ) {
return actions.some(
( action ) =>
! action.hideItemAction &&
( ! action.isEligible || action.isEligible( item ) )
);
}

function TableRow< Item >( {
hasBulkActions,
item,
Expand Down Expand Up @@ -135,6 +144,11 @@ function TableRow< Item >( {
( mediaField && showMedia ) ||
( descriptionField && showDescription );

const hasActionsColumn = useMemo(
() => getHasActionsColumn( actions, item ),
[ actions, item ]
);

return (
<tr
className={ clsx( 'dataviews-view-table__row', {
Expand Down Expand Up @@ -216,7 +230,7 @@ function TableRow< Item >( {
</td>
);
} ) }
{ !! actions?.length && (
{ hasActionsColumn && (
// Disable reason: we are not making the element interactive,
// but preventing any click events from bubbling up to the
// table row. This allows us to add a click handler to the row
Expand Down Expand Up @@ -265,6 +279,10 @@ function ViewTable< Item >( {
const [ nextHeaderMenuToFocus, setNextHeaderMenuToFocus ] =
useState< HTMLButtonElement >();
const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data );
const hasActionsColumns = useMemo(
() => data.some( ( item ) => getHasActionsColumn( actions, item ) ),
[ actions, data ]
);

useEffect( () => {
if ( headerMenuToFocusRef.current ) {
Expand Down Expand Up @@ -404,7 +422,7 @@ function ViewTable< Item >( {
</th>
);
} ) }
{ !! actions?.length && (
{ hasActionsColumns && (
<th
className={ clsx(
'dataviews-view-table__actions-column',
Expand Down
10 changes: 10 additions & 0 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,11 @@ interface ActionBase< Item > {
*/
isPrimary?: boolean;

/**
* Whether to show a text button instead of an icon button in the bulk actions footer.
*/
isTextButton?: boolean;

/**
* Whether the item passed as an argument supports the current action.
*/
Expand All @@ -527,6 +532,11 @@ interface ActionBase< Item > {
*/
supportsBulk?: boolean;

/**
* Whether the action is hidden on the item.
*/
hideItemAction?: boolean;

/**
* The context in which the action is visible.
* This is only a "meta" information for now.
Expand Down
Loading