diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index f6a534c5ec..dafdaa858a 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -1372,6 +1372,8 @@ export type JoinField = { admin?: { allowCreate?: boolean components?: { + afterInput?: CustomComponent[] + beforeInput?: CustomComponent[] Error?: CustomComponent Label?: CustomComponent } & Admin['components'] diff --git a/packages/ui/src/elements/RelationshipTable/index.tsx b/packages/ui/src/elements/RelationshipTable/index.tsx index 43705e69f7..d87224df57 100644 --- a/packages/ui/src/elements/RelationshipTable/index.tsx +++ b/packages/ui/src/elements/RelationshipTable/index.tsx @@ -35,7 +35,9 @@ import { RelationshipTablePagination } from './Pagination.js' const baseClass = 'relationship-table' type RelationshipTableComponentProps = { + readonly AfterInput?: React.ReactNode readonly allowCreate?: boolean + readonly BeforeInput?: React.ReactNode readonly disableTable?: boolean readonly field: JoinFieldClient readonly filterOptions?: Where @@ -47,7 +49,9 @@ type RelationshipTableComponentProps = { export const RelationshipTable: React.FC = (props) => { const { + AfterInput, allowCreate = true, + BeforeInput, disableTable = false, filterOptions, initialData: initialDataFromProps, @@ -91,7 +95,7 @@ export const RelationshipTable: React.FC = (pro () => getEntityConfig({ collectionSlug: relationTo }) as ClientCollectionConfig, ) - const [isLoadingTable, setIsLoadingTable] = useState(true) + const [isLoadingTable, setIsLoadingTable] = useState(!disableTable) const [data, setData] = useState(initialData) const [columnState, setColumnState] = useState() @@ -197,6 +201,7 @@ export const RelationshipTable: React.FC = (pro + {BeforeInput} {isLoadingTable ? (

{t('general:loading')}

) : ( @@ -257,6 +262,7 @@ export const RelationshipTable: React.FC = (pro )} )} + {AfterInput} ) diff --git a/packages/ui/src/fields/Join/index.tsx b/packages/ui/src/fields/Join/index.tsx index 3904d76356..261e3e0700 100644 --- a/packages/ui/src/fields/Join/index.tsx +++ b/packages/ui/src/fields/Join/index.tsx @@ -5,9 +5,11 @@ import type { JoinFieldClient, JoinFieldClientComponent, PaginatedDocs, Where } import React, { useMemo } from 'react' import { RelationshipTable } from '../../elements/RelationshipTable/index.js' +import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { useField } from '../../forms/useField/index.js' import { withCondition } from '../../forms/withCondition/index.js' import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' +import { FieldDescription } from '../FieldDescription/index.js' import { FieldLabel } from '../FieldLabel/index.js' import { fieldBaseClass } from '../index.js' @@ -15,7 +17,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => { const { field, field: { - admin: { allowCreate }, + admin: { allowCreate, description }, collection, label, localized, @@ -27,7 +29,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => { const { id: docID } = useDocumentInfo() - const { customComponents: { AfterInput, BeforeInput, Label } = {}, value } = + const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, value } = useField({ path, }) @@ -57,9 +59,10 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => { className={[fieldBaseClass, 'join'].filter(Boolean).join(' ')} id={`field-${path?.replace(/\./g, '__')}`} > - {BeforeInput} { } relationTo={collection} /> - {AfterInput} + } + /> ) } diff --git a/test/joins/collections/Categories.ts b/test/joins/collections/Categories.ts index 0803197cb1..8b3a896ba6 100644 --- a/test/joins/collections/Categories.ts +++ b/test/joins/collections/Categories.ts @@ -47,6 +47,13 @@ export const Categories: CollectionConfig = { name: 'relatedPosts', label: 'Related Posts', type: 'join', + admin: { + components: { + afterInput: ['/components/AfterInput.js#AfterInput'], + beforeInput: ['/components/BeforeInput.js#BeforeInput'], + Description: '/components/CustomDescription/index.js#FieldDescriptionComponent', + }, + }, collection: postsSlug, defaultSort: '-title', defaultLimit: 5, @@ -57,6 +64,9 @@ export const Categories: CollectionConfig = { name: 'hasManyPosts', type: 'join', collection: postsSlug, + admin: { + description: 'Static Description', + }, on: 'categories', }, { diff --git a/test/joins/components/AfterInput.tsx b/test/joins/components/AfterInput.tsx new file mode 100644 index 0000000000..cdd4fa3dd9 --- /dev/null +++ b/test/joins/components/AfterInput.tsx @@ -0,0 +1,7 @@ +'use client' + +import React from 'react' + +export const AfterInput: React.FC = () => { + return
#after-input
+} diff --git a/test/joins/components/BeforeInput.tsx b/test/joins/components/BeforeInput.tsx new file mode 100644 index 0000000000..244a6ed364 --- /dev/null +++ b/test/joins/components/BeforeInput.tsx @@ -0,0 +1,7 @@ +'use client' + +import React from 'react' + +export const BeforeInput: React.FC = () => { + return
#before-input
+} diff --git a/test/joins/components/CustomDescription/index.tsx b/test/joins/components/CustomDescription/index.tsx new file mode 100644 index 0000000000..49a6c680b4 --- /dev/null +++ b/test/joins/components/CustomDescription/index.tsx @@ -0,0 +1,8 @@ +'use client' +import type { FieldDescriptionClientComponent } from 'payload' + +import React from 'react' + +export const FieldDescriptionComponent: FieldDescriptionClientComponent = ({ path }) => { + return
Component description: {path}
+} diff --git a/test/joins/config.ts b/test/joins/config.ts index 94b700f962..e55ec49f0f 100644 --- a/test/joins/config.ts +++ b/test/joins/config.ts @@ -22,6 +22,11 @@ const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) export default buildConfigWithDefaults({ + admin: { + importMap: { + baseDir: path.resolve(dirname), + }, + }, collections: [ Posts, Categories, diff --git a/test/joins/e2e.spec.ts b/test/joins/e2e.spec.ts index ea2bc26711..c643746962 100644 --- a/test/joins/e2e.spec.ts +++ b/test/joins/e2e.spec.ts @@ -88,6 +88,22 @@ test.describe('Admin Panel', () => { await page.goto(categoriesURL.create) const nameField = page.locator('#field-name') 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 saveDocAndAssert(page) })