Skip to content

Commit

Permalink
DataViews: label prop in Actions API can be either a string or a …
Browse files Browse the repository at this point in the history
…`function` (WordPress#61942)

Co-authored-by: ntsekouras <[email protected]>
Co-authored-by: youknowriad <[email protected]>
Co-authored-by: jorgefilipecosta <[email protected]>
  • Loading branch information
4 people authored and carstingaxion committed Jun 4, 2024
1 parent 9ed7d02 commit 6902b05
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 42 deletions.
4 changes: 4 additions & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

- Remove some unused dependencies ([#62010](https://github.com/WordPress/gutenberg/pull/62010)).

### Enhancement

- `label` prop in Actions API can be either a `string` value or a `function`, in case we want to use information from the selected items. ([#61942](https://github.com/WordPress/gutenberg/pull/61942)).

## 1.2.0 (2024-05-16)

### Internal
Expand Down
63 changes: 33 additions & 30 deletions packages/dataviews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ npm install @wordpress/dataviews --save

```jsx
const Example = () => {

// Declare data, fields, etc.

return (
Expand All @@ -27,7 +26,7 @@ const Example = () => {
paginationInfo={ paginationInfo }
/>
);
}
};
```

## Properties
Expand All @@ -42,12 +41,14 @@ Example:
const data = [
{
id: 1,
title: "Title",
author: "Admin",
date: "2012-04-23T18:25:43.511Z"
title: 'Title',
author: 'Admin',
date: '2012-04-23T18:25:43.511Z',
},
{ /* ... */ }
]
{
/* ... */
},
];
```

By default, dataviews would use each record's `id` as an unique identifier. If it's not, the consumer should provide a `getItemId` function that returns one.
Expand Down Expand Up @@ -125,8 +126,8 @@ Each field is an object with the following properties:
- `enableSorting`: whether the data can be sorted by the given field. True by default.
- `enableHiding`: whether the field can be hidden. True by default.
- `filterBy`: configuration for the filters.
- `operators`: the list of operators supported by the field.
- `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter.
- `operators`: the list of operators supported by the field.
- `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter.

### `view`: `object`

Expand All @@ -140,7 +141,7 @@ const view = {
search: '',
filters: [
{ field: 'author', operator: 'is', value: 2 },
{ field: 'status', operator: 'isAny', value: [ 'publish', 'draft'] }
{ field: 'status', operator: 'isAny', value: [ 'publish', 'draft' ] },
],
page: 1,
perPage: 5,
Expand All @@ -150,7 +151,7 @@ const view = {
},
hiddenFields: [ 'date', 'featured-image' ],
layout: {},
}
};
```

Properties:
Expand All @@ -164,8 +165,8 @@ Properties:
- `perPage`: number of records to show per page.
- `page`: the page that is visible.
- `sort`:
- `field`: the field used for sorting the dataset.
- `direction`: the direction to use for sorting, one of `asc` or `desc`.
- `field`: the field used for sorting the dataset.
- `direction`: the direction to use for sorting, one of `asc` or `desc`.
- `hiddenFields`: the `id` of the fields that are hidden in the UI.
- `layout`: config that is specific to a particular layout type.
- `mediaField`: used by the `grid` and `list` layouts. The `id` of the field to be used for rendering each card's media.
Expand All @@ -192,7 +193,11 @@ function MyCustomPageTable() {
search: '',
filters: [
{ field: 'author', operator: 'is', value: 2 },
{ field: 'status', operator: 'isAny', value: [ 'publish', 'draft' ] }
{
field: 'status',
operator: 'isAny',
value: [ 'publish', 'draft' ],
},
],
hiddenFields: [ 'date', 'featured-image' ],
layout: {},
Expand All @@ -219,9 +224,7 @@ function MyCustomPageTable() {
};
}, [ view ] );

const {
records
} = useEntityRecords( 'postType', 'page', queryArgs );
const { records } = useEntityRecords( 'postType', 'page', queryArgs );

return (
<DataViews
Expand All @@ -241,7 +244,7 @@ Collection of operations that can be performed upon each record.
Each action is an object with the following properties:

- `id`: string, required. Unique identifier of the action. For example, `move-to-trash`.
- `label`: string, required. User facing description of the action. For example, `Move to Trash`.
- `label`: string|function, required. User facing description of the action. For example, `Move to Trash`. In case we want to adjust the label based on the selected items, a function which accepts the selected records as input can be provided. This function should always return a `string` value.
- `isPrimary`: boolean, optional. Whether the action should be listed inline (primary) or in hidden in the more actions menu (secondary).
- `icon`: icon to show for primary actions. It's required for a primary action, otherwise the action would be considered secondary.
- `isEligible`: function, optional. Whether the action can be performed for a given record. If not present, the action is considered to be eligible for all items. It takes the given record as input.
Expand All @@ -252,8 +255,8 @@ Each action is an object with the following properties:

### `paginationInfo`: `Object`

- `totalItems`: the total number of items in the datasets.
- `totalPages`: the total number of pages, taking into account the total items in the dataset and the number of items per page provided by the user.
- `totalItems`: the total number of items in the datasets.
- `totalPages`: the total number of pages, taking into account the total items in the dataset and the number of items per page provided by the user.

### `search`: `boolean`

Expand Down Expand Up @@ -283,9 +286,9 @@ Callback that signals the user selected one of more items, and takes them as par

### Layouts

- `table`: the view uses a table layout.
- `grid`: the view uses a grid layout.
- `list`: the view uses a list layout.
- `table`: the view uses a table layout.
- `grid`: the view uses a grid layout.
- `list`: the view uses a list layout.

### Fields

Expand All @@ -295,13 +298,13 @@ Callback that signals the user selected one of more items, and takes them as par

Allowed operators:

| Operator | Selection | Description | Example |
| --- | --- | --- | --- |
| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin |
| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin |
| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor |
| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor |
| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction |
| Operator | Selection | Description | Example |
| ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- |
| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin |
| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin |
| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor |
| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor |
| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction |
| `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction |

`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item.
Expand Down
6 changes: 5 additions & 1 deletion packages/dataviews/src/bulk-actions-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,14 @@ function ActionTrigger< Item extends AnyItem >( {
action,
onClick,
isBusy,
items,
}: ActionTriggerProps< Item > ) {
const label =
typeof action.label === 'string' ? action.label : action.label( items );
return (
<ToolbarButton
disabled={ isBusy }
label={ action.label }
label={ label }
icon={ action.icon }
isDestructive={ action.isDestructive }
size="compact"
Expand Down Expand Up @@ -112,6 +115,7 @@ function ActionButton< Item extends AnyItem >( {
setActionInProgress( action.id );
action.callback( selectedItems );
} }
items={ selectedEligibleItems }
isBusy={ actionInProgress === action.id }
/>
);
Expand Down
6 changes: 5 additions & 1 deletion packages/dataviews/src/bulk-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,13 @@ function ActionWithModal< Item extends AnyItem >( {
const onCloseModal = useCallback( () => {
setActionWithModal( undefined );
}, [ setActionWithModal ] );
const label =
typeof action.label === 'string'
? action.label
: action.label( selectedItems );
return (
<Modal
title={ ! hideModalHeader ? action.label : undefined }
title={ ! hideModalHeader ? label : undefined }
__experimentalHideHeader={ !! hideModalHeader }
onRequestClose={ onCloseModal }
overlayClassName="dataviews-action-modal"
Expand Down
17 changes: 14 additions & 3 deletions packages/dataviews/src/item-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface ActionTriggerProps< Item extends AnyItem > {
action: Action< Item >;
onClick: MouseEventHandler;
isBusy?: boolean;
items: Item[];
}

interface ActionModalProps< Item extends AnyItem > {
Expand Down Expand Up @@ -67,10 +68,13 @@ interface CompactItemActionsProps< Item extends AnyItem > {
function ButtonTrigger< Item extends AnyItem >( {
action,
onClick,
items,
}: ActionTriggerProps< Item > ) {
const label =
typeof action.label === 'string' ? action.label : action.label( items );
return (
<Button
label={ action.label }
label={ label }
icon={ action.icon }
isDestructive={ action.isDestructive }
size="compact"
Expand All @@ -82,13 +86,16 @@ function ButtonTrigger< Item extends AnyItem >( {
function DropdownMenuItemTrigger< Item extends AnyItem >( {
action,
onClick,
items,
}: ActionTriggerProps< Item > ) {
const label =
typeof action.label === 'string' ? action.label : action.label( items );
return (
<DropdownMenuItem
onClick={ onClick }
hideOnClick={ ! ( 'RenderModal' in action ) }
>
<DropdownMenuItemLabel>{ action.label }</DropdownMenuItemLabel>
<DropdownMenuItemLabel>{ label }</DropdownMenuItemLabel>
</DropdownMenuItem>
);
}
Expand All @@ -98,9 +105,11 @@ export function ActionModal< Item extends AnyItem >( {
items,
closeModal,
}: ActionModalProps< Item > ) {
const label =
typeof action.label === 'string' ? action.label : action.label( items );
return (
<Modal
title={ action.modalHeader || action.label }
title={ action.modalHeader || label }
__experimentalHideHeader={ !! action.hideModalHeader }
onRequestClose={ closeModal ?? ( () => {} ) }
overlayClassName={ `dataviews-action-modal dataviews-action-modal__${ kebabCase(
Expand Down Expand Up @@ -168,6 +177,7 @@ export function ActionsDropdownMenuGroup< Item extends AnyItem >( {
key={ action.id }
action={ action }
onClick={ () => action.callback( [ item ] ) }
items={ [ item ] }
/>
);
} ) }
Expand Down Expand Up @@ -224,6 +234,7 @@ export default function ItemActions< Item extends AnyItem >( {
key={ action.id }
action={ action }
onClick={ () => action.callback( [ item ] ) }
items={ [ item ] }
/>
);
} ) }
Expand Down
4 changes: 3 additions & 1 deletion packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,10 @@ interface ActionBase< Item extends AnyItem > {

/**
* The label of the action.
* In case we want to adjust the label based on the selected items,
* a function can be provided.
*/
label: string;
label: string | ( ( items: Item[] ) => string );

/**
* The icon of the action. (Either a string or an SVG element)
Expand Down
9 changes: 7 additions & 2 deletions packages/dataviews/src/view-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ function ListItem< Item extends AnyItem >( {
}, [ actions, item ] );

const [ isModalOpen, setIsModalOpen ] = useState( false );
const primaryActionLabel =
primaryAction &&
( typeof primaryAction.label === 'string'
? primaryAction.label
: primaryAction.label( [ item ] ) );

return (
<CompositeRow
Expand Down Expand Up @@ -193,7 +198,7 @@ function ListItem< Item extends AnyItem >( {
store={ store }
render={
<Button
label={ primaryAction.label }
label={ primaryActionLabel }
icon={ primaryAction.icon }
isDestructive={
primaryAction.isDestructive
Expand Down Expand Up @@ -224,7 +229,7 @@ function ListItem< Item extends AnyItem >( {
store={ store }
render={
<Button
label={ primaryAction.label }
label={ primaryActionLabel }
icon={ primaryAction.icon }
isDestructive={
primaryAction.isDestructive
Expand Down
11 changes: 9 additions & 2 deletions packages/editor/src/components/post-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,15 @@ const viewPostAction = {

const postRevisionsAction = {
id: 'view-post-revisions',
label: __( 'View revisions' ),
isPrimary: false,
label( items ) {
const revisionsCount =
items[ 0 ]._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
return sprintf(
/* translators: %s: number of revisions */
__( 'View revisions (%s)' ),
revisionsCount
);
},
isEligible: ( post ) => {
if ( post.status === 'trash' ) {
return false;
Expand Down
8 changes: 6 additions & 2 deletions packages/editor/src/components/post-actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,15 @@ export default function PostActions( { onActionPerformed, buttonProps } ) {
// so duplicating the code here seems like the least bad option.

// Copied as is from packages/dataviews/src/item-actions.js
function DropdownMenuItemTrigger( { action, onClick } ) {
function DropdownMenuItemTrigger( { action, onClick, items } ) {
const label =
typeof action.label === 'string' ? action.label : action.label( items );
return (
<DropdownMenuItem
onClick={ onClick }
hideOnClick={ ! action.RenderModal }
>
<DropdownMenuItemLabel>{ action.label }</DropdownMenuItemLabel>
<DropdownMenuItemLabel>{ label }</DropdownMenuItemLabel>
</DropdownMenuItem>
);
}
Expand All @@ -105,6 +107,7 @@ function ActionWithModal( { action, item, ActionTrigger, onClose } ) {
const actionTriggerProps = {
action,
onClick: () => setIsModalOpen( true ),
items: [ item ],
};
const { RenderModal, hideModalHeader } = action;
return (
Expand Down Expand Up @@ -156,6 +159,7 @@ function ActionsDropdownMenuGroup( { actions, item, onClose } ) {
key={ action.id }
action={ action }
onClick={ () => action.callback( [ item ] ) }
items={ [ item ] }
/>
);
} ) }
Expand Down

0 comments on commit 6902b05

Please sign in to comment.