Skip to content

Commit 9f7d8c6

Browse files
authored
fix(ui): nested fields with admin.disableListColumn still appear as columns in list view (#13504)
### What? This PR makes `filterFields` recurse into **fields with subfields** (e.g., tabs, row, group, collapsible, array) so nested fields with `admin.disableListColumn: true` (or hidden/disabled fields) are properly excluded. ### Why? Nested fields with `admin.disableListColumn: true` were still appearing in the list view. Example: a text field inside a `row` or `group` continued to show as a column despite being marked `disableListColumn`. ### How? - Call `filterFields` recursively for `tab.fields` and for any field exposing a `fields` array. Fixes #13496
1 parent 30ea8e1 commit 9f7d8c6

File tree

4 files changed

+82
-18
lines changed

4 files changed

+82
-18
lines changed

packages/ui/src/providers/TableColumns/buildColumnState/filterFields.tsx

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,44 @@ import type { ClientField, Field } from 'payload'
33
import { fieldIsHiddenOrDisabled, fieldIsID } from 'payload/shared'
44

55
/**
6-
* Filters fields that are hidden, disabled, or have `disableListColumn` set to `true`
7-
* Does so recursively for `tabs` fields.
6+
* Filters fields that are hidden, disabled, or have `disableListColumn` set to `true`.
7+
* Recurses through `tabs` and any container with `.fields` (e.g., `row`, `group`, `collapsible`).
88
*/
99
export const filterFields = <T extends ClientField | Field>(incomingFields: T[]): T[] => {
1010
const shouldSkipField = (field: T): boolean =>
1111
(field.type !== 'ui' && fieldIsHiddenOrDisabled(field) && !fieldIsID(field)) ||
1212
field?.admin?.disableListColumn === true
1313

14-
const fields: T[] = incomingFields?.reduce((acc, field) => {
14+
return (incomingFields ?? []).reduce<T[]>((acc, field) => {
1515
if (shouldSkipField(field)) {
1616
return acc
1717
}
1818

19-
// extract top-level `tabs` fields and filter out the same
20-
const formattedField: T =
21-
field.type === 'tabs' && 'tabs' in field
22-
? {
23-
...field,
24-
tabs: field.tabs.map((tab) => ({
25-
...tab,
26-
fields: tab.fields.filter((tabField) => !shouldSkipField(tabField)),
27-
})),
28-
}
29-
: field
19+
// handle tabs
20+
if (field.type === 'tabs' && 'tabs' in field) {
21+
const formattedField: T = {
22+
...field,
23+
tabs: field.tabs.map((tab) => ({
24+
...tab,
25+
fields: filterFields(tab.fields as T[]),
26+
})),
27+
}
28+
acc.push(formattedField)
29+
return acc
30+
}
3031

31-
acc.push(formattedField)
32+
// handle fields with subfields (row, group, collapsible, etc.)
33+
if ('fields' in field && Array.isArray(field.fields)) {
34+
const formattedField: T = {
35+
...field,
36+
fields: filterFields(field.fields as T[]),
37+
}
38+
acc.push(formattedField)
39+
return acc
40+
}
3241

42+
// leaf
43+
acc.push(field)
3344
return acc
3445
}, [])
35-
36-
return fields
3746
}

test/admin/collections/Posts.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ import { postsCollectionSlug, uploadCollectionSlug } from '../slugs.js'
88
export const Posts: CollectionConfig = {
99
slug: postsCollectionSlug,
1010
admin: {
11-
defaultColumns: ['id', 'number', 'title', 'description', 'demoUIField'],
11+
defaultColumns: [
12+
'id',
13+
'number',
14+
'title',
15+
'description',
16+
'demoUIField',
17+
'disableListColumnTextInRow',
18+
'someGroup.disableListColumnTextInGroup',
19+
],
1220
description: 'This is a custom collection description.',
1321
group: 'One',
1422
listSearchableFields: ['id', 'title', 'description', 'number'],
@@ -300,6 +308,31 @@ export const Posts: CollectionConfig = {
300308
read: () => false,
301309
},
302310
},
311+
{
312+
type: 'row',
313+
fields: [
314+
{
315+
name: 'disableListColumnTextInRow',
316+
type: 'text',
317+
admin: {
318+
disableListColumn: true,
319+
},
320+
},
321+
],
322+
},
323+
{
324+
name: 'someGroup',
325+
type: 'group',
326+
fields: [
327+
{
328+
name: 'disableListColumnTextInGroup',
329+
type: 'text',
330+
admin: {
331+
disableListColumn: true,
332+
},
333+
},
334+
],
335+
},
303336
],
304337
labels: {
305338
plural: slugPluralLabel,

test/admin/e2e/list-view/e2e.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,18 @@ describe('List View', () => {
915915
).toBeVisible()
916916
})
917917

918+
test('should hide nested field in row in list table columns when admin.disableListColumn is true', async () => {
919+
await page.goto(postsUrl.list)
920+
await expect(page.locator('.table #heading-disableListColumnTextInRow')).toBeHidden()
921+
})
922+
923+
test('should hide nested field in group in list table column when admin.disableListColumn is true', async () => {
924+
await page.goto(postsUrl.list)
925+
await expect(
926+
page.locator('.table #heading-someGroup__disableListColumnTextInGroup'),
927+
).toBeHidden()
928+
})
929+
918930
test('should toggle columns and effect table', async () => {
919931
const tableHeaders = 'table > thead > tr > th'
920932

test/admin/payload-types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ export interface Post {
275275
selectField?: ('option1' | 'option2')[] | null;
276276
file?: string | null;
277277
noReadAccessField?: string | null;
278+
disableListColumnTextInRow?: string | null;
279+
someGroup?: {
280+
disableListColumnTextInGroup?: string | null;
281+
};
278282
updatedAt: string;
279283
createdAt: string;
280284
_status?: ('draft' | 'published') | null;
@@ -824,6 +828,12 @@ export interface PostsSelect<T extends boolean = true> {
824828
selectField?: T;
825829
file?: T;
826830
noReadAccessField?: T;
831+
disableListColumnTextInRow?: T;
832+
someGroup?:
833+
| T
834+
| {
835+
disableListColumnTextInGroup?: T;
836+
};
827837
updatedAt?: T;
828838
createdAt?: T;
829839
_status?: T;

0 commit comments

Comments
 (0)