diff --git a/packages/dataviews/src/layouts.ts b/packages/dataviews/src/layouts.ts
index 0d00263b6fbc55..f8339c0a6b83f7 100644
--- a/packages/dataviews/src/layouts.ts
+++ b/packages/dataviews/src/layouts.ts
@@ -16,6 +16,7 @@ import ViewTable from './view-table';
import ViewGrid from './view-grid';
import ViewList from './view-list';
import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from './constants';
+import type { View } from './types';
export const VIEW_LAYOUTS = [
{
@@ -37,3 +38,29 @@ export const VIEW_LAYOUTS = [
icon: isRTL() ? formatListBulletsRTL : formatListBullets,
},
];
+
+export function getMandatoryFields( view: View ): string[] {
+ if ( view.type === 'table' ) {
+ return [ view.layout?.primaryField ]
+ .concat(
+ view.layout?.combinedFields?.flatMap(
+ ( field ) => field.children
+ ) ?? []
+ )
+ .filter( ( item ): item is string => !! item );
+ }
+
+ if ( view.type === 'grid' ) {
+ return [ view.layout?.primaryField, view.layout?.mediaField ].filter(
+ ( item ): item is string => !! item
+ );
+ }
+
+ if ( view.type === 'list' ) {
+ return [ view.layout?.primaryField, view.layout?.mediaField ].filter(
+ ( item ): item is string => !! item
+ );
+ }
+
+ return [];
+}
diff --git a/packages/dataviews/src/stories/fixtures.js b/packages/dataviews/src/stories/fixtures.js
index 133f8d3fea573c..cb107e56969d17 100644
--- a/packages/dataviews/src/stories/fixtures.js
+++ b/packages/dataviews/src/stories/fixtures.js
@@ -167,20 +167,17 @@ export const fields = [
);
},
- width: 50,
enableSorting: false,
},
{
header: 'Title',
id: 'title',
- maxWidth: 400,
enableHiding: false,
enableGlobalSearch: true,
},
{
header: 'Type',
id: 'type',
- maxWidth: 400,
enableHiding: false,
elements: [
{ value: 'Not a planet', label: 'Not a planet' },
@@ -197,7 +194,6 @@ export const fields = [
{
header: 'Description',
id: 'description',
- maxWidth: 200,
enableSorting: false,
enableGlobalSearch: true,
},
diff --git a/packages/dataviews/src/stories/index.story.js b/packages/dataviews/src/stories/index.story.js
index c04e89a92baa85..8a5ccd83450237 100644
--- a/packages/dataviews/src/stories/index.story.js
+++ b/packages/dataviews/src/stories/index.story.js
@@ -39,6 +39,20 @@ Default.args = {
[ LAYOUT_TABLE ]: {
layout: {
primaryField: 'title',
+ styles: {
+ image: {
+ width: 50,
+ },
+ title: {
+ maxWidth: 400,
+ },
+ type: {
+ maxWidth: 400,
+ },
+ description: {
+ maxWidth: 200,
+ },
+ },
},
},
[ LAYOUT_GRID ]: {
diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss
index 61d75d92da27d2..de2dcf027c2060 100644
--- a/packages/dataviews/src/style.scss
+++ b/packages/dataviews/src/style.scss
@@ -91,7 +91,7 @@
padding: $grid-unit-15;
white-space: nowrap;
- &[data-field-id="actions"] {
+ &.dataviews-view-table__actions-column {
text-align: right;
}
@@ -215,6 +215,10 @@
}
}
}
+
+ .components-v-stack > .dataviews-view-table__cell-content-wrapper:not(:first-child) {
+ min-height: 0;
+ }
}
.dataviews-view-table-header-button {
padding: $grid-unit-05 $grid-unit-10;
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 9cc9ffb0f76c0c..8e2626245682e6 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -75,21 +75,6 @@ export type Field< Item > = {
*/
render?: ( args: { item: Item } ) => ReactNode;
- /**
- * The width of the field column.
- */
- width?: string | number;
-
- /**
- * The minimum width of the field column.
- */
- maxWidth?: string | number;
-
- /**
- * The maximum width of the field column.
- */
- minWidth?: string | number;
-
/**
* Whether the field is sortable.
*/
@@ -249,11 +234,44 @@ interface ViewBase {
perPage?: number;
/**
- * The hidden fields.
+ * The fields to render
*/
fields?: string[];
}
+export interface CombinedField {
+ id: string;
+
+ header: string;
+
+ /**
+ * The fields to use as columns.
+ */
+ children: string[];
+
+ /**
+ * The direction of the stack.
+ */
+ direction: 'horizontal' | 'vertical';
+}
+
+export interface ColumnStyle {
+ /**
+ * The width of the field column.
+ */
+ width?: string | number;
+
+ /**
+ * The minimum width of the field column.
+ */
+ maxWidth?: string | number;
+
+ /**
+ * The maximum width of the field column.
+ */
+ minWidth?: string | number;
+}
+
export interface ViewTable extends ViewBase {
type: 'table';
@@ -264,9 +282,14 @@ export interface ViewTable extends ViewBase {
primaryField?: string;
/**
- * The field to use as the media field.
+ * The fields to use as columns.
*/
- mediaField?: string;
+ combinedFields?: CombinedField[];
+
+ /**
+ * The styles for the columns.
+ */
+ styles?: Record< string, ColumnStyle >;
};
}
diff --git a/packages/dataviews/src/view-actions.tsx b/packages/dataviews/src/view-actions.tsx
index fd9aff28b6479b..0493cfb5efea1f 100644
--- a/packages/dataviews/src/view-actions.tsx
+++ b/packages/dataviews/src/view-actions.tsx
@@ -20,7 +20,7 @@ import { cog } from '@wordpress/icons';
*/
import { unlock } from './lock-unlock';
import { SORTING_DIRECTIONS, sortLabels } from './constants';
-import { VIEW_LAYOUTS } from './layouts';
+import { VIEW_LAYOUTS, getMandatoryFields } from './layouts';
import type { NormalizedField, View, SupportedLayouts } from './types';
const {
@@ -147,10 +147,11 @@ function FieldsVisibilityMenu< Item >( {
onChangeView,
fields,
}: FieldsVisibilityMenuProps< Item > ) {
+ const mandatoryFields = getMandatoryFields( view );
const hidableFields = fields.filter(
( field ) =>
field.enableHiding !== false &&
- field.id !== view?.layout?.mediaField
+ ! mandatoryFields.includes( field.id )
);
const viewFields = view.fields || fields.map( ( field ) => field.id );
if ( ! hidableFields?.length ) {
diff --git a/packages/dataviews/src/view-table.tsx b/packages/dataviews/src/view-table.tsx
index 1ba489ebe07ef5..f560c56fea183f 100644
--- a/packages/dataviews/src/view-table.tsx
+++ b/packages/dataviews/src/view-table.tsx
@@ -15,6 +15,8 @@ import {
privateApis as componentsPrivateApis,
CheckboxControl,
Spinner,
+ __experimentalHStack as HStack,
+ __experimentalVStack as VStack,
} from '@wordpress/components';
import {
forwardRef,
@@ -50,6 +52,7 @@ import type {
SortDirection,
ViewTable as ViewTableType,
ViewTableProps,
+ CombinedField,
} from './types';
import type { SetSelection } from './private-types';
@@ -63,7 +66,7 @@ const {
} = unlock( componentsPrivateApis );
interface HeaderMenuProps< Item > {
- field: NormalizedField< Item >;
+ fieldId: string;
view: ViewTableType;
fields: NormalizedField< Item >[];
onChangeView: ( view: ViewTableType ) => void;
@@ -79,12 +82,35 @@ interface BulkSelectionCheckboxProps< Item > {
getItemId: ( item: Item ) => string;
}
+interface TableColumnFieldProps< Item > {
+ primaryField?: NormalizedField< Item >;
+ field: NormalizedField< Item >;
+ item: Item;
+}
+
+interface TableColumnCombinedProps< Item > {
+ primaryField?: NormalizedField< Item >;
+ fields: NormalizedField< Item >[];
+ field: CombinedField;
+ item: Item;
+ view: ViewTableType;
+}
+
+interface TableColumnProps< Item > {
+ primaryField?: NormalizedField< Item >;
+ fields: NormalizedField< Item >[];
+ item: Item;
+ column: string;
+ view: ViewTableType;
+}
+
interface TableRowProps< Item > {
hasBulkActions: boolean;
item: Item;
actions: Action< Item >[];
+ fields: NormalizedField< Item >[];
id: string;
- visibleFields: NormalizedField< Item >[];
+ view: ViewTableType;
primaryField?: NormalizedField< Item >;
selection: string[];
getItemId: ( item: Item ) => string;
@@ -104,7 +130,7 @@ function WithDropDownMenuSeparators( { children }: { children: ReactNode } ) {
const _HeaderMenu = forwardRef( function HeaderMenu< Item >(
{
- field,
+ fieldId,
view,
fields,
onChangeView,
@@ -113,6 +139,16 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >(
}: HeaderMenuProps< Item >,
ref: Ref< HTMLButtonElement >
) {
+ const combinedField = view.layout?.combinedFields?.find(
+ ( f ) => f.id === fieldId
+ );
+ if ( !! combinedField ) {
+ return combinedField.header;
+ }
+ const field = fields.find( ( f ) => f.id === fieldId );
+ if ( ! field ) {
+ return null;
+ }
const isHidable = field.enableHiding !== false;
const isSortable = field.enableSorting !== false;
const isSorted = view.sort?.field === field.id;
@@ -227,7 +263,7 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >(
onChangeView( {
...view,
fields: viewFields.filter(
- ( fieldId ) => fieldId !== field.id
+ ( id ) => id !== field.id
),
} );
} }
@@ -292,12 +328,79 @@ function BulkSelectionCheckbox< Item >( {
);
}
+function TableColumn< Item >( {
+ column,
+ fields,
+ view,
+ ...props
+}: TableColumnProps< Item > ) {
+ const field = fields.find( ( f ) => f.id === column );
+ if ( !! field ) {
+ return