feat: allow hiding the blockName field visible in blocks' headers via admin.disableBlockName (#11301)

Adds a new `admin.disableBlockName` property that allows you to disable
the blockName field entirely in the admin view. It defaults to false for
backwards compatibility.
This commit is contained in:
Paul
2025-03-05 18:24:39 +00:00
committed by GitHub
parent 4ebe67324a
commit 143b6e3b8e
9 changed files with 198 additions and 12 deletions

View File

@@ -84,6 +84,7 @@ The Blocks Field inherits all of the default options from the base [Field Admin
| **`group`** | Text or localization object used to group this Block in the Blocks Drawer. | | **`group`** | Text or localization object used to group this Block in the Blocks Drawer. |
| **`initCollapsed`** | Set the initial collapsed state | | **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable order sorting by setting this value to `false` | | **`isSortable`** | Disable order sorting by setting this value to `false` |
| **`disableBlockName`** | Hide the blockName field by setting this value to `true` |
#### Customizing the way your block is rendered in Lexical #### Customizing the way your block is rendered in Lexical
@@ -165,7 +166,7 @@ The `blockType` is saved as the slug of the block that has been selected.
**`blockName`** **`blockName`**
The Admin Panel provides each block with a `blockName` field which optionally allows editors to label their blocks for better editability and readability. The Admin Panel provides each block with a `blockName` field which optionally allows editors to label their blocks for better editability and readability. This can be visually hidden via `admin.disableBlockName`.
## Example ## Example

View File

@@ -126,6 +126,15 @@ export const createClientBlocks = ({
clientBlock.jsx = jsxResolved clientBlock.jsx = jsxResolved
} }
if (block?.admin?.disableBlockName) {
// Check for existing admin object, this way we don't have to spread it in
if (clientBlock.admin) {
clientBlock.admin.disableBlockName = block.admin.disableBlockName
} else {
clientBlock.admin = { disableBlockName: block.admin.disableBlockName }
}
}
if (block.labels) { if (block.labels) {
clientBlock.labels = {} as unknown as LabelsClient clientBlock.labels = {} as unknown as LabelsClient

View File

@@ -362,4 +362,71 @@ describe('sanitizeFields', () => {
expect(sanitizedFields).toStrictEqual([]) expect(sanitizedFields).toStrictEqual([])
}) })
}) })
describe('blocks', () => {
it('should maintain admin.blockName true after sanitization', async () => {
const fields: Field[] = [
{
name: 'noLabelBlock',
type: 'blocks',
blocks: [
{
slug: 'number',
admin: {
disableBlockName: true,
},
fields: [
{
name: 'testNumber',
type: 'number',
},
],
},
],
label: false,
},
]
const sanitizedField = (
await sanitizeFields({
config,
fields,
validRelationships: [],
})
)[0] as BlocksField
const sanitizedBlock = sanitizedField.blocks[0]
expect(sanitizedBlock.admin?.disableBlockName).toStrictEqual(true)
})
it('should default admin.disableBlockName to true after sanitization', async () => {
const fields: Field[] = [
{
name: 'noLabelBlock',
type: 'blocks',
blocks: [
{
slug: 'number',
fields: [
{
name: 'testNumber',
type: 'number',
},
],
},
],
label: false,
},
]
const sanitizedField = (
await sanitizeFields({
config,
fields,
validRelationships: [],
})
)[0] as BlocksField
const sanitizedBlock = sanitizedField.blocks[0]
expect(sanitizedBlock.admin?.disableBlockName).toStrictEqual(undefined)
})
})
}) })

View File

@@ -1378,6 +1378,12 @@ export type Block = {
} }
/** Extension point to add your custom data. Available in server and client. */ /** Extension point to add your custom data. Available in server and client. */
custom?: Record<string, any> custom?: Record<string, any>
/**
* Hides the block name field from the Block's header
*
* @default false
*/
disableBlockName?: boolean
group?: Record<string, string> | string group?: Record<string, string> | string
jsx?: PayloadComponent jsx?: PayloadComponent
} }
@@ -1407,7 +1413,7 @@ export type Block = {
} }
export type ClientBlock = { export type ClientBlock = {
admin?: Pick<Block['admin'], 'custom' | 'group'> admin?: Pick<Block['admin'], 'custom' | 'disableBlockName' | 'group'>
fields: ClientField[] fields: ClientField[]
labels?: LabelsClient labels?: LabelsClient
} & Pick<Block, 'imageAltText' | 'imageURL' | 'jsx' | 'slug'> } & Pick<Block, 'imageAltText' | 'imageURL' | 'jsx' | 'slug'>

View File

@@ -80,6 +80,8 @@ export const BlockRow: React.FC<BlocksFieldProps> = ({
const fieldHasErrors = hasSubmitted && errorCount > 0 const fieldHasErrors = hasSubmitted && errorCount > 0
const showBlockName = !block.admin?.disableBlockName
const classNames = [ const classNames = [
`${baseClass}__row`, `${baseClass}__row`,
fieldHasErrors ? `${baseClass}__row--has-errors` : `${baseClass}__row--no-errors`, fieldHasErrors ? `${baseClass}__row--has-errors` : `${baseClass}__row--no-errors`,
@@ -155,7 +157,7 @@ export const BlockRow: React.FC<BlocksFieldProps> = ({
> >
{getTranslation(block.labels.singular, i18n)} {getTranslation(block.labels.singular, i18n)}
</Pill> </Pill>
<SectionTitle path={`${path}.blockName`} readOnly={readOnly} /> {showBlockName && <SectionTitle path={`${path}.blockName`} readOnly={readOnly} />}
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />} {fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
</Fragment> </Fragment>
)} )}

View File

@@ -16,7 +16,7 @@ import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js' import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from '../../../helpers/reInitializeDB.js' import { reInitializeDB } from '../../../helpers/reInitializeDB.js'
import { RESTClient } from '../../../helpers/rest.js' import { RESTClient } from '../../../helpers/rest.js'
import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
const currentFolder = path.dirname(filename) const currentFolder = path.dirname(filename)
@@ -82,7 +82,7 @@ describe('Block fields', () => {
const addedRow = page.locator('#field-blocks .blocks-field__row').last() const addedRow = page.locator('#field-blocks .blocks-field__row').last()
await expect(addedRow).toBeVisible() await expect(addedRow).toBeVisible()
await expect(addedRow.locator('.blocks-field__block-header')).toHaveText( await expect(addedRow.locator('.blocks-field__block-header')).toHaveText(
'Custom Block Label: Content 04', 'Custom Block Label: Content 05',
) )
}) })
@@ -156,7 +156,7 @@ describe('Block fields', () => {
await duplicateButton.click() await duplicateButton.click()
const blocks = page.locator('#field-blocks > .blocks-field__rows > div') const blocks = page.locator('#field-blocks > .blocks-field__rows > div')
expect(await blocks.count()).toEqual(4) expect(await blocks.count()).toEqual(5)
}) })
test('should save when duplicating subblocks', async () => { test('should save when duplicating subblocks', async () => {
@@ -171,7 +171,7 @@ describe('Block fields', () => {
await duplicateButton.click() await duplicateButton.click()
const blocks = page.locator('#field-blocks > .blocks-field__rows > div') const blocks = page.locator('#field-blocks > .blocks-field__rows > div')
expect(await blocks.count()).toEqual(4) expect(await blocks.count()).toEqual(5)
await page.click('#action-save') await page.click('#action-save')
await expect(page.locator('.payload-toast-container')).toContainText('successfully') await expect(page.locator('.payload-toast-container')).toContainText('successfully')
@@ -379,6 +379,33 @@ describe('Block fields', () => {
}) })
}) })
describe('blockNames', () => {
test('should show blockName field', async () => {
await page.goto(url.create)
const blockWithBlockname = page.locator('#field-blocks .blocks-field__rows #blocks-row-1')
const blocknameField = blockWithBlockname.locator('.section-title')
await expect(async () => await expect(blocknameField).toBeVisible()).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
await expect(blocknameField).toHaveAttribute('data-value', 'Second block')
})
test("should not show blockName field when it's disabled", async () => {
await page.goto(url.create)
const blockWithBlockname = page.locator('#field-blocks .blocks-field__rows #blocks-row-3')
await expect(
async () => await expect(blockWithBlockname.locator('.section-title')).toBeHidden(),
).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
})
})
describe('block groups', () => { describe('block groups', () => {
test('should render group labels', async () => { test('should render group labels', async () => {
await page.goto(url.create) await page.goto(url.create)

View File

@@ -30,6 +30,19 @@ export const getBlocksField = (prefix?: string): BlocksField => ({
}, },
], ],
}, },
{
slug: prefix ? `${prefix}NoBlockname` : 'noBlockname',
interfaceName: prefix ? `${prefix}NoBlockname` : 'NoBlockname',
admin: {
disableBlockName: true,
},
fields: [
{
name: 'text',
type: 'text',
},
],
},
{ {
slug: prefix ? `${prefix}Number` : 'number', slug: prefix ? `${prefix}Number` : 'number',
interfaceName: prefix ? `${prefix}NumberBlock` : 'NumberBlock', interfaceName: prefix ? `${prefix}NumberBlock` : 'NumberBlock',

View File

@@ -32,6 +32,10 @@ export const getBlocksFieldSeedData = (prefix?: string): any => [
}, },
], ],
}, },
{
blockType: prefix ? `${prefix}NoBlockname` : 'noBlockname',
text: 'Hello world',
},
] ]
export const blocksDoc: Partial<BlockField> = { export const blocksDoc: Partial<BlockField> = {

View File

@@ -699,16 +699,29 @@ export interface ArrayField {
*/ */
export interface BlockField { export interface BlockField {
id: string; id: string;
blocks: (ContentBlock | NumberBlock | SubBlocksBlock | TabsBlock)[]; blocks: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[];
duplicate: (ContentBlock | NumberBlock | SubBlocksBlock | TabsBlock)[]; duplicate: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[];
collapsedByDefaultBlocks: ( collapsedByDefaultBlocks: (
| LocalizedContentBlock | LocalizedContentBlock
| LocalizedNoBlockname
| LocalizedNumberBlock
| LocalizedSubBlocksBlock
| LocalizedTabsBlock
)[];
disableSort: (
| LocalizedContentBlock
| LocalizedNoBlockname
| LocalizedNumberBlock
| LocalizedSubBlocksBlock
| LocalizedTabsBlock
)[];
localizedBlocks: (
| LocalizedContentBlock
| LocalizedNoBlockname
| LocalizedNumberBlock | LocalizedNumberBlock
| LocalizedSubBlocksBlock | LocalizedSubBlocksBlock
| LocalizedTabsBlock | LocalizedTabsBlock
)[]; )[];
disableSort: (LocalizedContentBlock | LocalizedNumberBlock | LocalizedSubBlocksBlock | LocalizedTabsBlock)[];
localizedBlocks: (LocalizedContentBlock | LocalizedNumberBlock | LocalizedSubBlocksBlock | LocalizedTabsBlock)[];
i18nBlocks?: i18nBlocks?:
| { | {
text?: string | null; text?: string | null;
@@ -883,6 +896,16 @@ export interface ContentBlock {
blockName?: string | null; blockName?: string | null;
blockType: 'content'; blockType: 'content';
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "NoBlockname".
*/
export interface NoBlockname {
text: string;
id?: string | null;
blockName?: string | null;
blockType: 'noBlockname';
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "NumberBlock". * via the `definition` "NumberBlock".
@@ -939,6 +962,16 @@ export interface LocalizedContentBlock {
blockName?: string | null; blockName?: string | null;
blockType: 'localizedContent'; blockType: 'localizedContent';
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedNoBlockname".
*/
export interface LocalizedNoBlockname {
text: string;
id?: string | null;
blockName?: string | null;
blockType: 'localizedNoBlockname';
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedNumberBlock". * via the `definition` "localizedNumberBlock".
@@ -1801,7 +1834,7 @@ export interface TabsField {
text: string; text: string;
id?: string | null; id?: string | null;
}[]; }[];
blocks: (ContentBlock | NumberBlock | SubBlocksBlock | TabsBlock)[]; blocks: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[];
group: { group: {
number: number; number: number;
}; };
@@ -2461,6 +2494,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
| T | T
| { | {
content?: T | ContentBlockSelect<T>; content?: T | ContentBlockSelect<T>;
noBlockname?: T | NoBlocknameSelect<T>;
number?: T | NumberBlockSelect<T>; number?: T | NumberBlockSelect<T>;
subBlocks?: T | SubBlocksBlockSelect<T>; subBlocks?: T | SubBlocksBlockSelect<T>;
tabs?: T | TabsBlockSelect<T>; tabs?: T | TabsBlockSelect<T>;
@@ -2469,6 +2503,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
| T | T
| { | {
content?: T | ContentBlockSelect<T>; content?: T | ContentBlockSelect<T>;
noBlockname?: T | NoBlocknameSelect<T>;
number?: T | NumberBlockSelect<T>; number?: T | NumberBlockSelect<T>;
subBlocks?: T | SubBlocksBlockSelect<T>; subBlocks?: T | SubBlocksBlockSelect<T>;
tabs?: T | TabsBlockSelect<T>; tabs?: T | TabsBlockSelect<T>;
@@ -2477,6 +2512,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
| T | T
| { | {
localizedContent?: T | LocalizedContentBlockSelect<T>; localizedContent?: T | LocalizedContentBlockSelect<T>;
localizedNoBlockname?: T | LocalizedNoBlocknameSelect<T>;
localizedNumber?: T | LocalizedNumberBlockSelect<T>; localizedNumber?: T | LocalizedNumberBlockSelect<T>;
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>; localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
localizedTabs?: T | LocalizedTabsBlockSelect<T>; localizedTabs?: T | LocalizedTabsBlockSelect<T>;
@@ -2485,6 +2521,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
| T | T
| { | {
localizedContent?: T | LocalizedContentBlockSelect<T>; localizedContent?: T | LocalizedContentBlockSelect<T>;
localizedNoBlockname?: T | LocalizedNoBlocknameSelect<T>;
localizedNumber?: T | LocalizedNumberBlockSelect<T>; localizedNumber?: T | LocalizedNumberBlockSelect<T>;
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>; localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
localizedTabs?: T | LocalizedTabsBlockSelect<T>; localizedTabs?: T | LocalizedTabsBlockSelect<T>;
@@ -2493,6 +2530,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
| T | T
| { | {
localizedContent?: T | LocalizedContentBlockSelect<T>; localizedContent?: T | LocalizedContentBlockSelect<T>;
localizedNoBlockname?: T | LocalizedNoBlocknameSelect<T>;
localizedNumber?: T | LocalizedNumberBlockSelect<T>; localizedNumber?: T | LocalizedNumberBlockSelect<T>;
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>; localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
localizedTabs?: T | LocalizedTabsBlockSelect<T>; localizedTabs?: T | LocalizedTabsBlockSelect<T>;
@@ -2690,6 +2728,15 @@ export interface ContentBlockSelect<T extends boolean = true> {
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "NoBlockname_select".
*/
export interface NoBlocknameSelect<T extends boolean = true> {
text?: T;
id?: T;
blockName?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "NumberBlock_select". * via the `definition` "NumberBlock_select".
@@ -2739,6 +2786,15 @@ export interface LocalizedContentBlockSelect<T extends boolean = true> {
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedNoBlockname_select".
*/
export interface LocalizedNoBlocknameSelect<T extends boolean = true> {
text?: T;
id?: T;
blockName?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedNumberBlock_select". * via the `definition` "localizedNumberBlock_select".
@@ -3404,6 +3460,7 @@ export interface TabsFieldsSelect<T extends boolean = true> {
| T | T
| { | {
content?: T | ContentBlockSelect<T>; content?: T | ContentBlockSelect<T>;
noBlockname?: T | NoBlocknameSelect<T>;
number?: T | NumberBlockSelect<T>; number?: T | NumberBlockSelect<T>;
subBlocks?: T | SubBlocksBlockSelect<T>; subBlocks?: T | SubBlocksBlockSelect<T>;
tabs?: T | TabsBlockSelect<T>; tabs?: T | TabsBlockSelect<T>;