feat(ui): add option for rendering the relationship field as list drawer (#11553)
### What? This PR adds the ability to use the ListDrawer component for selecting related collections for the relationship field instead of the default drop down interface. This exposes the advanced filtering options that the list view provides and provides a good interface for searching for the correct relationship when the workflows may be more complicated. I've added an additional "selectionType" prop to the relationship field admin config that defaults to "dropdown" for compatability with the existing implementation but "drawer" can be passed in as well which enables using the ListDrawer for selecting related components. ### Why? Adding the ability to search through the list view enables advanced workflows or handles edge cases when just using the useAsTitle may not be informative enough to find the related record that the user wants. For example, if we have a collection of oscars nominations and are trying to relate the nomination to the person who recieved the nomination there may be multiple actors with the same name (Michelle Williams, for example: [https://www.imdb.com/name/nm0931329/](https://www.imdb.com/name/nm0931329/), [https://www.imdb.com/name/nm0931332/](https://www.imdb.com/name/nm0931332/)). It would be hard to search through the current dropdown ui to choose the correct person, but in the list view the user could use other fields to identify the correct person such as an imdb id, description, or anything else they have in the collection for that person. Other advanced workflows could be if there are multiple versions of a record in a collection and the user wants to select the most recent one or just anything where the user needs to see more details about the record that they are setting up the relationship to. ### How? This implementation just re-uses the useListDrawer hook and the ListDrawer component so the code changes are pretty minimal. The main change is a new onListSelect handler that gets passed into the ListDrawer and handles updating the value in the field when a record is selected in the ListDrawer. There were also a two things that I didn't implement as they would require broader code changes 1) Bulk select from the ListDrawer when a relationship is hasMany - when using bulkSelect in the list drawer the relatedCollection doesn't get returned so this doesn't work for polymorphic relationships. Updating this would involve changing the useListDrawer hook 2) Hide values that are already selected from the ListDrawer - potentially possible by modifying the filterOptions and passing in an additional filter but honestly it may not be desired behaviour to hide values from the ListDrawer as this could be confusing for the user if they don't see records that they are expected to see (maybe if anything make them unselectable and indicate that they are disabled). Currently if an already selected value gets selected the selected value gets replaced by the new value https://github.com/user-attachments/assets/fee164da-4270-4612-9304-73ccf34ccf69 --------- Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
This commit is contained in:
@@ -94,6 +94,7 @@ The Relationship Field inherits all of the default options from the base [Field
|
|||||||
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
|
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
|
||||||
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
|
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
|
||||||
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
|
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
|
||||||
|
| **`appearance`** | Set to `drawer` or `select` to change the behavior of the field. Defaults to `select`. |
|
||||||
|
|
||||||
### Sort Options
|
### Sort Options
|
||||||
|
|
||||||
|
|||||||
@@ -1143,6 +1143,7 @@ type SharedRelationshipPropertiesClient = FieldBaseClient &
|
|||||||
type RelationshipAdmin = {
|
type RelationshipAdmin = {
|
||||||
allowCreate?: boolean
|
allowCreate?: boolean
|
||||||
allowEdit?: boolean
|
allowEdit?: boolean
|
||||||
|
appearance?: 'drawer' | 'select'
|
||||||
components?: {
|
components?: {
|
||||||
afterInput?: CustomComponent[]
|
afterInput?: CustomComponent[]
|
||||||
beforeInput?: CustomComponent[]
|
beforeInput?: CustomComponent[]
|
||||||
@@ -1157,7 +1158,7 @@ type RelationshipAdmin = {
|
|||||||
} & Admin
|
} & Admin
|
||||||
|
|
||||||
type RelationshipAdminClient = AdminClient &
|
type RelationshipAdminClient = AdminClient &
|
||||||
Pick<RelationshipAdmin, 'allowCreate' | 'allowEdit' | 'isSortable'>
|
Pick<RelationshipAdmin, 'allowCreate' | 'allowEdit' | 'appearance' | 'isSortable'>
|
||||||
|
|
||||||
export type PolymorphicRelationshipField = {
|
export type PolymorphicRelationshipField = {
|
||||||
admin?: {
|
admin?: {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { PaginatedDocs, RelationshipFieldClientComponent, Where } from 'payload'
|
import type {
|
||||||
|
FilterOptionsResult,
|
||||||
|
PaginatedDocs,
|
||||||
|
RelationshipFieldClientComponent,
|
||||||
|
Where,
|
||||||
|
} from 'payload'
|
||||||
|
|
||||||
import { dequal } from 'dequal/lite'
|
import { dequal } from 'dequal/lite'
|
||||||
import { wordBoundariesRegex } from 'payload/shared'
|
import { wordBoundariesRegex } from 'payload/shared'
|
||||||
@@ -7,11 +12,13 @@ import * as qs from 'qs-esm'
|
|||||||
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||||
|
|
||||||
import type { DocumentDrawerProps } from '../../elements/DocumentDrawer/types.js'
|
import type { DocumentDrawerProps } from '../../elements/DocumentDrawer/types.js'
|
||||||
|
import type { ListDrawerProps } from '../../elements/ListDrawer/types.js'
|
||||||
import type { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js'
|
import type { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js'
|
||||||
import type { GetResults, Option, Value } from './types.js'
|
import type { GetResults, Option, Value } from './types.js'
|
||||||
|
|
||||||
import { AddNewRelation } from '../../elements/AddNewRelation/index.js'
|
import { AddNewRelation } from '../../elements/AddNewRelation/index.js'
|
||||||
import { useDocumentDrawer } from '../../elements/DocumentDrawer/index.js'
|
import { useDocumentDrawer } from '../../elements/DocumentDrawer/index.js'
|
||||||
|
import { useListDrawer } from '../../elements/ListDrawer/index.js'
|
||||||
import { ReactSelect } from '../../elements/ReactSelect/index.js'
|
import { ReactSelect } from '../../elements/ReactSelect/index.js'
|
||||||
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
|
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
|
||||||
import { FieldDescription } from '../../fields/FieldDescription/index.js'
|
import { FieldDescription } from '../../fields/FieldDescription/index.js'
|
||||||
@@ -45,6 +52,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
admin: {
|
admin: {
|
||||||
allowCreate = true,
|
allowCreate = true,
|
||||||
allowEdit = true,
|
allowEdit = true,
|
||||||
|
appearance = 'select',
|
||||||
className,
|
className,
|
||||||
description,
|
description,
|
||||||
isSortable = true,
|
isSortable = true,
|
||||||
@@ -111,6 +119,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
path,
|
path,
|
||||||
validate: memoizedValidate,
|
validate: memoizedValidate,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [options, dispatchOptions] = useReducer(optionsReducer, [])
|
const [options, dispatchOptions] = useReducer(optionsReducer, [])
|
||||||
|
|
||||||
const valueRef = useRef(value)
|
const valueRef = useRef(value)
|
||||||
@@ -121,6 +130,60 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
collectionSlug: currentlyOpenRelationship.collectionSlug,
|
collectionSlug: currentlyOpenRelationship.collectionSlug,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Filter selected values from displaying in the list drawer
|
||||||
|
const listDrawerFilterOptions = useMemo<FilterOptionsResult>(() => {
|
||||||
|
let newFilterOptions = filterOptions
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
;(Array.isArray(value) ? value : [value]).forEach((val) => {
|
||||||
|
;(Array.isArray(relationTo) ? relationTo : [relationTo]).forEach((relationTo) => {
|
||||||
|
newFilterOptions = {
|
||||||
|
...(filterOptions || {}),
|
||||||
|
[relationTo]: {
|
||||||
|
...(typeof filterOptions?.[relationTo] === 'object' ? filterOptions[relationTo] : {}),
|
||||||
|
id: {
|
||||||
|
not_in: [typeof val === 'object' ? val.value : val],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFilterOptions
|
||||||
|
}, [filterOptions, value, relationTo])
|
||||||
|
|
||||||
|
const [
|
||||||
|
ListDrawer,
|
||||||
|
,
|
||||||
|
{ closeDrawer: closeListDrawer, isDrawerOpen: isListDrawerOpen, openDrawer: openListDrawer },
|
||||||
|
] = useListDrawer({
|
||||||
|
collectionSlugs: hasMultipleRelations ? relationTo : [relationTo],
|
||||||
|
filterOptions: listDrawerFilterOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onListSelect = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
|
||||||
|
({ collectionSlug, doc }) => {
|
||||||
|
const formattedSelection = hasMultipleRelations
|
||||||
|
? {
|
||||||
|
relationTo: collectionSlug,
|
||||||
|
value: doc.id,
|
||||||
|
}
|
||||||
|
: doc.id
|
||||||
|
|
||||||
|
if (hasMany) {
|
||||||
|
const withSelection = Array.isArray(value) ? value : []
|
||||||
|
withSelection.push(formattedSelection)
|
||||||
|
setValue(withSelection)
|
||||||
|
} else {
|
||||||
|
setValue(formattedSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
closeListDrawer()
|
||||||
|
},
|
||||||
|
[hasMany, hasMultipleRelations, setValue, closeListDrawer, value],
|
||||||
|
)
|
||||||
|
|
||||||
const openDrawerWhenRelationChanges = useRef(false)
|
const openDrawerWhenRelationChanges = useRef(false)
|
||||||
|
|
||||||
const getResults: GetResults = useCallback(
|
const getResults: GetResults = useCallback(
|
||||||
@@ -601,18 +664,19 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
{!errorLoading && (
|
{!errorLoading && (
|
||||||
<div className={`${baseClass}__wrap`}>
|
<div className={`${baseClass}__wrap`}>
|
||||||
<ReactSelect
|
<ReactSelect
|
||||||
backspaceRemovesValue={!isDrawerOpen}
|
backspaceRemovesValue={!(isDrawerOpen || isListDrawerOpen)}
|
||||||
components={{
|
components={{
|
||||||
MultiValueLabel,
|
MultiValueLabel,
|
||||||
SingleValue,
|
SingleValue,
|
||||||
|
...(appearance !== 'select' && { DropdownIndicator: null }),
|
||||||
}}
|
}}
|
||||||
customProps={{
|
customProps={{
|
||||||
disableKeyDown: isDrawerOpen,
|
disableKeyDown: isDrawerOpen || isListDrawerOpen,
|
||||||
disableMouseDown: isDrawerOpen,
|
disableMouseDown: isDrawerOpen || isListDrawerOpen,
|
||||||
onDocumentDrawerOpen,
|
onDocumentDrawerOpen,
|
||||||
onSave,
|
onSave,
|
||||||
}}
|
}}
|
||||||
disabled={readOnly || disabled || isDrawerOpen}
|
disabled={readOnly || disabled || isDrawerOpen || isListDrawerOpen}
|
||||||
filterOption={enableWordBoundarySearch ? filterOption : undefined}
|
filterOption={enableWordBoundarySearch ? filterOption : undefined}
|
||||||
getOptionValue={(option) => {
|
getOptionValue={(option) => {
|
||||||
if (!option) {
|
if (!option) {
|
||||||
@@ -622,9 +686,11 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
? `${option.relationTo}_${option.value}`
|
? `${option.relationTo}_${option.value}`
|
||||||
: (option.value as string)
|
: (option.value as string)
|
||||||
}}
|
}}
|
||||||
isLoading={isLoading}
|
isLoading={appearance === 'select' && isLoading}
|
||||||
isMulti={hasMany}
|
isMulti={hasMany}
|
||||||
|
isSearchable={appearance === 'select'}
|
||||||
isSortable={isSortable}
|
isSortable={isSortable}
|
||||||
|
menuIsOpen={appearance === 'select' ? menuIsOpen : false}
|
||||||
onChange={
|
onChange={
|
||||||
!(readOnly || disabled)
|
!(readOnly || disabled)
|
||||||
? (selected) => {
|
? (selected) => {
|
||||||
@@ -661,8 +727,10 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
setMenuIsOpen(false)
|
setMenuIsOpen(false)
|
||||||
}}
|
}}
|
||||||
onMenuOpen={() => {
|
onMenuOpen={() => {
|
||||||
|
if (appearance === 'drawer') {
|
||||||
|
openListDrawer()
|
||||||
|
} else if (appearance === 'select') {
|
||||||
setMenuIsOpen(true)
|
setMenuIsOpen(true)
|
||||||
|
|
||||||
if (!hasLoadedFirstPageRef.current) {
|
if (!hasLoadedFirstPageRef.current) {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
void getResults({
|
void getResults({
|
||||||
@@ -675,6 +743,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
value: initialValue,
|
value: initialValue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onMenuScrollToBottom={() => {
|
onMenuScrollToBottom={() => {
|
||||||
void getResults({
|
void getResults({
|
||||||
@@ -711,6 +780,9 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
|||||||
{currentlyOpenRelationship.collectionSlug && currentlyOpenRelationship.hasReadPermission && (
|
{currentlyOpenRelationship.collectionSlug && currentlyOpenRelationship.hasReadPermission && (
|
||||||
<DocumentDrawer onDelete={onDelete} onDuplicate={onDuplicate} onSave={onSave} />
|
<DocumentDrawer onDelete={onDelete} onDuplicate={onDuplicate} onSave={onSave} />
|
||||||
)}
|
)}
|
||||||
|
{appearance === 'drawer' && !readOnly && (
|
||||||
|
<ListDrawer allowCreate={allowCreate} enableRowSelections={false} onSelect={onListSelect} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -650,6 +650,163 @@ describe('relationship', () => {
|
|||||||
|
|
||||||
await expect(page.locator(tableRowLocator)).toHaveCount(1)
|
await expect(page.locator(tableRowLocator)).toHaveCount(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should be able to select relationship with drawer appearance', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
const relationshipField = page.locator('#field-relationshipDrawer')
|
||||||
|
await relationshipField.click()
|
||||||
|
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
|
||||||
|
await expect(listDrawerContent).toBeVisible()
|
||||||
|
|
||||||
|
const firstRow = listDrawerContent.locator('table tbody tr').first()
|
||||||
|
const button = firstRow.locator('button')
|
||||||
|
await button.click()
|
||||||
|
await expect(listDrawerContent).toBeHidden()
|
||||||
|
|
||||||
|
const selectedValue = relationshipField.locator('.relationship--single-value__text')
|
||||||
|
await expect(selectedValue).toBeVisible()
|
||||||
|
|
||||||
|
// Fill required field
|
||||||
|
await page.locator('#field-relationship').click()
|
||||||
|
await page.locator('.rs__option:has-text("Seeded text document")').click()
|
||||||
|
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should be able to search within relationship list drawer', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
const relationshipField = page.locator('#field-relationshipDrawer')
|
||||||
|
await relationshipField.click()
|
||||||
|
const searchField = page.locator('.list-drawer .search-filter')
|
||||||
|
await expect(searchField).toBeVisible()
|
||||||
|
|
||||||
|
const searchInput = searchField.locator('input')
|
||||||
|
await searchInput.fill('seeded')
|
||||||
|
const rows = page.locator('.list-drawer table tbody tr')
|
||||||
|
|
||||||
|
await expect(rows).toHaveCount(1)
|
||||||
|
const closeButton = page.locator('.list-drawer__header-close')
|
||||||
|
await closeButton.click()
|
||||||
|
|
||||||
|
await expect(page.locator('.list-drawer')).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle read-only relationship field when `appearance: "drawer"`', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
const readOnlyField = page.locator(
|
||||||
|
'#field-relationshipDrawerReadOnly .rs__control--is-disabled',
|
||||||
|
)
|
||||||
|
await expect(readOnlyField).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle polymorphic relationship when `appearance: "drawer"`', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
const relationshipField = page.locator('#field-polymorphicRelationshipDrawer')
|
||||||
|
await relationshipField.click()
|
||||||
|
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
|
||||||
|
await expect(listDrawerContent).toBeVisible()
|
||||||
|
|
||||||
|
const relationToSelector = page.locator('.list-header__select-collection')
|
||||||
|
await expect(relationToSelector).toBeVisible()
|
||||||
|
|
||||||
|
await relationToSelector.locator('.rs__control').click()
|
||||||
|
const option = relationToSelector.locator('.rs__option').nth(1)
|
||||||
|
await option.click()
|
||||||
|
const firstRow = listDrawerContent.locator('table tbody tr').first()
|
||||||
|
const button = firstRow.locator('button')
|
||||||
|
await button.click()
|
||||||
|
await expect(listDrawerContent).toBeHidden()
|
||||||
|
|
||||||
|
const selectedValue = relationshipField.locator('.relationship--single-value__text')
|
||||||
|
await expect(selectedValue).toBeVisible()
|
||||||
|
|
||||||
|
// Fill required field
|
||||||
|
await page.locator('#field-relationship').click()
|
||||||
|
await page.locator('.rs__option:has-text("Seeded text document")').click()
|
||||||
|
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle `hasMany` relationship when `appearance: "drawer"`', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
const relationshipField = page.locator('#field-relationshipDrawerHasMany')
|
||||||
|
await relationshipField.click()
|
||||||
|
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
|
||||||
|
await expect(listDrawerContent).toBeVisible()
|
||||||
|
|
||||||
|
const firstRow = listDrawerContent.locator('table tbody tr').first()
|
||||||
|
const button = firstRow.locator('button')
|
||||||
|
await button.click()
|
||||||
|
await expect(listDrawerContent).toBeHidden()
|
||||||
|
|
||||||
|
const selectedValue = relationshipField.locator('.relationship--multi-value-label__text')
|
||||||
|
await expect(selectedValue).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle `hasMany` polymorphic relationship when `appearance: "drawer"`', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
const relationshipField = page.locator('#field-relationshipDrawerHasManyPolymorphic')
|
||||||
|
await relationshipField.click()
|
||||||
|
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
|
||||||
|
await expect(listDrawerContent).toBeVisible()
|
||||||
|
|
||||||
|
const firstRow = listDrawerContent.locator('table tbody tr').first()
|
||||||
|
const button = firstRow.locator('button')
|
||||||
|
await button.click()
|
||||||
|
await expect(listDrawerContent).toBeHidden()
|
||||||
|
|
||||||
|
const selectedValue = relationshipField.locator('.relationship--multi-value-label__text')
|
||||||
|
await expect(selectedValue).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not be allowed to create in relationship list drawer when `allowCreate` is `false`', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
const relationshipField = page.locator('#field-relationshipDrawerWithAllowCreateFalse')
|
||||||
|
await relationshipField.click()
|
||||||
|
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
|
||||||
|
await expect(listDrawerContent).toBeVisible()
|
||||||
|
|
||||||
|
const createNewButton = listDrawerContent.locator('list-drawer__create-new-button')
|
||||||
|
await expect(createNewButton).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should respect `filterOptions` in the relationship list drawer for filtered relationship', async () => {
|
||||||
|
// Create test documents
|
||||||
|
await createTextFieldDoc({ text: 'list drawer test' })
|
||||||
|
await createTextFieldDoc({ text: 'not test' })
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
const relationshipField = page.locator('#field-relationshipDrawerWithFilterOptions')
|
||||||
|
await relationshipField.click()
|
||||||
|
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
|
||||||
|
await expect(listDrawerContent).toBeVisible()
|
||||||
|
|
||||||
|
const rows = page.locator('.list-drawer table tbody tr')
|
||||||
|
await expect(rows).toHaveCount(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should filter out existing values from relationship list drawer', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
await page.locator('#field-relationshipDrawer').click()
|
||||||
|
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
|
||||||
|
await expect(listDrawerContent).toBeVisible()
|
||||||
|
const rows = listDrawerContent.locator('table tbody tr')
|
||||||
|
await expect(rows).toHaveCount(2)
|
||||||
|
await listDrawerContent.getByText('Seeded text document', { exact: true }).click()
|
||||||
|
|
||||||
|
const selectedValue = page.locator(
|
||||||
|
'#field-relationshipDrawer .relationship--single-value__text',
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(selectedValue).toHaveText('Seeded text document')
|
||||||
|
await page.locator('#field-relationshipDrawer').click()
|
||||||
|
const newRows = listDrawerContent.locator('table tbody tr')
|
||||||
|
await expect(newRows).toHaveCount(1)
|
||||||
|
await expect(listDrawerContent.getByText('Seeded text document')).toHaveCount(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function createTextFieldDoc(overrides?: Partial<TextField>): Promise<TextField> {
|
async function createTextFieldDoc(overrides?: Partial<TextField>): Promise<TextField> {
|
||||||
|
|||||||
@@ -126,6 +126,71 @@ const RelationshipFields: CollectionConfig = {
|
|||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
hasMany: true,
|
hasMany: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'relationshipDrawer',
|
||||||
|
relationTo: 'text-fields',
|
||||||
|
admin: { appearance: 'drawer' },
|
||||||
|
type: 'relationship',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'relationshipDrawerReadOnly',
|
||||||
|
relationTo: 'text-fields',
|
||||||
|
admin: {
|
||||||
|
readOnly: true,
|
||||||
|
appearance: 'drawer',
|
||||||
|
},
|
||||||
|
type: 'relationship',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'polymorphicRelationshipDrawer',
|
||||||
|
admin: { appearance: 'drawer' },
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: ['text-fields', 'array-fields'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'relationshipDrawerHasMany',
|
||||||
|
relationTo: 'text-fields',
|
||||||
|
admin: {
|
||||||
|
appearance: 'drawer',
|
||||||
|
},
|
||||||
|
hasMany: true,
|
||||||
|
type: 'relationship',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'relationshipDrawerHasManyPolymorphic',
|
||||||
|
relationTo: ['text-fields'],
|
||||||
|
admin: {
|
||||||
|
appearance: 'drawer',
|
||||||
|
},
|
||||||
|
hasMany: true,
|
||||||
|
type: 'relationship',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'relationshipDrawerWithAllowCreateFalse',
|
||||||
|
admin: {
|
||||||
|
allowCreate: false,
|
||||||
|
appearance: 'drawer',
|
||||||
|
},
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'text-fields',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'relationshipDrawerWithFilterOptions',
|
||||||
|
admin: { appearance: 'drawer' },
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: ['text-fields'],
|
||||||
|
filterOptions: ({ relationTo }) => {
|
||||||
|
if (relationTo === 'text-fields') {
|
||||||
|
return {
|
||||||
|
text: {
|
||||||
|
equals: 'list drawer test',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
slug: relationshipFieldsSlug,
|
slug: relationshipFieldsSlug,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1312,6 +1312,29 @@ export interface RelationshipField {
|
|||||||
| null;
|
| null;
|
||||||
relationToRow?: (string | null) | RowField;
|
relationToRow?: (string | null) | RowField;
|
||||||
relationToRowMany?: (string | RowField)[] | null;
|
relationToRowMany?: (string | RowField)[] | null;
|
||||||
|
relationshipDrawer?: (string | null) | TextField;
|
||||||
|
relationshipDrawerReadOnly?: (string | null) | TextField;
|
||||||
|
polymorphicRelationshipDrawer?:
|
||||||
|
| ({
|
||||||
|
relationTo: 'text-fields';
|
||||||
|
value: string | TextField;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'array-fields';
|
||||||
|
value: string | ArrayField;
|
||||||
|
} | null);
|
||||||
|
relationshipDrawerHasMany?: (string | TextField)[] | null;
|
||||||
|
relationshipDrawerHasManyPolymorphic?:
|
||||||
|
| {
|
||||||
|
relationTo: 'text-fields';
|
||||||
|
value: string | TextField;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
relationshipDrawerWithAllowCreateFalse?: (string | null) | TextField;
|
||||||
|
relationshipDrawerWithFilterOptions?: {
|
||||||
|
relationTo: 'text-fields';
|
||||||
|
value: string | TextField;
|
||||||
|
} | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
@@ -2786,6 +2809,13 @@ export interface RelationshipFieldsSelect<T extends boolean = true> {
|
|||||||
relationshipWithMinRows?: T;
|
relationshipWithMinRows?: T;
|
||||||
relationToRow?: T;
|
relationToRow?: T;
|
||||||
relationToRowMany?: T;
|
relationToRowMany?: T;
|
||||||
|
relationshipDrawer?: T;
|
||||||
|
relationshipDrawerReadOnly?: T;
|
||||||
|
polymorphicRelationshipDrawer?: T;
|
||||||
|
relationshipDrawerHasMany?: T;
|
||||||
|
relationshipDrawerHasManyPolymorphic?: T;
|
||||||
|
relationshipDrawerWithAllowCreateFalse?: T;
|
||||||
|
relationshipDrawerWithFilterOptions?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@payload-config": ["./test/hooks/config.ts"],
|
"@payload-config": ["./test/_community/config.ts"],
|
||||||
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
||||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user