Skip to content

Commit 17e0547

Browse files
authored
feat(payload, ui): add admin.allowEdit relationship field (#8398)
This PR adds a new property `allowEdit` to the admin of the relationship field. It is very similar to the existing `allowCreate`, only in this case it hides the edit icon: <img width="796" alt="image" src="https://github.com/user-attachments/assets/bbe79bb2-db06-4ec4-b023-2f1c53330fcb">
1 parent e900e89 commit 17e0547

File tree

11 files changed

+63
-15
lines changed

11 files changed

+63
-15
lines changed

docs/fields/relationship.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ The Relationship Field inherits all of the default options from the base [Field
9090
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
9191
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany` is set to `true`). |
9292
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
93+
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
9394
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sortOptions) |
9495

9596
### Sort Options

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,7 @@ type SharedRelationshipPropertiesClient = FieldBaseClient &
11251125

11261126
type RelationshipAdmin = {
11271127
allowCreate?: boolean
1128+
allowEdit?: boolean
11281129
components?: {
11291130
Error?: CustomComponent<
11301131
RelationshipFieldErrorClientComponent | RelationshipFieldErrorServerComponent
@@ -1142,7 +1143,7 @@ type RelationshipAdminClient = {
11421143
Label?: MappedComponent
11431144
} & AdminClient['components']
11441145
} & AdminClient &
1145-
Pick<RelationshipAdmin, 'allowCreate' | 'isSortable'>
1146+
Pick<RelationshipAdmin, 'allowCreate' | 'allowEdit' | 'isSortable'>
11461147

11471148
export type PolymorphicRelationshipField = {
11481149
admin?: {

packages/ui/src/elements/ReactSelect/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ export type ReactSelectAdapterProps = {
6363
disabled?: boolean
6464
filterOption?:
6565
| ((
66-
{ data, label, value }: { data: Option; label: string; value: string },
66+
{
67+
allowEdit,
68+
data,
69+
label,
70+
value,
71+
}: { allowEdit: boolean; data: Option; label: string; value: string },
6772
search: string,
6873
) => boolean)
6974
| undefined

packages/ui/src/fields/Relationship/findOptionsByValue.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import type { Option } from '../../elements/ReactSelect/types.js'
33
import type { OptionGroup, Value } from './types.js'
44

55
type Args = {
6+
allowEdit: boolean
67
options: OptionGroup[]
78
value: Value | Value[]
89
}
910

10-
export const findOptionsByValue = ({ options, value }: Args): Option | Option[] => {
11+
export const findOptionsByValue = ({ allowEdit, options, value }: Args): Option | Option[] => {
1112
if (value || typeof value === 'number') {
1213
if (Array.isArray(value)) {
1314
return value.map((val) => {
@@ -25,7 +26,7 @@ export const findOptionsByValue = ({ options, value }: Args): Option | Option[]
2526
}
2627
})
2728

28-
return matchedOption
29+
return matchedOption ? { allowEdit, ...matchedOption } : undefined
2930
})
3031
}
3132

@@ -42,7 +43,7 @@ export const findOptionsByValue = ({ options, value }: Args): Option | Option[]
4243
}
4344
})
4445

45-
return matchedOption
46+
return matchedOption ? { allowEdit, ...matchedOption } : undefined
4647
}
4748

4849
return undefined

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
4646
_path: pathFromProps,
4747
admin: {
4848
allowCreate = true,
49+
allowEdit = true,
4950
className,
5051
description,
5152
isSortable = true,
@@ -576,7 +577,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
576577
}
577578
}, [openDrawer, currentlyOpenRelationship])
578579

579-
const valueToRender = findOptionsByValue({ options, value })
580+
const valueToRender = findOptionsByValue({ allowEdit, options, value })
580581

581582
if (!Array.isArray(valueToRender) && valueToRender?.value === 'null') {
582583
valueToRender.value = null

packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const MultiValueLabel: React.FC<
2424
} & MultiValueProps<Option>
2525
> = (props) => {
2626
const {
27-
data: { label, relationTo, value },
27+
data: { allowEdit, label, relationTo, value },
2828
selectProps: { customProps: { draggableProps, onDocumentDrawerOpen } = {} } = {},
2929
} = props
3030

@@ -44,7 +44,7 @@ export const MultiValueLabel: React.FC<
4444
}}
4545
/>
4646
</div>
47-
{relationTo && hasReadPermission && (
47+
{relationTo && hasReadPermission && allowEdit !== false && (
4848
<Fragment>
4949
<button
5050
aria-label={`Edit ${label}`}

packages/ui/src/fields/Relationship/select-components/SingleValue/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const SingleValue: React.FC<
2525
> = (props) => {
2626
const {
2727
children,
28-
data: { label, relationTo, value },
28+
data: { allowEdit, label, relationTo, value },
2929
selectProps: { customProps: { onDocumentDrawerOpen } = {} } = {},
3030
} = props
3131

@@ -39,7 +39,7 @@ export const SingleValue: React.FC<
3939
<div className={`${baseClass}__label`}>
4040
<div className={`${baseClass}__label-text`}>
4141
<div className={`${baseClass}__text`}>{children}</div>
42-
{relationTo && hasReadPermission && (
42+
{relationTo && hasReadPermission && allowEdit !== false && (
4343
<Fragment>
4444
<button
4545
aria-label={t('general:editLabel', { label })}

packages/ui/src/fields/Relationship/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { I18nClient } from '@payloadcms/translations'
22
import type { ClientCollectionConfig, ClientConfig, FilterOptionsResult } from 'payload'
33

44
export type Option = {
5+
allowEdit: boolean
56
label: string
67
options?: Option[]
78
relationTo?: string

test/fields/collections/Relationship/e2e.spec.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,32 @@ describe('relationship', () => {
159159

160160
test('should hide relationship add new button', async () => {
161161
await page.goto(url.create)
162+
await page.waitForURL(url.create)
163+
const locator1 = page.locator(
164+
'#relationWithAllowEditToFalse-add-new .relationship-add-new__add-button',
165+
)
166+
await expect(locator1).toHaveCount(1)
162167
// expect the button to not exist in the field
163-
const count = await page
164-
.locator('#relationToSelfSelectOnly-add-new .relationship-add-new__add-button')
165-
.count()
166-
expect(count).toEqual(0)
168+
const locator2 = page.locator(
169+
'#relationWithAllowCreateToFalse-add-new .relationship-add-new__add-button',
170+
)
171+
await expect(locator2).toHaveCount(0)
172+
})
173+
174+
test('should hide relationship edit button', async () => {
175+
await page.goto(url.create)
176+
await page.waitForURL(url.create)
177+
const locator1 = page
178+
.locator('#field-relationWithAllowEditToFalse')
179+
.getByLabel('Edit dev@payloadcms.com')
180+
await expect(locator1).toHaveCount(0)
181+
const locator2 = page
182+
.locator('#field-relationWithAllowCreateToFalse')
183+
.getByLabel('Edit dev@payloadcms.com')
184+
await expect(locator2).toHaveCount(1)
185+
// The reason why I check for locator 1 again is that I've noticed that sometimes
186+
// the default value does not appear after the first locator is tested. IDK why.
187+
await expect(locator1).toHaveCount(0)
167188
})
168189

169190
// TODO: Flaky test in CI - fix this. https://github.com/payloadcms/payload/actions/runs/8910825395/job/24470963991

test/fields/collections/Relationship/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,25 @@ const RelationshipFields: CollectionConfig = {
3838
},
3939
{
4040
name: 'relationToSelfSelectOnly',
41+
relationTo: relationshipFieldsSlug,
42+
type: 'relationship',
43+
},
44+
{
45+
name: 'relationWithAllowCreateToFalse',
4146
admin: {
4247
allowCreate: false,
4348
},
44-
relationTo: relationshipFieldsSlug,
49+
defaultValue: ({ user }) => user?.id,
50+
relationTo: 'users',
51+
type: 'relationship',
52+
},
53+
{
54+
name: 'relationWithAllowEditToFalse',
55+
admin: {
56+
allowEdit: false,
57+
},
58+
defaultValue: ({ user }) => user?.id,
59+
relationTo: 'users',
4560
type: 'relationship',
4661
},
4762
{

0 commit comments

Comments
 (0)