Skip to content

Commit

Permalink
DataViews: Implement isItemClickable and onClickItem props (#66365)
Browse files Browse the repository at this point in the history
Co-authored-by: gigitux <gigitux@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: louwie17 <louwie17@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: mcsf <mcsf@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
  • Loading branch information
7 people authored Nov 12, 2024
1 parent e6d38fe commit e07351b
Show file tree
Hide file tree
Showing 16 changed files with 175 additions and 51 deletions.
8 changes: 8 additions & 0 deletions packages/dataviews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,14 @@ Callback that signals the user selected one of more items. It receives the list

If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves as a controlled component, otherwise, it behaves like an uncontrolled component.

### `isItemClickable`: `function`

A function that determines if a media field or a primary field are clickable. It receives an item as an argument and returns a boolean value indicating whether the item can be clicked.

### `onClickItem`: `function`

A callback function that is triggered when a user clicks on a media field or primary field. This function is currently implemented only in the `grid` and `list` views.

#### `header`: React component

React component to be rendered next to the view config button.
Expand Down
4 changes: 4 additions & 0 deletions packages/dataviews/src/components/dataviews-context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type DataViewsContextType< Item > = {
openedFilter: string | null;
setOpenedFilter: ( openedFilter: string | null ) => void;
getItemId: ( item: Item ) => string;
onClickItem: ( item: Item ) => void;
isItemClickable: ( item: Item ) => boolean;
density: number;
};

Expand All @@ -43,6 +45,8 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( {
setOpenedFilter: () => {},
openedFilter: null,
getItemId: ( item ) => item.id,
onClickItem: () => {},
isItemClickable: () => false,
density: 0,
} );

Expand Down
4 changes: 4 additions & 0 deletions packages/dataviews/src/components/dataviews-layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export default function DataViewsLayout() {
onChangeSelection,
setOpenedFilter,
density,
onClickItem,
isItemClickable,
} = useContext( DataViewsContext );

const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type )
Expand All @@ -44,6 +46,8 @@ export default function DataViewsLayout() {
onChangeSelection={ onChangeSelection }
selection={ selection }
setOpenedFilter={ setOpenedFilter }
onClickItem={ onClickItem }
isItemClickable={ isItemClickable }
view={ view }
density={ density }
/>
Expand Down
11 changes: 10 additions & 1 deletion packages/dataviews/src/components/dataviews/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,34 @@ type DataViewsProps< Item > = {
defaultLayouts: SupportedLayouts;
selection?: string[];
onChangeSelection?: ( items: string[] ) => void;
onClickItem?: ( item: Item ) => void;
isItemClickable?: ( item: Item ) => boolean;
header?: ReactNode;
} & ( Item extends ItemWithId
? { getItemId?: ( item: Item ) => string }
: { getItemId: ( item: Item ) => string } );

const defaultGetItemId = ( item: ItemWithId ) => item.id;
const defaultIsItemClickable = () => false;
const defaultOnClickItem = () => {};
const EMPTY_ARRAY: any[] = [];

export default function DataViews< Item >( {
view,
onChangeView,
fields,
search = true,
searchLabel = undefined,
actions = [],
actions = EMPTY_ARRAY,
data,
getItemId = defaultGetItemId,
isLoading = false,
paginationInfo,
defaultLayouts,
selection: selectionProperty,
onChangeSelection,
onClickItem = defaultOnClickItem,
isItemClickable = defaultIsItemClickable,
header,
}: DataViewsProps< Item > ) {
const [ selectionState, setSelectionState ] = useState< string[] >( [] );
Expand Down Expand Up @@ -110,6 +117,8 @@ export default function DataViews< Item >( {
openedFilter,
setOpenedFilter,
getItemId,
isItemClickable,
onClickItem,
density,
} }
>
Expand Down
12 changes: 9 additions & 3 deletions packages/dataviews/src/components/dataviews/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
position: sticky;
left: 0;
transition: padding ease-out 0.1s;
@include reduce-motion("transition");
@include reduce-motion( "transition" );
}

.dataviews-view-list__primary-field,
Expand Down Expand Up @@ -62,6 +62,13 @@
}
}

.dataviews-view-list__primary-field--clickable,
.dataviews-view-grid__primary-field--clickable,
.dataviews-view-grid__media--clickable,
.dataviews-view-table__primary-field > .dataviews-view-table__cell-content--clickable {
cursor: pointer;
}

.dataviews-no-results,
.dataviews-loading {
padding: 0 $grid-unit-60;
Expand All @@ -70,7 +77,7 @@
align-items: center;
justify-content: center;
transition: padding ease-out 0.1s;
@include reduce-motion("transition");
@include reduce-motion( "transition" );
}

/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */
Expand All @@ -86,4 +93,3 @@
padding-right: $grid-unit-30;
}
}

34 changes: 29 additions & 5 deletions packages/dataviews/src/dataviews-layouts/grid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ import SingleSelectionCheckbox from '../../components/dataviews-selection-checkb
import { useHasAPossibleBulkAction } from '../../components/dataviews-bulk-actions';
import type { Action, NormalizedField, ViewGridProps } from '../../types';
import type { SetSelection } from '../../private-types';
import getClickableItemProps from '../utils/get-clickable-item-props';

interface GridItemProps< Item > {
selection: string[];
onChangeSelection: SetSelection;
getItemId: ( item: Item ) => string;
onClickItem: ( item: Item ) => void;
isItemClickable: ( item: Item ) => boolean;
item: Item;
actions: Action< Item >[];
mediaField?: NormalizedField< Item >;
Expand All @@ -41,6 +44,8 @@ interface GridItemProps< Item > {
function GridItem< Item >( {
selection,
onChangeSelection,
onClickItem,
isItemClickable,
getItemId,
item,
actions,
Expand All @@ -59,6 +64,21 @@ function GridItem< Item >( {
const renderedPrimaryField = primaryField?.render ? (
<primaryField.render item={ item } />
) : null;

const clickableMediaItemProps = getClickableItemProps(
item,
isItemClickable,
onClickItem,
'dataviews-view-grid__media'
);

const clickablePrimaryItemProps = getClickableItemProps(
item,
isItemClickable,
onClickItem,
'dataviews-view-grid__primary-field'
);

return (
<VStack
spacing={ 0 }
Expand All @@ -81,9 +101,7 @@ function GridItem< Item >( {
}
} }
>
<div className="dataviews-view-grid__media">
{ renderedMediaField }
</div>
<div { ...clickableMediaItemProps }>{ renderedMediaField }</div>
<SingleSelectionCheckbox
item={ item }
selection={ selection }
Expand All @@ -96,8 +114,10 @@ function GridItem< Item >( {
justify="space-between"
className="dataviews-view-grid__title-actions"
>
<HStack className="dataviews-view-grid__primary-field">
{ renderedPrimaryField }
<HStack>
<div { ...clickablePrimaryItemProps }>
{ renderedPrimaryField }
</div>
</HStack>
<ItemActions item={ item } actions={ actions } isCompact />
</HStack>
Expand Down Expand Up @@ -170,6 +190,8 @@ export default function ViewGrid< Item >( {
getItemId,
isLoading,
onChangeSelection,
onClickItem,
isItemClickable,
selection,
view,
density,
Expand Down Expand Up @@ -223,6 +245,8 @@ export default function ViewGrid< Item >( {
key={ getItemId( item ) }
selection={ selection }
onChangeSelection={ onChangeSelection }
onClickItem={ onClickItem }
isItemClickable={ isItemClickable }
getItemId={ getItemId }
item={ item }
actions={ actions }
Expand Down
5 changes: 5 additions & 0 deletions packages/dataviews/src/dataviews-layouts/grid/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@

.dataviews-view-grid__primary-field {
min-height: $grid-unit-40; // Preserve layout when there is no ellipsis button

&--clickable {
width: fit-content;
}
}


&.is-selected {
.dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-value {
color: $gray-900;
Expand Down
37 changes: 34 additions & 3 deletions packages/dataviews/src/dataviews-layouts/table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ import type {
import type { SetSelection } from '../../private-types';
import ColumnHeaderMenu from './column-header-menu';
import { getVisibleFieldIds } from '../index';
import getClickableItemProps from '../utils/get-clickable-item-props';

interface TableColumnFieldProps< Item > {
primaryField?: NormalizedField< Item >;
field: NormalizedField< Item >;
item: Item;
isItemClickable: ( item: Item ) => boolean;
onClickItem: ( item: Item ) => void;
}

interface TableColumnCombinedProps< Item > {
Expand All @@ -48,6 +51,8 @@ interface TableColumnCombinedProps< Item > {
field: CombinedField;
item: Item;
view: ViewTableType;
isItemClickable: ( item: Item ) => boolean;
onClickItem: ( item: Item ) => void;
}

interface TableColumnProps< Item > {
Expand All @@ -56,6 +61,8 @@ interface TableColumnProps< Item > {
item: Item;
column: string;
view: ViewTableType;
isItemClickable: ( item: Item ) => boolean;
onClickItem: ( item: Item ) => void;
}

interface TableRowProps< Item > {
Expand All @@ -69,6 +76,8 @@ interface TableRowProps< Item > {
selection: string[];
getItemId: ( item: Item ) => string;
onChangeSelection: SetSelection;
isItemClickable: ( item: Item ) => boolean;
onClickItem: ( item: Item ) => void;
}

function TableColumn< Item >( {
Expand Down Expand Up @@ -102,15 +111,29 @@ function TableColumnField< Item >( {
primaryField,
item,
field,
isItemClickable,
onClickItem,
}: TableColumnFieldProps< Item > ) {
const isPrimaryField = primaryField?.id === field.id;
const isItemClickableField = ( i: Item ) =>
isItemClickable( i ) && isPrimaryField;

const clickableProps = getClickableItemProps(
item,
isItemClickableField,
onClickItem,
'dataviews-view-table__cell-content'
);

return (
<div
className={ clsx( 'dataviews-view-table__cell-content-wrapper', {
'dataviews-view-table__primary-field':
primaryField?.id === field.id,
'dataviews-view-table__primary-field': isPrimaryField,
} ) }
>
<field.render { ...{ item } } />
<div { ...clickableProps }>
<field.render { ...{ item } } />
</div>
</div>
);
}
Expand Down Expand Up @@ -139,6 +162,8 @@ function TableRow< Item >( {
primaryField,
selection,
getItemId,
isItemClickable,
onClickItem,
onChangeSelection,
}: TableRowProps< Item > ) {
const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item );
Expand Down Expand Up @@ -214,6 +239,8 @@ function TableRow< Item >( {
<td key={ column } style={ { width, maxWidth, minWidth } }>
<TableColumn
primaryField={ primaryField }
isItemClickable={ isItemClickable }
onClickItem={ onClickItem }
fields={ fields }
item={ item }
column={ column }
Expand Down Expand Up @@ -252,6 +279,8 @@ function ViewTable< Item >( {
onChangeSelection,
selection,
setOpenedFilter,
onClickItem,
isItemClickable,
view,
}: ViewTableProps< Item > ) {
const headerMenuRefs = useRef<
Expand Down Expand Up @@ -392,6 +421,8 @@ function ViewTable< Item >( {
selection={ selection }
getItemId={ getItemId }
onChangeSelection={ onChangeSelection }
onClickItem={ onClickItem }
isItemClickable={ isItemClickable }
/>
) ) }
</tbody>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default function getClickableItemProps< Item >(
item: Item,
isItemClickable: ( item: Item ) => boolean,
onClickItem: ( item: Item ) => void,
className: string
) {
if ( ! isItemClickable( item ) ) {
return { className };
}

return {
className: `${ className } ${ className }--clickable`,
role: 'button',
tabIndex: 0,
onClick: () => onClickItem( item ),
onKeyDown: ( event: React.KeyboardEvent ) => {
if ( event.key === 'Enter' || event.key === '' ) {
onClickItem( item );
}
},
};
}
2 changes: 2 additions & 0 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ export interface ViewBaseProps< Item > {
onChangeSelection: SetSelection;
selection: string[];
setOpenedFilter: ( fieldId: string ) => void;
onClickItem: ( item: Item ) => void;
isItemClickable: ( item: Item ) => boolean;
view: View;
density: number;
}
Expand Down
Loading

0 comments on commit e07351b

Please sign in to comment.