Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
336c848
DataForm - Card Layout: implement summary field
gigitux Sep 10, 2025
2a68e6e
add documentation
gigitux Sep 10, 2025
ecd4d51
Merge branch 'trunk' of github.com:WordPress/gutenberg into nex-383-d…
gigitux Sep 24, 2025
d1d1569
replace summaryField specific for the Card Layout with the summary field
gigitux Sep 25, 2025
d7f822a
update documentation
gigitux Sep 25, 2025
7988f20
add summary prop to the SimpleFormField
gigitux Sep 25, 2025
caee7ea
add summary documentation
gigitux Sep 25, 2025
50f0f42
update changelog
gigitux Sep 25, 2025
8c7f710
Merge branch 'trunk' of github.com:WordPress/gutenberg into nex-383-d…
gigitux Sep 25, 2025
7c82254
clean up code
gigitux Sep 25, 2025
48b5a5b
fix unit test
gigitux Sep 25, 2025
e594c9a
update logic
gigitux Sep 30, 2025
8e2483e
Merge branch 'trunk' of github.com:WordPress/gutenberg into nex-383-d…
gigitux Sep 30, 2025
fd6b826
update documentation
gigitux Sep 30, 2025
9212350
remove export
gigitux Sep 30, 2025
621e9ad
fix unit test
gigitux Sep 30, 2025
1e973c8
fix logic
gigitux Sep 30, 2025
094d7a0
refine Card Summary Field type
gigitux Oct 1, 2025
84ffc13
improve logic
gigitux Oct 1, 2025
0524341
fix e2e test
gigitux Oct 1, 2025
f9cf0a5
use summary inside the layout property
gigitux Oct 1, 2025
0454fa0
update story
gigitux Oct 2, 2025
98c6be0
improve visibility logic
gigitux Oct 2, 2025
bb2e019
normalize summary layout
gigitux Oct 2, 2025
a00dbcc
fix unit test
gigitux Oct 2, 2025
ff1fef6
fix logic regarding summary field in the panel layout
gigitux Oct 3, 2025
0ed5ad9
update default value
gigitux Oct 3, 2025
755047d
update story
gigitux Oct 3, 2025
00033ed
refactor normalization of card summary field for improved clarity and…
gigitux Oct 3, 2025
bd85ec7
improve changelog
gigitux Oct 3, 2025
dce85e6
fix unit test
gigitux Oct 3, 2025
5b3694f
add breaking changes section
gigitux Oct 3, 2025
e19e7b0
Merge branch 'trunk' of github.com:WordPress/gutenberg into nex-383-d…
gigitux Oct 3, 2025
3e418c7
Update README
oandregal Oct 3, 2025
a3d9c7e
Remove layout info so highlight summary coming from field ID
oandregal Oct 3, 2025
df89d60
Better summaries
oandregal Oct 3, 2025
9df0b31
Update changelog
oandregal Oct 3, 2025
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: 4 additions & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Breaking changes

- DataForm: Add summary field support for both card and panel layouts. The `summary` property has been moved from the field level to the layout level, and so fields using `summary` at the field level must now configure it within the `layout` object. Additionally, the first children will only be used as summary for the panel if 1) there is no `layout.summary` and 2) the form field ID doesn't match any existing field. See README for details. [#71576](https://github.com/WordPress/gutenberg/pull/71576)

## 9.1.0 (2025-10-01)

### Features
Expand Down
55 changes: 38 additions & 17 deletions packages/dataviews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ Example:
const form = {
layout: {
type: 'panel',
labelPosition: 'side'
labelPosition: 'side',
},
fields: [
'title',
Expand Down Expand Up @@ -1235,9 +1235,9 @@ Example:

Object that contains the validation rules for the field. If a rule is not met, the control will be marked as invalid and a message will be displayed.

- `required`: boolean indicating whether the field is required or not.
- `elements`: boolean restricting selection to the provided list of elements only. Used with the `array` field type.
- `custom`: a function that validates a field's value. If the value is invalid, the function should return a string explaining why the value is invalid. Otherwise, the function must return null.
- `required`: boolean indicating whether the field is required or not.
- `elements`: boolean restricting selection to the provided list of elements only. Used with the `array` field type.
- `custom`: a function that validates a field's value. If the value is invalid, the function should return a string explaining why the value is invalid. Otherwise, the function must return null.

Example:

Expand Down Expand Up @@ -1280,9 +1280,9 @@ Fields that define their own Edit component have access to the validation rules

```js
{
Edit: ( { field }) => {
return <input required={ !! field.isValid.required } />
}
Edit: ( { field } ) => {
return <input required={ !! field.isValid.required } />;
};
}
```

Expand Down Expand Up @@ -1409,8 +1409,8 @@ Operators:
| `contains` | Text | `CONTAINS`. The item's field contains the given substring. | Title contains: Mars |
| `notContains` | Text | `NOT CONTAINS`. The item's field does not contain the given substring. | Description doesn't contain: photo |
| `startsWith` | Text | `STARTS WITH`. The item's field starts with the given substring. | Title starts with: Mar |
| `on` | Date | `ON`. The item's field is on a given date (date equality using proper date parsing). | Date is on: 2024-01-01 |
| `notOn` | Date | `NOT ON`. The item's field is not on a given date (date inequality using proper date parsing). | Date is not on: 2024-01-01 |
| `on` | Date | `ON`. The item's field is on a given date (date equality using proper date parsing). | Date is on: 2024-01-01 |
| `notOn` | Date | `NOT ON`. The item's field is not on a given date (date inequality using proper date parsing). | Date is not on: 2024-01-01 |
| `before` | Date | `BEFORE`. The item's field is before a given date. | Date is before 2024-01-01 |
| `after` | Date | `AFTER`. The item's field is after a given date. | Date is after 2024-01-01 |
| `beforeInc` | Date | `BEFORE (Inc)`. The item's field is before a given date, including the date. | Date is before 2024-01-01, including 2024-01-01 |
Expand Down Expand Up @@ -1480,8 +1480,8 @@ Represents the type of layout used to render the field. It'll be one of Regular,

#### Regular

- `type`: `regular`. Required.
- `labelPosition`: one of `side`, `top`, or `none`. Optional. `top` by default.
- `type`: `regular`. Required.
- `labelPosition`: one of `side`, `top`, or `none`. Optional. `top` by default.

For example:

Expand All @@ -1499,6 +1499,16 @@ For example:

- `type`: `panel`. Required.
- `labelPosition`: one of `side`, `top`, or `none`. Optional. `top` by default.
- `summary`: Summary field configuration. Optional. Specifies which field(s) to display in the panel header. Can be:
- A string (single field ID)
- An array of strings (multiple field IDs)

When no summary fields are explicitly configured, the panel automatically determines which fields to display using this priority:

1. Use `summary` fields if they exist
2. Fall back to the field definition that matches the form field's id
3. If the form field id doesn't exist, pick the first child field
4. If no field definition is found, return empty summary fields

For example:
```js
Expand All @@ -1513,9 +1523,18 @@ For example:

#### Card

- `type`: `card`. Required.
- `isOpened`: boolean. Optional. `true` by default.
- `withHeader`: boolean. Optional. `true` by default.
- `type`: `card`. Required.
- `isOpened`: boolean. Optional. `true` by default.
- `withHeader`: boolean. Optional. `true` by default.
- `summary`: Summary field configuration. Optional. Specifies which field(s) to display in the card header. Can be:
- A string (single field ID)
- An array of strings (multiple field IDs)
- An array of objects for per-field visibility control `[{ id: string, visibility: 'always' | 'when-collapsed' }]`

Cards can be collapsed while visible, so you can control when summary fields appear:

- `'always'`: Show the field in both expanded and collapsed states.
- `'when-collapsed'`: Show the field only when the card is collapsed. This is the default.

For example:

Expand All @@ -1532,8 +1551,8 @@ For example:

#### Row

- `type`: `row`. Required.
- `alignment`: one of `start`, `center`, or `end`. Optional. `center` by default.
- `type`: `row`. Required.
- `alignment`: one of `start`, `center`, or `end`. Optional. `center` by default.

The Row layout displays fields horizontally in a single row. It's particularly useful for grouping related fields that should be displayed side by side. This layout can be used both as a top-level form layout and for individual field groups.

Expand Down Expand Up @@ -1576,7 +1595,9 @@ Example:
```js
{
id: 'status',
layout: 'panel',
layout: {
type: 'panel',
},
label: 'Combined Field',
children: [ 'field1', 'field2' ],
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type {
} from '../../../types';
import { unlock } from '../../../lock-unlock';

const { ValidatedTextControl } = unlock( privateApis );
const { ValidatedTextControl, Badge } = unlock( privateApis );

type SamplePost = {
title: string;
Expand Down Expand Up @@ -431,7 +431,6 @@ const LayoutPanelComponent = ( {
id: 'discussion',
label: 'Discussion',
children: [ 'comment_status', 'ping_status' ],
summary: 'discussion',
},
{
id: 'address1',
Expand All @@ -447,13 +446,19 @@ const LayoutPanelComponent = ( {
'flight_status',
'gate',
],
summary: [ 'origin', 'destination', 'flight_status' ],
layout: {
type: 'panel',
summary: [ 'origin', 'destination', 'flight_status' ],
},
},
{
id: 'passenger_details',
label: 'Passenger Details',
children: [ 'author', 'seat' ],
summary: [ 'author', 'seat' ],
layout: {
type: 'panel',
summary: [ 'author', 'seat' ],
},
},
],
};
Expand Down Expand Up @@ -999,6 +1004,7 @@ const LayoutCardComponent = ( { withHeader }: { withHeader: boolean } ) => {
hasVat: boolean;
vat: number;
commission: number;
dueDate: string;
};

const customerFields: Field< Customer >[] = [
Expand Down Expand Up @@ -1070,6 +1076,22 @@ const LayoutCardComponent = ( { withHeader }: { withHeader: boolean } ) => {
label: 'Commission',
type: 'integer',
},
{
id: 'dueDate',
label: 'Due Date',
type: 'text',
render: ( { item } ) => {
return <Badge>Due on: { item.dueDate }</Badge>;
},
},
{
id: 'plan-summary',
type: 'text',
readOnly: true,
render: ( { item } ) => {
return <Badge>{ item.plan }</Badge>;
},
},
];

const [ customer, setCustomer ] = useState< Customer >( {
Expand All @@ -1086,6 +1108,7 @@ const LayoutCardComponent = ( { withHeader }: { withHeader: boolean } ) => {
hasVat: true,
vat: 10,
commission: 5,
dueDate: 'March 1st, 2028',
} );

const form: Form = useMemo(
Expand All @@ -1097,14 +1120,18 @@ const LayoutCardComponent = ( { withHeader }: { withHeader: boolean } ) => {
fields: [
{
id: 'customerCard',
layout: { type: 'card', summary: 'plan-summary' },
label: 'Customer',
description:
'Enter your contact details, plan type, and addresses to complete your customer information.',
children: [
{
id: 'customerContact',
label: 'Contact',
layout: { type: 'panel', labelPosition: 'top' },
layout: {
type: 'panel',
labelPosition: 'top',
},
children: [
{
id: 'name',
Expand Down Expand Up @@ -1157,6 +1184,7 @@ const LayoutCardComponent = ( { withHeader }: { withHeader: boolean } ) => {
layout: {
type: 'card',
isOpened: false,
summary: [ { id: 'dueDate', visibility: 'always' } ],
},
children: [ 'vat', 'commission' ],
},
Expand Down
109 changes: 102 additions & 7 deletions packages/dataviews/src/dataforms-layouts/card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ import { chevronDown, chevronUp } from '@wordpress/icons';
*/
import { getFormFieldLayout } from '..';
import DataFormContext from '../../components/dataform-context';
import type { NormalizedCardLayout, FieldLayoutProps, Form } from '../../types';
import type {
NormalizedCardLayout,
CardLayout,
FieldLayoutProps,
Form,
Layout,
NormalizedField,
} from '../../types';
import { DataFormLayout } from '../data-form-layout';
import { isCombinedField } from '../is-combined-field';
import { DEFAULT_LAYOUT, normalizeLayout } from '../../normalize-form-fields';
import { getSummaryFields } from '../get-summary-fields';

export function useCollapsibleCard( initialIsOpen: boolean = true ) {
const [ isOpen, setIsOpen ] = useState( initialIsOpen );
Expand Down Expand Up @@ -63,6 +71,57 @@ export function useCollapsibleCard( initialIsOpen: boolean = true ) {
return { isOpen, CollapsibleCardHeader };
}

function isSummaryFieldVisible< Item >(
summaryField: NormalizedField< Item >,
summaryConfig: NormalizedCardLayout[ 'summary' ],
isOpen: boolean
) {
// If no summary config, dont't show any fields
if (
! summaryConfig ||
( Array.isArray( summaryConfig ) && summaryConfig.length === 0 )
) {
return false;
}

// Convert to array for consistent handling
const summaryConfigArray = Array.isArray( summaryConfig )
? summaryConfig
: [ summaryConfig ];

// Find the config for this specific field
const fieldConfig = summaryConfigArray.find( ( config ) => {
if ( typeof config === 'string' ) {
return config === summaryField.id;
}
if ( typeof config === 'object' && 'id' in config ) {
return config.id === summaryField.id;
}
return false;
} );

// If field is not in summary config, don't show it
if ( ! fieldConfig ) {
return false;
}

// If it's a string, always show it
if ( typeof fieldConfig === 'string' ) {
return true;
}

// If it has visibility rules, respect them
if ( typeof fieldConfig === 'object' && 'visibility' in fieldConfig ) {
return (
fieldConfig.visibility === 'always' ||
( fieldConfig.visibility === 'when-collapsed' && ! isOpen )
);
}

// Default to always show
return true;
}

export default function FormCardField< Item >( {
data,
field,
Expand All @@ -74,11 +133,11 @@ export default function FormCardField< Item >( {
const layout: NormalizedCardLayout = normalizeLayout( {
...field.layout,
type: 'card',
} ) as NormalizedCardLayout;
} as CardLayout ) as NormalizedCardLayout;

const form: Form = useMemo(
(): Form => ( {
layout: DEFAULT_LAYOUT,
layout: DEFAULT_LAYOUT as Layout,
fields: isCombinedField( field ) ? field.children : [],
} ),
[ field ]
Expand All @@ -87,13 +146,36 @@ export default function FormCardField< Item >( {
const { isOpen, CollapsibleCardHeader } = useCollapsibleCard(
layout.isOpened
);

const summaryFields = getSummaryFields< Item >( layout.summary, fields );

const visibleSummaryFields = summaryFields.filter( ( summaryField ) =>
isSummaryFieldVisible( summaryField, layout.summary, isOpen )
);

if ( isCombinedField( field ) ) {
const withHeader = !! field.label && layout.withHeader;
return (
<Card className="dataforms-layouts-card__field">
{ withHeader && (
<CollapsibleCardHeader className="dataforms-layouts-card__field-label">
{ field.label }
<CollapsibleCardHeader className="dataforms-layouts-card__field-header">
<span className="dataforms-layouts-card__field-header-label">
{ field.label }
</span>
{ visibleSummaryFields.length > 0 &&
layout.withHeader && (
<div className="dataforms-layouts-card__field-summary">
{ visibleSummaryFields.map(
( summaryField ) => (
<summaryField.render
key={ summaryField.id }
item={ data }
field={ summaryField }
/>
)
) }
</div>
) }
</CollapsibleCardHeader>
) }
{ ( isOpen || ! withHeader ) && (
Expand Down Expand Up @@ -132,8 +214,21 @@ export default function FormCardField< Item >( {
return (
<Card className="dataforms-layouts-card__field">
{ withHeader && (
<CollapsibleCardHeader className="dataforms-layouts-card__field-label">
{ fieldDefinition.label }
<CollapsibleCardHeader className="dataforms-layouts-card__field-header">
<span className="dataforms-layouts-card__field-header-label">
{ fieldDefinition.label }
</span>
{ visibleSummaryFields.length > 0 && layout.withHeader && (
<div className="dataforms-layouts-card__field-summary">
{ visibleSummaryFields.map( ( summaryField ) => (
<summaryField.render
key={ summaryField.id }
item={ data }
field={ summaryField }
/>
) ) }
</div>
) }
</CollapsibleCardHeader>
) }
{ ( isOpen || ! withHeader ) && (
Expand Down
6 changes: 6 additions & 0 deletions packages/dataviews/src/dataforms-layouts/card/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@
font-size: $font-size-medium;
margin-bottom: $grid-unit-20;
}

.dataforms-layouts-card__field-summary {
display: flex;
flex-direction: row;
gap: $grid-unit-20;
}
Loading
Loading