fix: join field description, custom components and loading state (#9703)

- [fix: join field shows loading when creating a
document](9f7a2e7936)
- [fix: join field
descriptions](90e8cdb464)
- [feat(ui): adds before & after inputs to join
field](19d43329ad)

---------

Co-authored-by: Patrik <patrik@payloadcms.com>
This commit is contained in:
Dan Ribbens
2024-12-03 15:58:42 -05:00
committed by GitHub
parent fbb59bab0a
commit 67179a7fb8
9 changed files with 72 additions and 5 deletions

View File

@@ -1372,6 +1372,8 @@ export type JoinField = {
admin?: { admin?: {
allowCreate?: boolean allowCreate?: boolean
components?: { components?: {
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
Error?: CustomComponent<JoinFieldErrorClientComponent | JoinFieldErrorServerComponent> Error?: CustomComponent<JoinFieldErrorClientComponent | JoinFieldErrorServerComponent>
Label?: CustomComponent<JoinFieldLabelClientComponent | JoinFieldLabelServerComponent> Label?: CustomComponent<JoinFieldLabelClientComponent | JoinFieldLabelServerComponent>
} & Admin['components'] } & Admin['components']

View File

@@ -35,7 +35,9 @@ import { RelationshipTablePagination } from './Pagination.js'
const baseClass = 'relationship-table' const baseClass = 'relationship-table'
type RelationshipTableComponentProps = { type RelationshipTableComponentProps = {
readonly AfterInput?: React.ReactNode
readonly allowCreate?: boolean readonly allowCreate?: boolean
readonly BeforeInput?: React.ReactNode
readonly disableTable?: boolean readonly disableTable?: boolean
readonly field: JoinFieldClient readonly field: JoinFieldClient
readonly filterOptions?: Where readonly filterOptions?: Where
@@ -47,7 +49,9 @@ type RelationshipTableComponentProps = {
export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (props) => { export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (props) => {
const { const {
AfterInput,
allowCreate = true, allowCreate = true,
BeforeInput,
disableTable = false, disableTable = false,
filterOptions, filterOptions,
initialData: initialDataFromProps, initialData: initialDataFromProps,
@@ -91,7 +95,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
() => getEntityConfig({ collectionSlug: relationTo }) as ClientCollectionConfig, () => getEntityConfig({ collectionSlug: relationTo }) as ClientCollectionConfig,
) )
const [isLoadingTable, setIsLoadingTable] = useState(true) const [isLoadingTable, setIsLoadingTable] = useState(!disableTable)
const [data, setData] = useState<PaginatedDocs>(initialData) const [data, setData] = useState<PaginatedDocs>(initialData)
const [columnState, setColumnState] = useState<Column[]>() const [columnState, setColumnState] = useState<Column[]>()
@@ -197,6 +201,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
</Pill> </Pill>
</div> </div>
</div> </div>
{BeforeInput}
{isLoadingTable ? ( {isLoadingTable ? (
<p>{t('general:loading')}</p> <p>{t('general:loading')}</p>
) : ( ) : (
@@ -257,6 +262,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
)} )}
</Fragment> </Fragment>
)} )}
{AfterInput}
<DocumentDrawer initialData={initialDrawerData} onSave={onDrawerCreate} /> <DocumentDrawer initialData={initialDrawerData} onSave={onDrawerCreate} />
</div> </div>
) )

View File

@@ -5,9 +5,11 @@ import type { JoinFieldClient, JoinFieldClientComponent, PaginatedDocs, Where }
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { RelationshipTable } from '../../elements/RelationshipTable/index.js' import { RelationshipTable } from '../../elements/RelationshipTable/index.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { FieldDescription } from '../FieldDescription/index.js'
import { FieldLabel } from '../FieldLabel/index.js' import { FieldLabel } from '../FieldLabel/index.js'
import { fieldBaseClass } from '../index.js' import { fieldBaseClass } from '../index.js'
@@ -15,7 +17,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
const { const {
field, field,
field: { field: {
admin: { allowCreate }, admin: { allowCreate, description },
collection, collection,
label, label,
localized, localized,
@@ -27,7 +29,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
const { id: docID } = useDocumentInfo() const { id: docID } = useDocumentInfo()
const { customComponents: { AfterInput, BeforeInput, Label } = {}, value } = const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, value } =
useField<PaginatedDocs>({ useField<PaginatedDocs>({
path, path,
}) })
@@ -57,9 +59,10 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
className={[fieldBaseClass, 'join'].filter(Boolean).join(' ')} className={[fieldBaseClass, 'join'].filter(Boolean).join(' ')}
id={`field-${path?.replace(/\./g, '__')}`} id={`field-${path?.replace(/\./g, '__')}`}
> >
{BeforeInput}
<RelationshipTable <RelationshipTable
AfterInput={AfterInput}
allowCreate={typeof docID !== 'undefined' && allowCreate} allowCreate={typeof docID !== 'undefined' && allowCreate}
BeforeInput={BeforeInput}
disableTable={filterOptions === null} disableTable={filterOptions === null}
field={field as JoinFieldClient} field={field as JoinFieldClient}
filterOptions={filterOptions} filterOptions={filterOptions}
@@ -76,7 +79,10 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
} }
relationTo={collection} relationTo={collection}
/> />
{AfterInput} <RenderCustomComponent
CustomComponent={Description}
Fallback={<FieldDescription description={description} path={path} />}
/>
</div> </div>
) )
} }

View File

@@ -47,6 +47,13 @@ export const Categories: CollectionConfig = {
name: 'relatedPosts', name: 'relatedPosts',
label: 'Related Posts', label: 'Related Posts',
type: 'join', type: 'join',
admin: {
components: {
afterInput: ['/components/AfterInput.js#AfterInput'],
beforeInput: ['/components/BeforeInput.js#BeforeInput'],
Description: '/components/CustomDescription/index.js#FieldDescriptionComponent',
},
},
collection: postsSlug, collection: postsSlug,
defaultSort: '-title', defaultSort: '-title',
defaultLimit: 5, defaultLimit: 5,
@@ -57,6 +64,9 @@ export const Categories: CollectionConfig = {
name: 'hasManyPosts', name: 'hasManyPosts',
type: 'join', type: 'join',
collection: postsSlug, collection: postsSlug,
admin: {
description: 'Static Description',
},
on: 'categories', on: 'categories',
}, },
{ {

View File

@@ -0,0 +1,7 @@
'use client'
import React from 'react'
export const AfterInput: React.FC = () => {
return <div className="after-input">#after-input</div>
}

View File

@@ -0,0 +1,7 @@
'use client'
import React from 'react'
export const BeforeInput: React.FC = () => {
return <div className="before-input">#before-input</div>
}

View File

@@ -0,0 +1,8 @@
'use client'
import type { FieldDescriptionClientComponent } from 'payload'
import React from 'react'
export const FieldDescriptionComponent: FieldDescriptionClientComponent = ({ path }) => {
return <div className={`field-description-${path}`}>Component description: {path}</div>
}

View File

@@ -22,6 +22,11 @@ const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename) const dirname = path.dirname(filename)
export default buildConfigWithDefaults({ export default buildConfigWithDefaults({
admin: {
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [ collections: [
Posts, Posts,
Categories, Categories,

View File

@@ -88,6 +88,22 @@ test.describe('Admin Panel', () => {
await page.goto(categoriesURL.create) await page.goto(categoriesURL.create)
const nameField = page.locator('#field-name') const nameField = page.locator('#field-name')
await expect(nameField).toBeVisible() await expect(nameField).toBeVisible()
// assert that the join field is visible and is not stuck in a loading state
await expect(page.locator('#field-relatedPosts')).toContainText('No Posts found.')
await expect(page.locator('#field-relatedPosts')).not.toContainText('loading')
// assert that the create new button is not visible
await expect(page.locator('#field-relatedPosts > .relationship-table__add-new')).toBeHidden()
// assert that the admin.description is visible
await expect(page.locator('.field-description-hasManyPosts')).toHaveText('Static Description')
//assert that the admin.components.Description is visible
await expect(page.locator('.field-description-relatedPosts')).toHaveText(
'Component description: relatedPosts',
)
await nameField.fill('test category') await nameField.fill('test category')
await saveDocAndAssert(page) await saveDocAndAssert(page)
}) })