chore: properly types cell components (#5474)

This commit is contained in:
Jacob Fletcher
2024-03-26 12:08:33 -04:00
committed by GitHub
parent 9c7e7ed8d4
commit 20b4585666
24 changed files with 93 additions and 67 deletions

View File

@@ -36,5 +36,9 @@ export const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
if (globalSlug) to = `${admin}/globals/${globalSlug}/versions/${versionID}` if (globalSlug) to = `${admin}/globals/${globalSlug}/versions/${versionID}`
return <Link href={to}>{cellData && formatDate(cellData, dateFormat, i18n.language)}</Link> return (
<Link href={to}>
{cellData && formatDate(cellData as Date | number | string, dateFormat, i18n.language)}
</Link>
)
} }

View File

@@ -4,5 +4,5 @@ import React, { Fragment } from 'react'
export const IDCell: React.FC = () => { export const IDCell: React.FC = () => {
const { cellData } = useTableCell() const { cellData } = useTableCell()
return <Fragment>{cellData}</Fragment> return <Fragment>{cellData as number | string}</Fragment>
} }

View File

@@ -9,7 +9,9 @@ import type {
SelectField, SelectField,
} from '../../fields/config/types.js' } from '../../fields/config/types.js'
export type CellProps = { export type RowData = Record<string, any>
export type CellComponentProps = {
/** /**
* A custom component to override the default cell component. If this is not set, the React component will be * A custom component to override the default cell component. If this is not set, the React component will be
* taken from cellComponents based on the field type. * taken from cellComponents based on the field type.
@@ -32,19 +34,18 @@ export type CellProps = {
onClick?: (args: { onClick?: (args: {
cellData: unknown cellData: unknown
collectionSlug: SanitizedCollectionConfig['slug'] collectionSlug: SanitizedCollectionConfig['slug']
rowData: Record<string, unknown> rowData: RowData
}) => void }) => void
options?: SelectField['options'] options?: SelectField['options']
relationTo?: RelationshipField['relationTo'] relationTo?: RelationshipField['relationTo']
richTextComponentMap?: Map<string, React.ReactNode> // any should be MappedField richTextComponentMap?: Map<string, React.ReactNode> // any should be MappedField
} }
export type CellComponentProps<Data = unknown> = { export type DefaultCellComponentProps<T = any> = CellComponentProps & {
cellData: Data cellData: T
customCellContext?: { customCellContext?: {
collectionSlug?: SanitizedCollectionConfig['slug'] collectionSlug?: SanitizedCollectionConfig['slug']
uploadConfig?: SanitizedCollectionConfig['upload'] uploadConfig?: SanitizedCollectionConfig['upload']
} }
richTextComponentMap?: Map<string, React.ReactNode> rowData: RowData
rowData?: Record<string, unknown>
} }

View File

@@ -1,5 +1,5 @@
export type { RichTextAdapter, RichTextFieldProps } from './RichText.js' export type { RichTextAdapter, RichTextFieldProps } from './RichText.js'
export type { CellComponentProps, CellProps } from './elements/Cell.js' export type { CellComponentProps, DefaultCellComponentProps } from './elements/Cell.js'
export type { ConditionalDateProps } from './elements/DatePicker.js' export type { ConditionalDateProps } from './elements/DatePicker.js'
export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js' export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js'
export type { DefaultPreviewButtonProps } from './elements/PreviewButton.js' export type { DefaultPreviewButtonProps } from './elements/PreviewButton.js'

View File

@@ -1,9 +1,9 @@
'use client' 'use client'
import type { CellComponentProps } from 'payload/types' import type { DefaultCellComponentProps } from 'payload/types'
import React from 'react' import React from 'react'
export const RichTextCell: React.FC<CellComponentProps<any[]>> = ({ cellData }) => { export const RichTextCell: React.FC<DefaultCellComponentProps<any[]>> = ({ cellData }) => {
const flattenedText = cellData?.map((i) => i?.children?.map((c) => c.text)).join(' ') const flattenedText = cellData?.map((i) => i?.children?.map((c) => c.text)).join(' ')
// Limiting the number of characters shown is done in a CSS rule // Limiting the number of characters shown is done in a CSS rule

View File

@@ -1,12 +1,12 @@
'use client' 'use client'
import type { CellComponentProps, CellProps } from 'payload/types' import type { CellComponentProps, DefaultCellComponentProps } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { useTranslation } from '@payloadcms/ui/providers/Translation' import { useTranslation } from '@payloadcms/ui/providers/Translation'
import React from 'react' import React from 'react'
export interface ArrayCellProps extends CellComponentProps<Record<string, unknown>[]> { export interface ArrayCellProps extends DefaultCellComponentProps<Record<string, unknown>[]> {
labels: CellProps['labels'] labels: CellComponentProps['labels']
} }
export const ArrayCell: React.FC<ArrayCellProps> = ({ cellData, labels }) => { export const ArrayCell: React.FC<ArrayCellProps> = ({ cellData, labels }) => {

View File

@@ -1,13 +1,13 @@
'use client' 'use client'
import type { CellComponentProps, CellProps } from 'payload/types' import type { CellComponentProps, DefaultCellComponentProps } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { useTranslation } from '@payloadcms/ui/providers/Translation' import { useTranslation } from '@payloadcms/ui/providers/Translation'
import React from 'react' import React from 'react'
export interface BlocksCellProps extends CellComponentProps<any> { export interface BlocksCellProps extends DefaultCellComponentProps<any> {
blocks: CellProps['blocks'] blocks: CellComponentProps['blocks']
labels: CellProps['labels'] labels: CellComponentProps['labels']
} }
export const BlocksCell: React.FC<BlocksCellProps> = ({ blocks, cellData, labels }) => { export const BlocksCell: React.FC<BlocksCellProps> = ({ blocks, cellData, labels }) => {

View File

@@ -1,11 +1,11 @@
'use client' 'use client'
import type { CellComponentProps } from 'payload/types' import type { DefaultCellComponentProps } from 'payload/types'
import React from 'react' import React from 'react'
import './index.scss' import './index.scss'
export const CheckboxCell: React.FC<CellComponentProps<boolean>> = ({ cellData }) => ( export const CheckboxCell: React.FC<DefaultCellComponentProps<boolean>> = ({ cellData }) => (
<code className="bool-cell"> <code className="bool-cell">
<span>{JSON.stringify(cellData)}</span> <span>{JSON.stringify(cellData)}</span>
</code> </code>

View File

@@ -1,10 +1,10 @@
import type { CellComponentProps } from 'payload/types' import type { DefaultCellComponentProps } from 'payload/types'
import React from 'react' import React from 'react'
import './index.scss' import './index.scss'
export interface CodeCellProps extends CellComponentProps<string> { export interface CodeCellProps extends DefaultCellComponentProps<string> {
nowrap?: boolean nowrap?: boolean
} }

View File

@@ -1,16 +1,15 @@
'use client' 'use client'
import type { CellComponentProps, CellProps } from 'payload/types' import type { DefaultCellComponentProps } from 'payload/types'
import { useConfig } from '@payloadcms/ui/providers/Config' import { useConfig } from '@payloadcms/ui/providers/Config'
import { useTranslation } from '@payloadcms/ui/providers/Translation' import { useTranslation } from '@payloadcms/ui/providers/Translation'
import { formatDate } from '@payloadcms/ui/utilities/formatDate' import { formatDate } from '@payloadcms/ui/utilities/formatDate'
import React from 'react' import React from 'react'
export interface DateCellProps extends CellComponentProps<string> { export const DateCell: React.FC<DefaultCellComponentProps<Date | number | string>> = ({
dateDisplayFormat?: CellProps['dateDisplayFormat'] cellData,
} dateDisplayFormat,
}) => {
export const DateCell: React.FC<DateCellProps> = ({ cellData, dateDisplayFormat }) => {
const { const {
admin: { dateFormat: dateFormatFromConfig }, admin: { dateFormat: dateFormatFromConfig },
} = useConfig() } = useConfig()

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { CellComponentProps } from 'payload/types' import type { DefaultCellComponentProps } from 'payload/types'
import { Thumbnail } from '@payloadcms/ui/elements/Thumbnail' import { Thumbnail } from '@payloadcms/ui/elements/Thumbnail'
import React from 'react' import React from 'react'
@@ -8,7 +8,7 @@ import './index.scss'
const baseClass = 'file' const baseClass = 'file'
export interface FileCellProps extends CellComponentProps<any> {} export interface FileCellProps extends DefaultCellComponentProps<any> {}
export const FileCell: React.FC<FileCellProps> = ({ cellData, customCellContext, rowData }) => { export const FileCell: React.FC<FileCellProps> = ({ cellData, customCellContext, rowData }) => {
const { collectionSlug, uploadConfig } = customCellContext const { collectionSlug, uploadConfig } = customCellContext

View File

@@ -1,11 +1,11 @@
'use client' 'use client'
import type { CellComponentProps } from 'payload/types' import type { DefaultCellComponentProps } from 'payload/types'
import React from 'react' import React from 'react'
import './index.scss' import './index.scss'
export const JSONCell: React.FC<CellComponentProps<string>> = ({ cellData }) => { export const JSONCell: React.FC<DefaultCellComponentProps<string>> = ({ cellData }) => {
const textToShow = cellData.length > 100 ? `${cellData.substring(0, 100)}\u2026` : cellData const textToShow = cellData.length > 100 ? `${cellData.substring(0, 100)}\u2026` : cellData
return ( return (

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { CellComponentProps, CellProps } from 'payload/types' import type { CellComponentProps, DefaultCellComponentProps } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { useIntersect } from '@payloadcms/ui/hooks/useIntersect' import { useIntersect } from '@payloadcms/ui/hooks/useIntersect'
@@ -16,9 +16,9 @@ type Value = { relationTo: string; value: number | string }
const baseClass = 'relationship-cell' const baseClass = 'relationship-cell'
const totalToShow = 3 const totalToShow = 3
export interface RelationshipCellProps extends CellComponentProps<any> { export interface RelationshipCellProps extends DefaultCellComponentProps<any> {
label: CellProps['label'] label: CellComponentProps['label']
relationTo: CellProps['relationTo'] relationTo: CellComponentProps['relationTo']
} }
export const RelationshipCell: React.FC<RelationshipCellProps> = ({ export const RelationshipCell: React.FC<RelationshipCellProps> = ({

View File

@@ -1,13 +1,13 @@
'use client' 'use client'
import type { CellComponentProps, CellProps, OptionObject } from 'payload/types' import type { CellComponentProps, DefaultCellComponentProps, OptionObject } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { useTranslation } from '@payloadcms/ui/providers/Translation' import { useTranslation } from '@payloadcms/ui/providers/Translation'
import { optionsAreObjects } from 'payload/types' import { optionsAreObjects } from 'payload/types'
import React from 'react' import React from 'react'
export interface SelectCellProps extends CellComponentProps<any> { export interface SelectCellProps extends DefaultCellComponentProps<any> {
options: CellProps['options'] options: CellComponentProps['options']
} }
export const SelectCell: React.FC<SelectCellProps> = ({ cellData, options }) => { export const SelectCell: React.FC<SelectCellProps> = ({ cellData, options }) => {

View File

@@ -1,9 +1,9 @@
'use client' 'use client'
import type { CellComponentProps } from 'payload/types' import type { DefaultCellComponentProps } from 'payload/types'
import React from 'react' import React from 'react'
export const TextareaCell: React.FC<CellComponentProps<string>> = ({ cellData }) => { export const TextareaCell: React.FC<DefaultCellComponentProps<string>> = ({ cellData }) => {
const textToShow = cellData?.length > 100 ? `${cellData.substr(0, 100)}\u2026` : cellData const textToShow = cellData?.length > 100 ? `${cellData.substr(0, 100)}\u2026` : cellData
return <span>{textToShow}</span> return <span>{textToShow}</span>
} }

View File

@@ -2,7 +2,7 @@
import LinkImport from 'next/link.js' import LinkImport from 'next/link.js'
import React from 'react' // TODO: abstract this out to support all routers import React from 'react' // TODO: abstract this out to support all routers
import type { CellProps } from 'payload/types' import type { CellComponentProps, DefaultCellComponentProps } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { TableCellProvider, useTableCell } from '@payloadcms/ui/elements/Table' import { TableCellProvider, useTableCell } from '@payloadcms/ui/elements/Table'
@@ -14,7 +14,7 @@ import { cellComponents } from './fields/index.js'
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
export const DefaultCell: React.FC<CellProps> = (props) => { export const DefaultCell: React.FC<CellComponentProps> = (props) => {
const { const {
name, name,
CellComponentOverride, CellComponentOverride,
@@ -77,12 +77,12 @@ export const DefaultCell: React.FC<CellProps> = (props) => {
if (name === 'id') { if (name === 'id') {
return ( return (
<WrapElement {...wrapElementProps}> <WrapElement {...wrapElementProps}>
<CodeCell cellData={`ID: ${cellData}`} nowrap /> <CodeCell cellData={`ID: ${cellData}`} name={name} nowrap rowData={rowData} />
</WrapElement> </WrapElement>
) )
} }
const DefaultCellComponent = cellComponents[fieldType] const DefaultCellComponent: React.FC<DefaultCellComponentProps> = cellComponents[fieldType]
let CellComponent: React.ReactNode = let CellComponent: React.ReactNode =
cellData && cellData &&

View File

@@ -1,27 +1,27 @@
'use client' 'use client'
import type { CellComponentProps, CellProps } from 'payload/types' import type { CellComponentProps, DefaultCellComponentProps } from 'payload/types'
import React from 'react' import React from 'react'
export type ITableCellContext = { export type ITableCellContext = {
cellData: any cellData: DefaultCellComponentProps['cellData']
cellProps?: Partial<CellProps> cellProps?: Partial<CellComponentProps>
columnIndex?: number columnIndex?: number
customCellContext: CellComponentProps['customCellContext'] customCellContext: DefaultCellComponentProps['customCellContext']
richTextComponentMap?: CellComponentProps['richTextComponentMap'] richTextComponentMap?: DefaultCellComponentProps['richTextComponentMap']
rowData: any rowData: DefaultCellComponentProps['rowData']
} }
const TableCellContext = React.createContext<ITableCellContext>({} as ITableCellContext) const TableCellContext = React.createContext<ITableCellContext>({} as ITableCellContext)
export const TableCellProvider: React.FC<{ export const TableCellProvider: React.FC<{
cellData?: any cellData?: DefaultCellComponentProps['cellData']
cellProps?: Partial<CellProps> cellProps?: Partial<CellComponentProps>
children: React.ReactNode children: React.ReactNode
columnIndex?: number columnIndex?: number
customCellContext?: CellComponentProps['customCellContext'] customCellContext?: DefaultCellComponentProps['customCellContext']
richTextComponentMap?: CellComponentProps['richTextComponentMap'] richTextComponentMap?: DefaultCellComponentProps['richTextComponentMap']
rowData?: any rowData?: DefaultCellComponentProps['rowData']
}> = (props) => { }> = (props) => {
const { const {
cellData, cellData,

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { CellProps, FieldBase } from 'payload/types' import type { CellComponentProps, FieldBase } from 'payload/types'
import React from 'react' import React from 'react'
@@ -11,12 +11,14 @@ import { useTableColumns } from '../TableColumns/index.js'
import { TableCellProvider } from './TableCellProvider/index.js' import { TableCellProvider } from './TableCellProvider/index.js'
import './index.scss' import './index.scss'
export { TableCellProvider }
const baseClass = 'table' const baseClass = 'table'
export type Column = { export type Column = {
accessor: string accessor: string
active: boolean active: boolean
cellProps?: Partial<CellProps> cellProps?: Partial<CellComponentProps>
components: { components: {
Cell: React.ReactNode Cell: React.ReactNode
Heading: React.ReactNode Heading: React.ReactNode
@@ -28,7 +30,7 @@ export type Column = {
export type Props = { export type Props = {
columns?: Column[] columns?: Column[]
customCellContext?: Record<string, unknown> customCellContext?: Record<string, unknown>
data: unknown[] data: Record<string, unknown>[]
fieldMap: FieldMap fieldMap: FieldMap
} }

View File

@@ -1,5 +1,5 @@
import { FieldLabel } from '@payloadcms/ui/forms/FieldLabel' import { FieldLabel } from '@payloadcms/ui/forms/FieldLabel'
import { type CellProps, type SanitizedCollectionConfig } from 'payload/types' import { type CellComponentProps, type SanitizedCollectionConfig } from 'payload/types'
import React from 'react' import React from 'react'
import type { FieldMap, MappedField } from '../../providers/ComponentMap/buildComponentMap/types.js' import type { FieldMap, MappedField } from '../../providers/ComponentMap/buildComponentMap/types.js'
@@ -15,7 +15,7 @@ import { DefaultCell } from '../Table/DefaultCell/index.js'
const fieldIsPresentationalOnly = (field: MappedField): boolean => field.type === 'ui' const fieldIsPresentationalOnly = (field: MappedField): boolean => field.type === 'ui'
export const buildColumns = (args: { export const buildColumns = (args: {
cellProps: Partial<CellProps>[] cellProps: Partial<CellComponentProps>[]
columnPreferences: ColumnPreferences columnPreferences: ColumnPreferences
defaultColumns?: string[] defaultColumns?: string[]
enableRowSelections: boolean enableRowSelections: boolean

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import type { SanitizedCollectionConfig } from 'payload/types' import type { SanitizedCollectionConfig } from 'payload/types'
import type { CellProps } from 'payload/types' import type { CellComponentProps } from 'payload/types'
import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef } from 'react' import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef } from 'react'
@@ -31,7 +31,7 @@ export type ListPreferences = {
} }
type Props = { type Props = {
cellProps?: Partial<CellProps>[] cellProps?: Partial<CellComponentProps>[]
children: React.ReactNode children: React.ReactNode
collectionSlug: string collectionSlug: string
enableRowSelections?: boolean enableRowSelections?: boolean

View File

@@ -1,6 +1,6 @@
import type { FieldDescriptionProps } from '@payloadcms/ui/forms/FieldDescription' import type { FieldDescriptionProps } from '@payloadcms/ui/forms/FieldDescription'
import type { import type {
CellProps, CellComponentProps,
Field, Field,
FieldWithPath, FieldWithPath,
LabelProps, LabelProps,
@@ -189,7 +189,7 @@ export const mapFields = (args: {
let fieldComponentProps: FieldComponentProps let fieldComponentProps: FieldComponentProps
const cellComponentProps: CellProps = { const cellComponentProps: CellComponentProps = {
name: 'name' in field ? field.name : undefined, name: 'name' in field ? field.name : undefined,
fieldType: field.type, fieldType: field.type,
isFieldAffectingData, isFieldAffectingData,

View File

@@ -2,7 +2,7 @@ import type { HiddenInputFieldProps } from '@payloadcms/ui/fields/HiddenInput'
import type { FieldTypes } from 'payload/config' import type { FieldTypes } from 'payload/config'
import type { import type {
BlockField, BlockField,
CellProps, CellComponentProps,
SanitizedCollectionConfig, SanitizedCollectionConfig,
SanitizedGlobalConfig, SanitizedGlobalConfig,
TabsField, TabsField,
@@ -67,7 +67,7 @@ export type FieldComponentProps =
export type MappedField = { export type MappedField = {
CustomCell?: React.ReactNode CustomCell?: React.ReactNode
CustomField?: React.ReactNode CustomField?: React.ReactNode
cellComponentProps: CellProps cellComponentProps: CellComponentProps
disableBulkEdit?: boolean disableBulkEdit?: boolean
disabled?: boolean disabled?: boolean
fieldComponentProps: FieldComponentProps fieldComponentProps: FieldComponentProps

View File

@@ -2,6 +2,7 @@ import type { CollectionConfig } from 'payload/types'
import { slateEditor } from '@payloadcms/richtext-slate' import { slateEditor } from '@payloadcms/richtext-slate'
import { CustomCell } from '../components/CustomCell/index.js'
import { DemoUIFieldCell } from '../components/DemoUIField/Cell.js' import { DemoUIFieldCell } from '../components/DemoUIField/Cell.js'
import { DemoUIField } from '../components/DemoUIField/Field.js' import { DemoUIField } from '../components/DemoUIField/Field.js'
import { import {
@@ -82,6 +83,15 @@ export const Posts: CollectionConfig = {
}, },
relationTo: 'posts', relationTo: 'posts',
}, },
{
name: 'customCell',
type: 'text',
admin: {
components: {
Cell: CustomCell,
},
},
},
{ {
name: 'sidebarField', name: 'sidebarField',
type: 'text', type: 'text',

View File

@@ -0,0 +1,10 @@
'use client'
import type { CellComponentProps } from 'payload/types'
import { useTableCell } from '@payloadcms/ui/elements/Table'
import React from 'react'
export const CustomCell: React.FC<CellComponentProps> = (props) => {
const context = useTableCell()
return <div>{`Custom cell: ${context.cellData || 'No data'}`}</div>
}