fix(ui): blocks field not respecting width styles in row layouts (#13502)

### What?

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

### Why?

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

### How?

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

Fixes #13498
This commit is contained in:
Patrik
2025-08-18 12:15:40 -04:00
committed by GitHub
parent f9bbca8bfe
commit 30ea8e1bac
4 changed files with 121 additions and 0 deletions

View File

@@ -35,6 +35,7 @@ import './index.scss'
import { FieldDescription } from '../FieldDescription/index.js' import { FieldDescription } from '../FieldDescription/index.js'
import { FieldError } from '../FieldError/index.js' import { FieldError } from '../FieldError/index.js'
import { FieldLabel } from '../FieldLabel/index.js' import { FieldLabel } from '../FieldLabel/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { fieldBaseClass } from '../shared/index.js' import { fieldBaseClass } from '../shared/index.js'
import { BlockRow } from './BlockRow.js' import { BlockRow } from './BlockRow.js'
import { BlocksDrawer } from './BlocksDrawer/index.js' import { BlocksDrawer } from './BlocksDrawer/index.js'
@@ -45,6 +46,7 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const { const {
field,
field: { field: {
name, name,
type, type,
@@ -294,6 +296,8 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
const showMinRows = rows.length < minRows || (required && rows.length === 0) const showMinRows = rows.length < minRows || (required && rows.length === 0)
const showRequired = readOnly && rows.length === 0 const showRequired = readOnly && rows.length === 0
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -305,6 +309,7 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (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

@@ -205,4 +205,41 @@ describe('Row', () => {
expect(fieldABox.height).toEqual(fieldBBox.height) expect(fieldABox.height).toEqual(fieldBBox.height)
}).toPass() }).toPass()
}) })
test('should respect admin.width for Blocks fields inside a row', async () => {
await page.goto(url.create)
// Target the Blocks field wrappers
const left = page.locator('#field-leftColumn')
const right = page.locator('#field-rightColumn')
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(),
)
expect(leftVar).toBe('50%')
expect(rightVar).toBe('50%')
// 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()
})
}) })

View File

@@ -137,6 +137,47 @@ const RowFields: CollectionConfig = {
}, },
], ],
}, },
{
type: 'row',
fields: [
{
name: 'leftColumn',
type: 'blocks',
blocks: [
{
slug: 'leftTextBlock',
fields: [
{
name: 'leftText',
type: 'text',
},
],
},
],
admin: {
width: '50%',
},
},
{
name: 'rightColumn',
type: 'blocks',
blocks: [
{
slug: 'rightTextBlock',
fields: [
{
name: 'rightText',
type: 'text',
},
],
},
],
admin: {
width: '50%',
},
},
],
},
], ],
} }

View File

@@ -1129,6 +1129,22 @@ export interface RowField {
no_set_width_within_row_b?: string | null; no_set_width_within_row_b?: string | null;
no_set_width_within_row_c?: string | null; no_set_width_within_row_c?: string | null;
field_20_percent_width_within_row_d?: string | null; field_20_percent_width_within_row_d?: string | null;
leftColumn?:
| {
leftText?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'leftTextBlock';
}[]
| null;
rightColumn?:
| {
rightText?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'rightTextBlock';
}[]
| null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -2760,6 +2776,28 @@ export interface RowFieldsSelect<T extends boolean = true> {
no_set_width_within_row_b?: T; no_set_width_within_row_b?: T;
no_set_width_within_row_c?: T; no_set_width_within_row_c?: T;
field_20_percent_width_within_row_d?: T; field_20_percent_width_within_row_d?: T;
leftColumn?:
| T
| {
leftTextBlock?:
| T
| {
leftText?: T;
id?: T;
blockName?: T;
};
};
rightColumn?:
| T
| {
rightTextBlock?:
| T
| {
rightText?: T;
id?: T;
blockName?: T;
};
};
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }