Skip to content

Commit a1822d2

Browse files
authored
fix(ui): properly render create new button in polymorphic joins (#12930)
<!-- Thank you for the PR! Please go through the checklist below and make sure you've completed all the steps. Please review the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository if you haven't already. The following items will ensure that your PR is handled as smoothly as possible: - PR Title must follow conventional commits format. For example, `feat: my new feature`, `fix(plugin-seo): my fix`. - Minimal description explained as if explained to someone not immediately familiar with the code. - Provide before/after screenshots or code diffs if applicable. - Link any related issues/discussions from GitHub or Discord. - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Fixes # --> ### What? This PR fixes an issue where the bottom "Create new ..." button would cause a runtime error due to not accounting for a polymorphic join setup. ### Why? To prevent a runtime error and allow users the ability to add new documents to the join as expected even in a polymorphic setup. ### How? Creation of a new `AddNewButton` which handles all of the add new button instances in the `RelationshipTable` component. Addresses #12913 (comment) Before: [join-polymorphic-runtime-error--Payload.webm](https://github.com/user-attachments/assets/fad3a1ba-c51c-4731-84cc-c27adbaac1d9) After: [polymorphic-after-Editing---Multiple-Collections-Parent---Payload (1).webm](https://github.com/user-attachments/assets/e3baf902-1b2b-4f19-8b6d-838edd6fef80)
1 parent 4b9566f commit a1822d2

File tree

3 files changed

+135
-69
lines changed

3 files changed

+135
-69
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use client'
2+
import type { I18nClient } from '@payloadcms/translations'
3+
import type { ClientCollectionConfig, SanitizedPermissions } from 'payload'
4+
5+
import { getTranslation } from '@payloadcms/translations'
6+
7+
import type { Props as ButtonProps } from '../../elements/Button/types.js'
8+
9+
import { Button } from '../../elements/Button/index.js'
10+
import { Popup, PopupList } from '../Popup/index.js'
11+
12+
export const AddNewButton = ({
13+
allowCreate,
14+
baseClass,
15+
buttonStyle,
16+
className,
17+
collections,
18+
i18n,
19+
icon,
20+
label,
21+
onClick,
22+
permissions,
23+
relationTo,
24+
}: {
25+
allowCreate: boolean
26+
baseClass: string
27+
buttonStyle?: ButtonProps['buttonStyle']
28+
className?: string
29+
collections: ClientCollectionConfig[]
30+
i18n: I18nClient
31+
icon?: ButtonProps['icon']
32+
label: string
33+
onClick: (selectedCollection?: string) => void
34+
permissions: SanitizedPermissions
35+
relationTo: string | string[]
36+
}) => {
37+
if (!allowCreate) {
38+
return null
39+
}
40+
41+
const isPolymorphic = Array.isArray(relationTo)
42+
43+
if (!isPolymorphic) {
44+
return (
45+
<Button buttonStyle={buttonStyle} className={className} onClick={() => onClick()}>
46+
{label}
47+
</Button>
48+
)
49+
}
50+
51+
return (
52+
<div className={`${baseClass}__add-new-polymorphic-wrapper`}>
53+
<Popup
54+
button={
55+
<Button buttonStyle={buttonStyle} className={className} icon={icon}>
56+
{label}
57+
</Button>
58+
}
59+
buttonType="custom"
60+
horizontalAlign="center"
61+
render={({ close: closePopup }) => (
62+
<PopupList.ButtonGroup>
63+
{relationTo.map((relatedCollection) => {
64+
if (permissions.collections[relatedCollection]?.create) {
65+
return (
66+
<PopupList.Button
67+
className={`${baseClass}__relation-button--${relatedCollection}`}
68+
key={relatedCollection}
69+
onClick={() => {
70+
closePopup()
71+
onClick(relatedCollection)
72+
}}
73+
>
74+
{getTranslation(
75+
collections.find((each) => each.slug === relatedCollection).labels.singular,
76+
i18n,
77+
)}
78+
</PopupList.Button>
79+
)
80+
}
81+
82+
return null
83+
})}
84+
</PopupList.ButtonGroup>
85+
)}
86+
size="medium"
87+
/>
88+
</div>
89+
)
90+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
padding-bottom: var(--base);
1919
}
2020

21+
&__add-new-polymorphic-wrapper {
22+
display: inline-flex;
23+
}
24+
2125
&__add-new-polymorphic .btn__label {
2226
display: flex;
2327
text-wrap: nowrap;

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

Lines changed: 41 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ import React, { Fragment, useCallback, useEffect, useState } from 'react'
1313

1414
import type { DocumentDrawerProps } from '../DocumentDrawer/types.js'
1515

16-
import { Button } from '../../elements/Button/index.js'
1716
import { Pill } from '../../elements/Pill/index.js'
1817
import { useEffectEvent } from '../../hooks/useEffectEvent.js'
1918
import { ChevronIcon } from '../../icons/Chevron/index.js'
20-
import { PlusIcon } from '../../icons/Plus/index.js'
2119
import { useAuth } from '../../providers/Auth/index.js'
2220
import { useConfig } from '../../providers/Config/index.js'
2321
import { ListQueryProvider } from '../../providers/ListQuery/index.js'
@@ -27,11 +25,11 @@ import { useTranslation } from '../../providers/Translation/index.js'
2725
import { AnimateHeight } from '../AnimateHeight/index.js'
2826
import { ColumnSelector } from '../ColumnSelector/index.js'
2927
import { useDocumentDrawer } from '../DocumentDrawer/index.js'
30-
import { Popup, PopupList } from '../Popup/index.js'
3128
import { RelationshipProvider } from '../Table/RelationshipProvider/index.js'
29+
import { AddNewButton } from './AddNewButton.js'
3230
import { DrawerLink } from './cells/DrawerLink/index.js'
33-
import { RelationshipTablePagination } from './Pagination.js'
3431
import './index.scss'
32+
import { RelationshipTablePagination } from './Pagination.js'
3533

3634
const baseClass = 'relationship-table'
3735

@@ -102,8 +100,10 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
102100

103101
const [collectionConfig] = useState(() => getEntityConfig({ collectionSlug: relationTo }))
104102

103+
const isPolymorphic = Array.isArray(relationTo)
104+
105105
const [selectedCollection, setSelectedCollection] = useState(
106-
Array.isArray(relationTo) ? undefined : relationTo,
106+
isPolymorphic ? undefined : relationTo,
107107
)
108108
const [isLoadingTable, setIsLoadingTable] = useState(!disableTable)
109109
const [data, setData] = useState<PaginatedDocs>(initialData)
@@ -183,10 +183,9 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
183183
handleTableRender(query, disableTable)
184184
}, [query, disableTable])
185185

186-
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer, isDrawerOpen, openDrawer }] =
187-
useDocumentDrawer({
188-
collectionSlug: selectedCollection,
189-
})
186+
const [DocumentDrawer, , { closeDrawer, isDrawerOpen, openDrawer }] = useDocumentDrawer({
187+
collectionSlug: selectedCollection,
188+
})
190189

191190
const onDrawerSave = useCallback<DocumentDrawerProps['onSave']>(
192191
(args) => {
@@ -225,16 +224,16 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
225224

226225
const canCreate =
227226
allowCreate !== false &&
228-
permissions?.collections?.[Array.isArray(relationTo) ? relationTo[0] : relationTo]?.create
227+
permissions?.collections?.[isPolymorphic ? relationTo[0] : relationTo]?.create
229228

230229
useEffect(() => {
231-
if (Array.isArray(relationTo) && selectedCollection) {
230+
if (isPolymorphic && selectedCollection) {
232231
openDrawer()
233232
}
234-
}, [selectedCollection, openDrawer, relationTo])
233+
}, [selectedCollection, openDrawer, isPolymorphic])
235234

236235
useEffect(() => {
237-
if (Array.isArray(relationTo) && !isDrawerOpen && selectedCollection) {
236+
if (isPolymorphic && !isDrawerOpen && selectedCollection) {
238237
setSelectedCollection(undefined)
239238
}
240239
// eslint-disable-next-line react-compiler/react-compiler -- TODO: fix
@@ -246,53 +245,19 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
246245
<div className={`${baseClass}__header`}>
247246
{Label}
248247
<div className={`${baseClass}__actions`}>
249-
{!Array.isArray(relationTo) && canCreate && (
250-
<DocumentDrawerToggler className={`${baseClass}__add-new`}>
251-
{i18n.t('fields:addNew')}
252-
</DocumentDrawerToggler>
253-
)}
254-
255-
{Array.isArray(relationTo) && (
256-
<Fragment>
257-
<Popup
258-
button={
259-
<Button buttonStyle="none" className={`${baseClass}__add-new-polymorphic`}>
260-
{i18n.t('fields:addNew')}
261-
<PlusIcon />
262-
</Button>
263-
}
264-
buttonType="custom"
265-
horizontalAlign="center"
266-
render={({ close: closePopup }) => (
267-
<PopupList.ButtonGroup>
268-
{relationTo.map((relatedCollection) => {
269-
if (permissions.collections[relatedCollection].create) {
270-
return (
271-
<PopupList.Button
272-
className={`${baseClass}__relation-button--${relatedCollection}`}
273-
key={relatedCollection}
274-
onClick={() => {
275-
closePopup()
276-
setSelectedCollection(relatedCollection)
277-
}}
278-
>
279-
{getTranslation(
280-
config.collections.find((each) => each.slug === relatedCollection)
281-
.labels.singular,
282-
i18n,
283-
)}
284-
</PopupList.Button>
285-
)
286-
}
287-
288-
return null
289-
})}
290-
</PopupList.ButtonGroup>
291-
)}
292-
size="medium"
293-
/>
294-
</Fragment>
295-
)}
248+
<AddNewButton
249+
allowCreate={allowCreate !== false}
250+
baseClass={baseClass}
251+
buttonStyle="none"
252+
className={`${baseClass}__add-new${isPolymorphic ? '-polymorphic' : ' doc-drawer__toggler'}`}
253+
collections={config.collections}
254+
i18n={i18n}
255+
icon={isPolymorphic ? 'plus' : undefined}
256+
label={i18n.t('fields:addNew')}
257+
onClick={isPolymorphic ? setSelectedCollection : openDrawer}
258+
permissions={permissions}
259+
relationTo={relationTo}
260+
/>
296261
<Pill
297262
aria-controls={`${baseClass}-columns`}
298263
aria-expanded={openColumnSelector}
@@ -317,18 +282,25 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
317282
<div className={`${baseClass}__no-results`}>
318283
<p>
319284
{i18n.t('general:noResults', {
320-
label: Array.isArray(relationTo)
285+
label: isPolymorphic
321286
? i18n.t('general:documents')
322287
: getTranslation(collectionConfig?.labels?.plural, i18n),
323288
})}
324289
</p>
325-
{canCreate && (
326-
<Button onClick={openDrawer}>
327-
{i18n.t('general:createNewLabel', {
328-
label: getTranslation(collectionConfig?.labels?.singular, i18n),
329-
})}
330-
</Button>
331-
)}
290+
<AddNewButton
291+
allowCreate={canCreate}
292+
baseClass={baseClass}
293+
collections={config.collections}
294+
i18n={i18n}
295+
label={i18n.t('general:createNewLabel', {
296+
label: isPolymorphic
297+
? i18n.t('general:document')
298+
: getTranslation(collectionConfig?.labels?.singular, i18n),
299+
})}
300+
onClick={isPolymorphic ? setSelectedCollection : openDrawer}
301+
permissions={permissions}
302+
relationTo={relationTo}
303+
/>
332304
</div>
333305
)}
334306
{data?.docs && data.docs.length > 0 && (
@@ -349,7 +321,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
349321
}
350322
>
351323
<TableColumnsProvider
352-
collectionSlug={Array.isArray(relationTo) ? relationTo[0] : relationTo}
324+
collectionSlug={isPolymorphic ? relationTo[0] : relationTo}
353325
columnState={columnState}
354326
LinkedCellOverride={
355327
<DrawerLink onDrawerDelete={onDrawerDelete} onDrawerSave={onDrawerSave} />

0 commit comments

Comments
 (0)