From cd546b312595bda8a911c0188fbf98b05331be27 Mon Sep 17 00:00:00 2001 From: Said Akhrarov <36972061+akhrarovsaid@users.noreply.github.com> Date: Fri, 3 Oct 2025 06:10:10 -0400 Subject: [PATCH] feat(ui): add support for disabling join field row types (#12738) ### What? This PR adds a new `admin.disableRowTypes` config to `'join'` fields which hides the `"Type"` column from the relationship table. ### Why? While the collection type column _can be_ helpful for providing information, it's not always necessary and can sometimes be redundant when the field only has a singular relationTo. Hiding it can be helpful by removing visual noise and providing editors the data directly. ### How? By threading `admin.disableRowTypes` directly to the `getTableState` function of the `RelationshipTable` component. **With row types** (via `admin.disableRowTypes: false | undefined` OR default for polymorphic): ![image](https://github.com/user-attachments/assets/22b55477-cf56-4b0e-a845-e6f2b39efe3b) **Without row types** (default for monomorphic): ![image](https://github.com/user-attachments/assets/3a2bb0ba-2d5e-4299-8689-249b2d3fefe2) --- docs/fields/join.mdx | 11 +++-- packages/payload/src/fields/config/types.ts | 8 +++- .../src/elements/RelationshipTable/index.tsx | 8 +++- test/joins/collections/Categories.ts | 26 ++++++++++ test/joins/collections/Uploads.ts | 3 ++ test/joins/config.ts | 1 - test/joins/e2e.spec.ts | 48 ++++++++++++++++++- 7 files changed, 94 insertions(+), 11 deletions(-) diff --git a/docs/fields/join.mdx b/docs/fields/join.mdx index 67c753183..56cef9619 100644 --- a/docs/fields/join.mdx +++ b/docs/fields/join.mdx @@ -157,11 +157,12 @@ _\* An asterisk denotes that a property is required._ You can control the user experience of the join field using the `admin` config properties. The following options are supported: -| Option | Description | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| **`defaultColumns`** | Array of field names that correspond to which columns to show in the relationship table. Default is the collection config. | -| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. | -| **`components.Label`** | Override the default Label of the Field Component. [More details](./overview#label) | +| Option | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`defaultColumns`** | Array of field names that correspond to which columns to show in the relationship table. Default is the collection config. | +| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. | +| **`components.Label`** | Override the default Label of the Field Component. [More details](./overview#label) | +| **`disableRowTypes`** | Set to `false` to render row types, and `true` to hide them. Defaults to `false` for join fields with a singular `relationTo`, and `true` for join fields where `relationTo` is an array. | ## Join Field Data diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index b99f17b9c..38855e637 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -1628,6 +1628,7 @@ export type JoinField = { } & Admin['components'] defaultColumns?: string[] disableBulkEdit?: never + disableRowTypes?: boolean readOnly?: never } & Admin /** @@ -1679,8 +1680,11 @@ export type JoinField = { export type JoinFieldClient = { admin?: AdminClient & - // @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve - Pick + Pick< + JoinField['admin'], + // @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve + 'allowCreate' | 'defaultColumns' | 'disableBulkEdit' | 'disableRowTypes' | 'readOnly' + > } & { targetField: Pick } & FieldBaseClient & Pick< JoinField, diff --git a/packages/ui/src/elements/RelationshipTable/index.tsx b/packages/ui/src/elements/RelationshipTable/index.tsx index fbfc9d1b9..b08f6ca97 100644 --- a/packages/ui/src/elements/RelationshipTable/index.tsx +++ b/packages/ui/src/elements/RelationshipTable/index.tsx @@ -144,6 +144,11 @@ export const RelationshipTable: React.FC = (pro })) : undefined + const renderRowTypes = + typeof field.admin.disableRowTypes === 'boolean' + ? !field.admin.disableRowTypes + : Array.isArray(relationTo) + const { data: newData, state: newColumnState, @@ -159,7 +164,7 @@ export const RelationshipTable: React.FC = (pro : `_${field.collection}_${field.name}_order`, parent, query: newQuery, - renderRowTypes: true, + renderRowTypes, tableAppearance: 'condensed', }) @@ -172,6 +177,7 @@ export const RelationshipTable: React.FC = (pro field.defaultLimit, field.defaultSort, field.admin.defaultColumns, + field.admin.disableRowTypes, field.collection, field.name, field.orderable, diff --git a/test/joins/collections/Categories.ts b/test/joins/collections/Categories.ts index c70dd42b4..fd56cc431 100644 --- a/test/joins/collections/Categories.ts +++ b/test/joins/collections/Categories.ts @@ -4,6 +4,7 @@ import { ValidationError } from 'payload' import { categoriesSlug, hiddenPostsSlug, postsSlug } from '../shared.js' import { singularSlug } from './Singular.js' +import { versionsSlug } from './Versions.js' export const Categories: CollectionConfig = { slug: categoriesSlug, @@ -55,6 +56,7 @@ export const Categories: CollectionConfig = { beforeInput: ['/components/BeforeInput.js#BeforeInput'], Description: '/components/CustomDescription/index.js#FieldDescriptionComponent', }, + disableRowTypes: false, }, collection: postsSlug, defaultSort: '-title', @@ -62,6 +64,14 @@ export const Categories: CollectionConfig = { on: 'category', maxDepth: 1, }, + { + name: 'noRowTypes', + type: 'join', + collection: postsSlug, + defaultLimit: 5, + on: 'category', + maxDepth: 1, + }, { name: 'hasManyPosts', type: 'join', @@ -95,6 +105,7 @@ export const Categories: CollectionConfig = { on: 'group.category', admin: { defaultColumns: ['id', 'createdAt', 'title'], + disableRowTypes: false, }, }, { @@ -129,6 +140,21 @@ export const Categories: CollectionConfig = { collection: 'posts', on: 'blocks.category', }, + { + name: 'polymorphicJoin', + type: 'join', + collection: [postsSlug, versionsSlug], + on: 'category', + }, + { + name: 'polymorphicJoinNoRowTypes', + type: 'join', + collection: [postsSlug, versionsSlug], + on: 'category', + admin: { + disableRowTypes: true, + }, + }, { name: 'polymorphic', type: 'join', diff --git a/test/joins/collections/Uploads.ts b/test/joins/collections/Uploads.ts index 0262fc0d1..3c6a4e585 100644 --- a/test/joins/collections/Uploads.ts +++ b/test/joins/collections/Uploads.ts @@ -15,6 +15,9 @@ export const Uploads: CollectionConfig = { type: 'join', collection: 'posts', on: 'upload', + admin: { + disableRowTypes: false, + }, }, ], upload: { diff --git a/test/joins/config.ts b/test/joins/config.ts index e84ac129b..f634c5c3c 100644 --- a/test/joins/config.ts +++ b/test/joins/config.ts @@ -277,7 +277,6 @@ export default buildConfigWithDefaults({ }, ], }, - { slug: 'folders', fields: [ diff --git a/test/joins/e2e.spec.ts b/test/joins/e2e.spec.ts index ac3905d6a..145a76a0e 100644 --- a/test/joins/e2e.spec.ts +++ b/test/joins/e2e.spec.ts @@ -219,7 +219,7 @@ describe('Join Field', () => { await saveDocAndAssert(page) }) - test('should render collection type in first column of relationship table', async () => { + test('should render collection type in first column of relationship table when disableRowTypes false', async () => { await page.goto(categoriesURL.edit(categoryID)) const joinField = page.locator('#field-relatedPosts.field-type.join') await expect(joinField).toBeVisible() @@ -237,7 +237,51 @@ describe('Join Field', () => { } }) - test('should render drawer toggler without document link in second column of relationship table', async () => { + test('should hide collection type column of monomorphic relationship table by default', async () => { + await page.goto(categoriesURL.edit(categoryID)) + const joinField = page.locator('#field-noRowTypes.field-type.join') + const tableHeaderRow = joinField.locator('.table thead > tr') + const firstColumnHeader = tableHeaderRow.locator('th').first() + await expect(firstColumnHeader).toHaveId('heading-title') + }) + + test('should render collection type in first column of polymorphic relationship table by default', async () => { + await page.goto(categoriesURL.edit(categoryID)) + const joinField = page.locator('#field-polymorphicJoin.field-type.join') + await expect(joinField).toBeVisible() + const text = joinField.locator('thead tr th#heading-collection:first-child') + await expect(text).toHaveText('Type') + const cells = joinField.locator('.relationship-table tbody tr td:first-child .pill__label') + + const count = await cells.count() + + for (let i = 0; i < count; i++) { + const element = cells.nth(i) + // Perform actions on each element + await expect(element).toBeVisible() + await expect(element).toHaveText('Post') + } + }) + + test('should not render collection type in polymorphic relationship table with disableRowTypes true', async () => { + await page.goto(categoriesURL.edit(categoryID)) + const joinField = page.locator('#field-polymorphicJoinNoRowTypes.field-type.join') + await expect(joinField).toBeVisible() + const text = joinField.locator('thead tr th#heading-title:first-child') + await expect(text).toHaveText('Title') + const cells = joinField.locator('.relationship-table tbody tr td:first-child .pill__label') + + const count = await cells.count() + + for (let i = 0; i < count; i++) { + const element = cells.nth(i) + // Perform actions on each element + await expect(element).toBeVisible() + await expect(element).toHaveText(/Test Post \d+/) + } + }) + + test('should render drawer toggler without document link in second column of relationship table with row types', async () => { await page.goto(categoriesURL.edit(categoryID)) const joinField = page.locator('#field-relatedPosts.field-type.join') await expect(joinField).toBeVisible()