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:
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user