feat(ui): threads row data through list drawer onSelect callback (#11339)

When rendering a list drawer, you can pass a custom `onSelect` callback
to execute when the user clicks on the linked cell within the table. The
underlying handler, however, only passes the `docID` and
`collectionSlug` args through the callback, rather than the document
itself. This makes it impossible to perform side-effects that require
the data of the row that was selected.

Instances of this callback were also largely untyped.

Needed for #11330.
This commit is contained in:
Jacob Fletcher
2025-02-21 17:08:05 -05:00
committed by GitHub
parent f31568c69c
commit d766b1904c
10 changed files with 47 additions and 29 deletions

View File

@@ -1,4 +1,5 @@
'use client' 'use client'
import type { ListDrawerProps } from '@payloadcms/ui'
import type { LexicalEditor } from 'lexical' import type { LexicalEditor } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
@@ -67,13 +68,13 @@ const RelationshipDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }
) )
}, [editor, openListDrawer]) }, [editor, openListDrawer])
const onSelect = useCallback( const onSelect = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
({ collectionSlug, docID }: { collectionSlug: string; docID: number | string }) => { ({ collectionSlug, doc }) => {
insertRelationship({ insertRelationship({
editor, editor,
relationTo: collectionSlug, relationTo: collectionSlug,
replaceNodeKey, replaceNodeKey,
value: docID, value: doc.id,
}) })
closeListDrawer() closeListDrawer()
}, },

View File

@@ -1,4 +1,5 @@
'use client' 'use client'
import type { ListDrawerProps } from '@payloadcms/ui'
import type { LexicalEditor } from 'lexical' import type { LexicalEditor } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
@@ -77,14 +78,14 @@ const UploadDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }) => {
) )
}, [editor, openListDrawer]) }, [editor, openListDrawer])
const onSelect = useCallback( const onSelect = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
({ collectionSlug, docID }: { collectionSlug: string; docID: number | string }) => { ({ collectionSlug, doc }) => {
closeListDrawer() closeListDrawer()
insertUpload({ insertUpload({
editor, editor,
relationTo: collectionSlug, relationTo: collectionSlug,
replaceNodeKey, replaceNodeKey,
value: docID, value: doc.id,
}) })
}, },
[editor, closeListDrawer, replaceNodeKey], [editor, closeListDrawer, replaceNodeKey],

View File

@@ -1,7 +1,8 @@
'use client' 'use client'
import type { ListDrawerProps } from '@payloadcms/ui'
import { useListDrawer, useTranslation } from '@payloadcms/ui' import { useListDrawer, useTranslation } from '@payloadcms/ui'
import React, { Fragment, useCallback, useEffect, useState } from 'react' import React, { Fragment, useCallback, useState } from 'react'
import { ReactEditor, useSlate } from 'slate-react' import { ReactEditor, useSlate } from 'slate-react'
import { RelationshipIcon } from '../../../icons/Relationship/index.js' import { RelationshipIcon } from '../../../icons/Relationship/index.js'
@@ -34,15 +35,13 @@ type Props = {
const RelationshipButtonComponent: React.FC<Props> = ({ enabledCollectionSlugs }) => { const RelationshipButtonComponent: React.FC<Props> = ({ enabledCollectionSlugs }) => {
const { t } = useTranslation() const { t } = useTranslation()
const editor = useSlate() const editor = useSlate()
const [selectedCollectionSlug, setSelectedCollectionSlug] = useState( const [selectedCollectionSlug] = useState(() => enabledCollectionSlugs[0])
() => enabledCollectionSlugs[0], const [ListDrawer, ListDrawerToggler, { closeDrawer }] = useListDrawer({
)
const [ListDrawer, ListDrawerToggler, { closeDrawer, isDrawerOpen }] = useListDrawer({
collectionSlugs: enabledCollectionSlugs, collectionSlugs: enabledCollectionSlugs,
selectedCollection: selectedCollectionSlug, selectedCollection: selectedCollectionSlug,
}) })
const onSelect = useCallback( const onSelect = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
({ collectionSlug, docID }) => { ({ collectionSlug, docID }) => {
insertRelationship(editor, { insertRelationship(editor, {
relationTo: collectionSlug, relationTo: collectionSlug,

View File

@@ -1,5 +1,7 @@
'use client' 'use client'
import type { ListDrawerProps } from '@payloadcms/ui'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { import {
Button, Button,
@@ -103,8 +105,8 @@ const RelationshipElementComponent: React.FC = () => {
[editor, element, relatedCollection, cacheBust, setParams, closeDrawer], [editor, element, relatedCollection, cacheBust, setParams, closeDrawer],
) )
const swapRelationship = React.useCallback( const swapRelationship = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
({ collectionSlug, docID }) => { ({ collectionSlug, doc }) => {
const elementPath = ReactEditor.findPath(editor, element) const elementPath = ReactEditor.findPath(editor, element)
Transforms.setNodes( Transforms.setNodes(
@@ -113,7 +115,7 @@ const RelationshipElementComponent: React.FC = () => {
type: 'relationship', type: 'relationship',
children: [{ text: ' ' }], children: [{ text: ' ' }],
relationTo: collectionSlug, relationTo: collectionSlug,
value: { id: docID }, value: { id: doc.id },
}, },
{ at: elementPath }, { at: elementPath },
) )

View File

@@ -1,5 +1,7 @@
'use client' 'use client'
import type { ListDrawerProps } from '@payloadcms/ui'
import { useListDrawer, useTranslation } from '@payloadcms/ui' import { useListDrawer, useTranslation } from '@payloadcms/ui'
import React, { Fragment, useCallback } from 'react' import React, { Fragment, useCallback } from 'react'
import { ReactEditor, useSlate } from 'slate-react' import { ReactEditor, useSlate } from 'slate-react'
@@ -41,12 +43,12 @@ const UploadButton: React.FC<ButtonProps> = ({ enabledCollectionSlugs }) => {
uploads: true, uploads: true,
}) })
const onSelect = useCallback( const onSelect = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
({ collectionSlug, docID }) => { ({ collectionSlug, doc }) => {
insertUpload(editor, { insertUpload(editor, {
relationTo: collectionSlug, relationTo: collectionSlug,
value: { value: {
id: docID, id: doc.id,
}, },
}) })
closeDrawer() closeDrawer()

View File

@@ -1,5 +1,6 @@
'use client' 'use client'
import type { ListDrawerProps } from '@payloadcms/ui'
import type { ClientCollectionConfig } from 'payload' import type { ClientCollectionConfig } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
@@ -23,8 +24,8 @@ import type { UploadElementType } from '../types.js'
import { useElement } from '../../../providers/ElementProvider.js' import { useElement } from '../../../providers/ElementProvider.js'
import { EnabledRelationshipsCondition } from '../../EnabledRelationshipsCondition.js' import { EnabledRelationshipsCondition } from '../../EnabledRelationshipsCondition.js'
import { uploadFieldsSchemaPath, uploadName } from '../shared.js' import { uploadFieldsSchemaPath, uploadName } from '../shared.js'
import './index.scss'
import { UploadDrawer } from './UploadDrawer/index.js' import { UploadDrawer } from './UploadDrawer/index.js'
import './index.scss'
const baseClass = 'rich-text-upload' const baseClass = 'rich-text-upload'
@@ -110,13 +111,13 @@ const UploadElementComponent: React.FC<{ enabledCollectionSlugs?: string[] }> =
[editor, element, setParams, cacheBust, closeDrawer], [editor, element, setParams, cacheBust, closeDrawer],
) )
const swapUpload = React.useCallback( const swapUpload = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
({ collectionSlug, docID }) => { ({ collectionSlug, doc }) => {
const newNode = { const newNode = {
type: uploadName, type: uploadName,
children: [{ text: ' ' }], children: [{ text: ' ' }],
relationTo: collectionSlug, relationTo: collectionSlug,
value: { id: docID }, value: { id: doc.id },
} }
const elementPath = ReactEditor.findPath(editor, element) const elementPath = ReactEditor.findPath(editor, element)

View File

@@ -111,6 +111,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
[ [
serverFunction, serverFunction,
closeModal, closeModal,
allowCreate,
drawerSlug, drawerSlug,
isOpen, isOpen,
enableRowSelections, enableRowSelections,
@@ -130,6 +131,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
if (typeof onSelect === 'function') { if (typeof onSelect === 'function') {
onSelect({ onSelect({
collectionSlug: selectedOption.value, collectionSlug: selectedOption.value,
doc,
docID: doc.id, docID: doc.id,
}) })
} }

View File

@@ -1,4 +1,4 @@
import type { CollectionSlug, ListQuery } from 'payload' import type { CollectionSlug, Data, ListQuery } from 'payload'
import { createContext, useContext } from 'react' import { createContext, useContext } from 'react'
@@ -14,7 +14,16 @@ export type ListDrawerContextProps = {
readonly enabledCollections?: CollectionSlug[] readonly enabledCollections?: CollectionSlug[]
readonly onBulkSelect?: (selected: ReturnType<typeof useSelection>['selected']) => void readonly onBulkSelect?: (selected: ReturnType<typeof useSelection>['selected']) => void
readonly onQueryChange?: (query: ListQuery) => void readonly onQueryChange?: (query: ListQuery) => void
readonly onSelect?: (args: { collectionSlug: CollectionSlug; docID: string }) => void readonly onSelect?: (args: {
collectionSlug: CollectionSlug
doc: Data
/**
* @deprecated
* The `docID` property is deprecated and will be removed in the next major version of Payload.
* Use `doc.id` instead.
*/
docID: string
}) => void
readonly selectedOption?: Option<string> readonly selectedOption?: Option<string>
readonly setSelectedOption?: (option: Option<string>) => void readonly setSelectedOption?: (option: Option<string>) => void
} }

View File

@@ -36,6 +36,7 @@ export const RenderDefaultCell: React.FC<{
if (typeof onSelect === 'function') { if (typeof onSelect === 'function') {
onSelect({ onSelect({
collectionSlug: rowColl, collectionSlug: rowColl,
doc: rowData,
docID: rowData.id as string, docID: rowData.id as string,
}) })
} }

View File

@@ -350,9 +350,9 @@ export function UploadInput(props: UploadInputProps) {
[closeCreateDocDrawer, activeRelationTo, onChange], [closeCreateDocDrawer, activeRelationTo, onChange],
) )
const onListSelect = React.useCallback<NonNullable<ListDrawerProps['onSelect']>>( const onListSelect = useCallback<NonNullable<ListDrawerProps['onSelect']>>(
async ({ collectionSlug, docID }) => { async ({ collectionSlug, doc }) => {
const loadedDocs = await populateDocs([docID], collectionSlug) const loadedDocs = await populateDocs([doc.id], collectionSlug)
const selectedDoc = loadedDocs ? loadedDocs.docs?.[0] : null const selectedDoc = loadedDocs ? loadedDocs.docs?.[0] : null
setPopulatedDocs((currentDocs) => { setPopulatedDocs((currentDocs) => {
if (selectedDoc) { if (selectedDoc) {
@@ -375,9 +375,9 @@ export function UploadInput(props: UploadInputProps) {
return currentDocs return currentDocs
}) })
if (hasMany) { if (hasMany) {
onChange([...(Array.isArray(value) ? value : []), docID]) onChange([...(Array.isArray(value) ? value : []), doc.id])
} else { } else {
onChange(docID) onChange(doc.id)
} }
closeListDrawer() closeListDrawer()
}, },