From d5611953a73049ba11d515a49f7f84ab4df8779b Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 29 May 2025 15:03:39 -0700 Subject: [PATCH] fix: allow unnamed group fields to not set a label at all (#12580) Technically you could already set `label: undefined` and it would be supported by group fields but the types didn't reflect this. So now you can create an unnamed group field like this: ```ts { type: 'group', fields: [ { type: 'text', name: 'insideGroupWithNoLabel', }, ], }, ``` This will remove the label while still visually grouping the fields. ![image](https://github.com/user-attachments/assets/ecb0b364-9cff-4d71-bf9f-86961915aecd) --------- Co-authored-by: Jacob Fletcher --- docs/fields/group.mdx | 5 ++--- packages/payload/src/fields/config/types.ts | 11 ----------- test/fields/collections/Group/e2e.spec.ts | 21 +++++++++++++++++++++ test/fields/collections/Group/index.ts | 9 +++++++++ test/fields/payload-types.ts | 2 ++ 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docs/fields/group.mdx b/docs/fields/group.mdx index 356cdd6acf..29da25afd0 100644 --- a/docs/fields/group.mdx +++ b/docs/fields/group.mdx @@ -37,7 +37,7 @@ export const MyGroupField: Field = { | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`name`** | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`fields`** \* | Array of field types to nest within this Group. | -| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. Required when name is undefined, defaults to name converted to words. | +| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. Defaults to the field name, if defined. | | **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | | **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | @@ -113,8 +113,7 @@ export const ExampleCollection: CollectionConfig = { ## Presentational group fields -You can also use the Group field to create a presentational group of fields. This is useful when you want to group fields together visually without affecting the data structure. -The label will be required when a `name` is not provided. +You can also use the Group field to only visually group fields without affecting the data structure. Not defining a label will render just the grouped fields. ```ts import type { CollectionConfig } from 'payload' diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 800bb71375..c5aaa15886 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -746,17 +746,6 @@ export type NamedGroupField = { export type UnnamedGroupField = { interfaceName?: never - /** - * Can be either: - * - A string, which will be used as the tab's label. - * - An object, where the key is the language code and the value is the label. - */ - label: - | { - [selectedLanguage: string]: string - } - | LabelFunction - | string localized?: never } & Omit diff --git a/test/fields/collections/Group/e2e.spec.ts b/test/fields/collections/Group/e2e.spec.ts index d49fbabf43..8bd4396459 100644 --- a/test/fields/collections/Group/e2e.spec.ts +++ b/test/fields/collections/Group/e2e.spec.ts @@ -119,5 +119,26 @@ describe('Group', () => { const unnamedNestedGroupField = page.locator(unnamedNestedGroupSelector) await expect(unnamedNestedGroupField).toBeVisible() }) + + test('should display with no label when label is undefined', async () => { + await page.goto(url.create) + + // Makes sure the fields are rendered + await page.mouse.wheel(0, 2000) + + const nolabelGroupSelector = `.field-type.group-field#field-_index-14 .group-field__header` + const nolabelGroupField = page.locator(nolabelGroupSelector) + + await expect(nolabelGroupField).toBeHidden() + + // Makes sure the fields are rendered + await page.mouse.wheel(0, 2000) + + // Children should render even if the group has no label + const nolabelGroupChildSelector = `.field-type.group-field#field-_index-14 #field-insideGroupWithNoLabel` + const nolabelGroupChildField = page.locator(nolabelGroupChildSelector) + + await expect(nolabelGroupChildField).toBeVisible() + }) }) }) diff --git a/test/fields/collections/Group/index.ts b/test/fields/collections/Group/index.ts index 1323c92fef..c76a10e55c 100644 --- a/test/fields/collections/Group/index.ts +++ b/test/fields/collections/Group/index.ts @@ -314,6 +314,15 @@ const GroupFields: CollectionConfig = { }, ], }, + { + type: 'group', + fields: [ + { + type: 'text', + name: 'insideGroupWithNoLabel', + }, + ], + }, { type: 'group', label: 'Deeply nested group', diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index 3dfa9f9d85..66f6622451 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -1082,6 +1082,7 @@ export interface GroupField { | null; }; insideUnnamedGroup?: string | null; + insideGroupWithNoLabel?: string | null; deeplyNestedGroup?: { insideNestedUnnamedGroup?: string | null; }; @@ -2685,6 +2686,7 @@ export interface GroupFieldsSelect { email?: T; }; insideUnnamedGroup?: T; + insideGroupWithNoLabel?: T; deeplyNestedGroup?: | T | {