Skip to content

Commit 67179a7

Browse files
fix: join field description, custom components and loading state (#9703)
- [fix: join field shows loading when creating a document](9f7a2e7) - [fix: join field descriptions](90e8cdb) - [feat(ui): adds before & after inputs to join field](19d4332) --------- Co-authored-by: Patrik <patrik@payloadcms.com>
1 parent fbb59ba commit 67179a7

File tree

9 files changed

+72
-5
lines changed

9 files changed

+72
-5
lines changed

packages/payload/src/fields/config/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,8 @@ export type JoinField = {
13721372
admin?: {
13731373
allowCreate?: boolean
13741374
components?: {
1375+
afterInput?: CustomComponent[]
1376+
beforeInput?: CustomComponent[]
13751377
Error?: CustomComponent<JoinFieldErrorClientComponent | JoinFieldErrorServerComponent>
13761378
Label?: CustomComponent<JoinFieldLabelClientComponent | JoinFieldLabelServerComponent>
13771379
} & Admin['components']

packages/ui/src/elements/RelationshipTable/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ import { RelationshipTablePagination } from './Pagination.js'
3535
const baseClass = 'relationship-table'
3636

3737
type RelationshipTableComponentProps = {
38+
readonly AfterInput?: React.ReactNode
3839
readonly allowCreate?: boolean
40+
readonly BeforeInput?: React.ReactNode
3941
readonly disableTable?: boolean
4042
readonly field: JoinFieldClient
4143
readonly filterOptions?: Where
@@ -47,7 +49,9 @@ type RelationshipTableComponentProps = {
4749

4850
export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (props) => {
4951
const {
52+
AfterInput,
5053
allowCreate = true,
54+
BeforeInput,
5155
disableTable = false,
5256
filterOptions,
5357
initialData: initialDataFromProps,
@@ -91,7 +95,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
9195
() => getEntityConfig({ collectionSlug: relationTo }) as ClientCollectionConfig,
9296
)
9397

94-
const [isLoadingTable, setIsLoadingTable] = useState(true)
98+
const [isLoadingTable, setIsLoadingTable] = useState(!disableTable)
9599
const [data, setData] = useState<PaginatedDocs>(initialData)
96100
const [columnState, setColumnState] = useState<Column[]>()
97101

@@ -197,6 +201,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
197201
</Pill>
198202
</div>
199203
</div>
204+
{BeforeInput}
200205
{isLoadingTable ? (
201206
<p>{t('general:loading')}</p>
202207
) : (
@@ -257,6 +262,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
257262
)}
258263
</Fragment>
259264
)}
265+
{AfterInput}
260266
<DocumentDrawer initialData={initialDrawerData} onSave={onDrawerCreate} />
261267
</div>
262268
)

packages/ui/src/fields/Join/index.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import type { JoinFieldClient, JoinFieldClientComponent, PaginatedDocs, Where }
55
import React, { useMemo } from 'react'
66

77
import { RelationshipTable } from '../../elements/RelationshipTable/index.js'
8+
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
89
import { useField } from '../../forms/useField/index.js'
910
import { withCondition } from '../../forms/withCondition/index.js'
1011
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
12+
import { FieldDescription } from '../FieldDescription/index.js'
1113
import { FieldLabel } from '../FieldLabel/index.js'
1214
import { fieldBaseClass } from '../index.js'
1315

1416
const JoinFieldComponent: JoinFieldClientComponent = (props) => {
1517
const {
1618
field,
1719
field: {
18-
admin: { allowCreate },
20+
admin: { allowCreate, description },
1921
collection,
2022
label,
2123
localized,
@@ -27,7 +29,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
2729

2830
const { id: docID } = useDocumentInfo()
2931

30-
const { customComponents: { AfterInput, BeforeInput, Label } = {}, value } =
32+
const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, value } =
3133
useField<PaginatedDocs>({
3234
path,
3335
})
@@ -57,9 +59,10 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
5759
className={[fieldBaseClass, 'join'].filter(Boolean).join(' ')}
5860
id={`field-${path?.replace(/\./g, '__')}`}
5961
>
60-
{BeforeInput}
6162
<RelationshipTable
63+
AfterInput={AfterInput}
6264
allowCreate={typeof docID !== 'undefined' && allowCreate}
65+
BeforeInput={BeforeInput}
6366
disableTable={filterOptions === null}
6467
field={field as JoinFieldClient}
6568
filterOptions={filterOptions}
@@ -76,7 +79,10 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
7679
}
7780
relationTo={collection}
7881
/>
79-
{AfterInput}
82+
<RenderCustomComponent
83+
CustomComponent={Description}
84+
Fallback={<FieldDescription description={description} path={path} />}
85+
/>
8086
</div>
8187
)
8288
}

test/joins/collections/Categories.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ export const Categories: CollectionConfig = {
4747
name: 'relatedPosts',
4848
label: 'Related Posts',
4949
type: 'join',
50+
admin: {
51+
components: {
52+
afterInput: ['/components/AfterInput.js#AfterInput'],
53+
beforeInput: ['/components/BeforeInput.js#BeforeInput'],
54+
Description: '/components/CustomDescription/index.js#FieldDescriptionComponent',
55+
},
56+
},
5057
collection: postsSlug,
5158
defaultSort: '-title',
5259
defaultLimit: 5,
@@ -57,6 +64,9 @@ export const Categories: CollectionConfig = {
5764
name: 'hasManyPosts',
5865
type: 'join',
5966
collection: postsSlug,
67+
admin: {
68+
description: 'Static Description',
69+
},
6070
on: 'categories',
6171
},
6272
{

test/joins/components/AfterInput.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use client'
2+
3+
import React from 'react'
4+
5+
export const AfterInput: React.FC = () => {
6+
return <div className="after-input">#after-input</div>
7+
}

test/joins/components/BeforeInput.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use client'
2+
3+
import React from 'react'
4+
5+
export const BeforeInput: React.FC = () => {
6+
return <div className="before-input">#before-input</div>
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use client'
2+
import type { FieldDescriptionClientComponent } from 'payload'
3+
4+
import React from 'react'
5+
6+
export const FieldDescriptionComponent: FieldDescriptionClientComponent = ({ path }) => {
7+
return <div className={`field-description-${path}`}>Component description: {path}</div>
8+
}

test/joins/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const filename = fileURLToPath(import.meta.url)
2222
const dirname = path.dirname(filename)
2323

2424
export default buildConfigWithDefaults({
25+
admin: {
26+
importMap: {
27+
baseDir: path.resolve(dirname),
28+
},
29+
},
2530
collections: [
2631
Posts,
2732
Categories,

test/joins/e2e.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ test.describe('Admin Panel', () => {
8888
await page.goto(categoriesURL.create)
8989
const nameField = page.locator('#field-name')
9090
await expect(nameField).toBeVisible()
91+
92+
// assert that the join field is visible and is not stuck in a loading state
93+
await expect(page.locator('#field-relatedPosts')).toContainText('No Posts found.')
94+
await expect(page.locator('#field-relatedPosts')).not.toContainText('loading')
95+
96+
// assert that the create new button is not visible
97+
await expect(page.locator('#field-relatedPosts > .relationship-table__add-new')).toBeHidden()
98+
99+
// assert that the admin.description is visible
100+
await expect(page.locator('.field-description-hasManyPosts')).toHaveText('Static Description')
101+
102+
//assert that the admin.components.Description is visible
103+
await expect(page.locator('.field-description-relatedPosts')).toHaveText(
104+
'Component description: relatedPosts',
105+
)
106+
91107
await nameField.fill('test category')
92108
await saveDocAndAssert(page)
93109
})

0 commit comments

Comments
 (0)