Skip to content

Commit 20f7017

Browse files
authored
feat: show nested fields in named tabs as separate columns in the list view (#12530)
### What Continuation of #7355 by extending the functionality to named tabs. Updates `flattenFields` to hoist nested fields inside named tabs to the top-level field array when `moveSubFieldsToTop` is enabled. Also fixes an issue where group fields with custom cells were being flattened out. Now, group fields with a custom cell components remain available as top-level columns. Fixes #12563
1 parent 0204f0d commit 20f7017

File tree

7 files changed

+526
-111
lines changed

7 files changed

+526
-111
lines changed

packages/payload/src/utilities/flattenTopLevelFields.spec.ts

Lines changed: 221 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ describe('flattenFields', () => {
4949
i18n,
5050
})
5151

52-
expect(result).toHaveLength(1)
53-
expect(result[0].name).toBe('slug')
54-
expect(result[0].accessor).toBe('meta-slug')
55-
expect(result[0].labelWithPrefix).toBe('Meta Info > Slug')
52+
expect(result).toHaveLength(2)
53+
expect(result[1].name).toBe('slug')
54+
expect(result[1].accessor).toBe('meta-slug')
55+
expect(result[1].labelWithPrefix).toBe('Meta Info > Slug')
5656
})
5757

5858
it('should NOT flatten fields inside group without moveSubFieldsToTop', () => {
@@ -109,10 +109,10 @@ describe('flattenFields', () => {
109109
i18n,
110110
})
111111

112-
expect(hoisted).toHaveLength(1)
113-
expect(hoisted[0].name).toBe('deep')
114-
expect(hoisted[0].accessor).toBe('outer-inner-deep')
115-
expect(hoisted[0].labelWithPrefix).toBe('Outer > Inner > Deep Field')
112+
expect(hoisted).toHaveLength(3)
113+
expect(hoisted[2].name).toBe('deep')
114+
expect(hoisted[2].accessor).toBe('outer-inner-deep')
115+
expect(hoisted[2].labelWithPrefix).toBe('Outer > Inner > Deep Field')
116116

117117
const nonHoisted = flattenFields(fields)
118118

@@ -142,14 +142,15 @@ describe('flattenFields', () => {
142142
i18n,
143143
})
144144

145-
// Should keep the group as a single top-level field
146-
expect(withExtract).toHaveLength(1)
147-
expect(withExtract[0].type).toBe('text')
148-
expect(withExtract[0].accessor).toBeUndefined()
149-
expect(withExtract[0].labelWithPrefix).toBeUndefined()
145+
// Should include top level group and its nested field as a top-level field
146+
expect(withExtract).toHaveLength(2)
147+
expect(withExtract[1].type).toBe('text')
148+
expect(withExtract[1].accessor).toBeUndefined()
149+
expect(withExtract[1].labelWithPrefix).toBeUndefined()
150150

151151
const withoutExtract = flattenFields(fields)
152152

153+
// Should return the group as a top-level item, not the inner field
153154
expect(withoutExtract).toHaveLength(1)
154155
expect(withoutExtract[0].type).toBe('group')
155156
expect(withoutExtract[0].accessor).toBeUndefined()
@@ -195,10 +196,10 @@ describe('flattenFields', () => {
195196
i18n,
196197
})
197198

198-
expect(hoistedResult).toHaveLength(1)
199-
expect(hoistedResult[0].name).toBe('nestedField')
200-
expect(hoistedResult[0].accessor).toBe('namedGroup-nestedField')
201-
expect(hoistedResult[0].labelWithPrefix).toBe('Named Group > Nested Field')
199+
expect(hoistedResult).toHaveLength(5)
200+
expect(hoistedResult[4].name).toBe('nestedField')
201+
expect(hoistedResult[4].accessor).toBe('namedGroup-nestedField')
202+
expect(hoistedResult[4].labelWithPrefix).toBe('Named Group > Nested Field')
202203

203204
const nonHoistedResult = flattenFields(fields)
204205

@@ -432,9 +433,9 @@ describe('flattenFields', () => {
432433
i18n,
433434
})
434435

435-
expect(result).toHaveLength(1)
436-
expect(result[0].accessor).toBe('groupInRow-nestedInRowGroup')
437-
expect(result[0].labelWithPrefix).toBe('Group In Row > Nested In Row Group')
436+
expect(result).toHaveLength(2)
437+
expect(result[1].accessor).toBe('groupInRow-nestedInRowGroup')
438+
expect(result[1].labelWithPrefix).toBe('Group In Row > Nested In Row Group')
438439
})
439440

440441
it('should hoist named group fields inside collapsibles', () => {
@@ -464,15 +465,114 @@ describe('flattenFields', () => {
464465
i18n,
465466
})
466467

467-
expect(result).toHaveLength(1)
468-
expect(result[0].accessor).toBe('groupInCollapsible-nestedInCollapsibleGroup')
469-
expect(result[0].labelWithPrefix).toBe('Group In Collapsible > Nested In Collapsible Group')
468+
expect(result).toHaveLength(2)
469+
expect(result[1].accessor).toBe('groupInCollapsible-nestedInCollapsibleGroup')
470+
expect(result[1].labelWithPrefix).toBe('Group In Collapsible > Nested In Collapsible Group')
470471
})
471472
})
472473

473474
describe('tab integration', () => {
474-
it('should hoist named group fields inside tabs when moveSubFieldsToTop is true', () => {
475-
const fields: ClientField[] = [
475+
const namedTabFields: ClientField[] = [
476+
{
477+
type: 'tabs',
478+
tabs: [
479+
{
480+
label: 'Tab One',
481+
name: 'tabOne',
482+
fields: [
483+
{
484+
type: 'array',
485+
name: 'array',
486+
fields: [
487+
{
488+
type: 'text',
489+
name: 'text',
490+
},
491+
],
492+
},
493+
{
494+
type: 'row',
495+
fields: [
496+
{
497+
name: 'arrayInRow',
498+
type: 'array',
499+
fields: [
500+
{
501+
name: 'textInArrayInRow',
502+
type: 'text',
503+
},
504+
],
505+
},
506+
],
507+
},
508+
{
509+
type: 'text',
510+
name: 'textInTab',
511+
label: 'Text In Tab',
512+
},
513+
{
514+
type: 'group',
515+
name: 'groupInTab',
516+
label: 'Group In Tab',
517+
fields: [
518+
{
519+
type: 'text',
520+
name: 'nestedTextInTabGroup',
521+
label: 'Nested Text In Tab Group',
522+
},
523+
],
524+
},
525+
],
526+
},
527+
],
528+
},
529+
]
530+
531+
const unnamedTabFields: ClientField[] = [
532+
{
533+
type: 'tabs',
534+
tabs: [
535+
{
536+
label: 'Tab One',
537+
fields: [
538+
{
539+
type: 'array',
540+
name: 'array',
541+
fields: [
542+
{
543+
type: 'text',
544+
name: 'text',
545+
},
546+
],
547+
},
548+
{
549+
type: 'row',
550+
fields: [
551+
{
552+
name: 'arrayInRow',
553+
type: 'array',
554+
fields: [
555+
{
556+
name: 'textInArrayInRow',
557+
type: 'text',
558+
},
559+
],
560+
},
561+
],
562+
},
563+
{
564+
type: 'text',
565+
name: 'textInTab',
566+
label: 'Text In Tab',
567+
},
568+
],
569+
},
570+
],
571+
},
572+
]
573+
574+
it('should hoist named group fields inside unamed tabs when moveSubFieldsToTop is true', () => {
575+
const unnamedTabWithNamedGroup: ClientField[] = [
476576
{
477577
type: 'tabs',
478578
tabs: [
@@ -497,14 +597,107 @@ describe('flattenFields', () => {
497597
},
498598
]
499599

500-
const result = flattenFields(fields, {
600+
const result = flattenFields(unnamedTabWithNamedGroup, {
501601
moveSubFieldsToTop: true,
502602
i18n,
503603
})
504604

605+
expect(result).toHaveLength(2)
606+
expect(result[1].accessor).toBe('groupInTab-nestedInTabGroup')
607+
expect(result[1].labelWithPrefix).toBe('Group In Tab > Nested In Tab Group')
608+
})
609+
610+
it('should hoist fields inside unnamed groups inside unnamed tabs when moveSubFieldsToTop is true', () => {
611+
const unnamedTabWithUnnamedGroup: ClientField[] = [
612+
{
613+
type: 'tabs',
614+
tabs: [
615+
{
616+
label: 'Tab One',
617+
fields: [
618+
{
619+
type: 'group',
620+
label: 'Unnamed Group In Tab',
621+
fields: [
622+
{
623+
type: 'text',
624+
name: 'nestedInUnnamedGroup',
625+
label: 'Nested In Unnamed Group',
626+
},
627+
],
628+
},
629+
],
630+
},
631+
],
632+
},
633+
]
634+
635+
const defaultResult = flattenFields(unnamedTabWithUnnamedGroup)
636+
637+
expect(defaultResult).toHaveLength(1)
638+
expect(defaultResult[0].type).toBe('group')
639+
expect(defaultResult[0].label).toBe('Unnamed Group In Tab')
640+
expect('accessor' in defaultResult[0]).toBe(false)
641+
expect('labelWithPrefix' in defaultResult[0]).toBe(false)
642+
643+
const hoistedResult = flattenFields(unnamedTabWithUnnamedGroup, {
644+
moveSubFieldsToTop: true,
645+
i18n,
646+
})
647+
648+
expect(hoistedResult).toHaveLength(2)
649+
const hoistedField = hoistedResult[1]
650+
expect(hoistedField.name).toBe('nestedInUnnamedGroup')
651+
expect(hoistedField.accessor).toBeUndefined()
652+
expect(hoistedField.labelWithPrefix).toBeUndefined()
653+
})
654+
655+
it('should properly hoist fields inside named tabs when moveSubFieldsToTop is true', () => {
656+
const result = flattenFields(namedTabFields, {
657+
moveSubFieldsToTop: true,
658+
i18n,
659+
})
660+
661+
expect(result).toHaveLength(5)
662+
expect(result[0].accessor).toBe('tabOne-array')
663+
expect(result[0].labelWithPrefix).toBe('Tab One > array')
664+
expect(result[1].accessor).toBe('tabOne-arrayInRow')
665+
expect(result[1].labelWithPrefix).toBe('Tab One > arrayInRow')
666+
expect(result[2].accessor).toBe('tabOne-textInTab')
667+
expect(result[2].labelWithPrefix).toBe('Tab One > Text In Tab')
668+
expect(result[4].accessor).toBe('tabOne-groupInTab-nestedTextInTabGroup')
669+
expect(result[4].labelWithPrefix).toBe('Tab One > Group In Tab > Nested Text In Tab Group')
670+
})
671+
672+
it('should NOT hoist fields inside named tabs when moveSubFieldsToTop is false', () => {
673+
const result = flattenFields(namedTabFields)
674+
675+
// We expect one top-level field: the tabs container itself is *not* hoisted
505676
expect(result).toHaveLength(1)
506-
expect(result[0].accessor).toBe('groupInTab-nestedInTabGroup')
507-
expect(result[0].labelWithPrefix).toBe('Group In Tab > Nested In Tab Group')
677+
678+
const tabField = result[0]
679+
expect(tabField.type).toBe('tab')
680+
681+
// Confirm nested fields are NOT hoisted: no accessors or labelWithPrefix at the top level
682+
expect('accessor' in tabField).toBe(false)
683+
expect('labelWithPrefix' in tabField).toBe(false)
684+
})
685+
686+
it('should hoist fields inside unnamed tabs regardless of moveSubFieldsToTop', () => {
687+
const resultDefault = flattenFields(unnamedTabFields)
688+
const resultHoisted = flattenFields(unnamedTabFields, {
689+
moveSubFieldsToTop: true,
690+
i18n,
691+
})
692+
693+
expect(resultDefault).toHaveLength(3)
694+
expect(resultHoisted).toHaveLength(3)
695+
expect(resultDefault).toEqual(resultHoisted)
696+
697+
for (const field of resultDefault) {
698+
expect(field.accessor).toBeUndefined()
699+
expect(field.labelWithPrefix).toBeUndefined()
700+
}
508701
})
509702
})
510703
})

0 commit comments

Comments
 (0)