fix(ui): array fields not respecting width styles in row layouts (#13986)

### What?

This PR applies `mergeFieldStyles` to the `ArrayFieldComponent`
component, ensuring that custom admin styles such as `width` are
correctly respected when Array fields are placed inside row layouts.

### Why?

Previously, Array fields did not inherit or apply their `admin.width`
(or other merged field styles). For example, when placing two array
fields side by side inside a row with `width: '50%'`, the widths were
ignored, causing layout issues.

### How?

- Imported and used `mergeFieldStyles` within `ArrayFieldComponent`.
- Applied the merged styles to the root `<div>` via the `style` prop,
consistent with how other field components (like `TextField`) handle
styles.

Fixes #13973 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211511898438801
This commit is contained in:
Patrik
2025-10-01 13:15:30 -04:00
committed by GitHub
parent 54b6f15392
commit accd95ec8a
4 changed files with 104 additions and 3 deletions

View File

@@ -6,7 +6,7 @@ import type {
} from 'payload' } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { Fragment, useCallback } from 'react' import React, { Fragment, useCallback, useMemo } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import type { ClipboardPasteData } from '../../elements/ClipboardAction/types.js' import type { ClipboardPasteData } from '../../elements/ClipboardAction/types.js'
@@ -36,6 +36,7 @@ import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { useLocale } from '../../providers/Locale/index.js' import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { scrollToID } from '../../utilities/scrollToID.js' import { scrollToID } from '../../utilities/scrollToID.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { fieldBaseClass } from '../shared/index.js' import { fieldBaseClass } from '../shared/index.js'
import { ArrayRow } from './ArrayRow.js' import { ArrayRow } from './ArrayRow.js'
import './index.scss' import './index.scss'
@@ -44,6 +45,7 @@ const baseClass = 'array-field'
export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => { export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, name,
type, type,
@@ -299,6 +301,8 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
const showRequired = (readOnly || disabled) && rows.length === 0 const showRequired = (readOnly || disabled) && rows.length === 0
const showMinRows = (rows.length && rows.length < minRows) || (required && rows.length === 0) const showMinRows = (rows.length && rows.length < minRows) || (required && rows.length === 0)
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -310,6 +314,7 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
id={`field-${path.replace(/\./g, '__')}`} id={`field-${path.replace(/\./g, '__')}`}
style={styles}
> >
{showError && ( {showError && (
<RenderCustomComponent <RenderCustomComponent

View File

@@ -224,8 +224,49 @@ describe('Row', () => {
getComputedStyle(el).getPropertyValue('--field-width').trim(), getComputedStyle(el).getPropertyValue('--field-width').trim(),
) )
await expect(() => {
expect(leftVar).toBe('50%') expect(leftVar).toBe('50%')
expect(rightVar).toBe('50%') expect(rightVar).toBe('50%')
}).toPass()
// Also assert inline style contains the var (robust to other inline styles)
await expect(left).toHaveAttribute('style', /--field-width:\s*50%/)
await expect(right).toHaveAttribute('style', /--field-width:\s*50%/)
// 2) Layout reflects the widths (same row, equal widths)
const leftBox = await left.boundingBox()
const rightBox = await right.boundingBox()
await expect(() => {
// Same row
expect(Math.round(leftBox.y)).toEqual(Math.round(rightBox.y))
// Equal width (tolerate sub-pixel differences)
expect(Math.round(leftBox.width)).toEqual(Math.round(rightBox.width))
}).toPass()
})
test('should respect admin.width for array fields inside a row', async () => {
await page.goto(url.create)
// Target the Array field wrappers
const left = page.locator('#field-arrayLeftColumn')
const right = page.locator('#field-arrayRightColumn')
await expect(left).toBeVisible()
await expect(right).toBeVisible()
// 1) CSS variable is applied (via mergeFieldStyles)
const leftVar = await left.evaluate((el) =>
getComputedStyle(el).getPropertyValue('--field-width').trim(),
)
const rightVar = await right.evaluate((el) =>
getComputedStyle(el).getPropertyValue('--field-width').trim(),
)
await expect(() => {
expect(leftVar).toBe('50%')
expect(rightVar).toBe('50%')
}).toPass()
// Also assert inline style contains the var (robust to other inline styles) // Also assert inline style contains the var (robust to other inline styles)
await expect(left).toHaveAttribute('style', /--field-width:\s*50%/) await expect(left).toHaveAttribute('style', /--field-width:\s*50%/)

View File

@@ -178,6 +178,37 @@ const RowFields: CollectionConfig = {
}, },
], ],
}, },
{
type: 'row',
fields: [
{
name: 'arrayLeftColumn',
type: 'array',
admin: {
width: '50%',
},
fields: [
{
name: 'leftArrayChild',
type: 'text',
},
],
},
{
name: 'arrayRightColumn',
type: 'array',
fields: [
{
name: 'rightArrayChild',
type: 'text',
},
],
admin: {
width: '50%',
},
},
],
},
], ],
} }

View File

@@ -1194,6 +1194,18 @@ export interface RowField {
blockType: 'rightTextBlock'; blockType: 'rightTextBlock';
}[] }[]
| null; | null;
arrayLeftColumn?:
| {
leftArrayChild?: string | null;
id?: string | null;
}[]
| null;
arrayRightColumn?:
| {
rightArrayChild?: string | null;
id?: string | null;
}[]
| null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -2904,6 +2916,18 @@ export interface RowFieldsSelect<T extends boolean = true> {
blockName?: T; blockName?: T;
}; };
}; };
arrayLeftColumn?:
| T
| {
leftArrayChild?: T;
id?: T;
};
arrayRightColumn?:
| T
| {
rightArrayChild?: T;
id?: T;
};
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }