chore(ui): server renders custom list view
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ dist
|
||||
test-results
|
||||
.devcontainer
|
||||
/migrations
|
||||
/media
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
// import { CollectionList } from '@payloadcms/next/pages/CollectionList'
|
||||
import { Document } from '@payloadcms/next/pages/Document'
|
||||
import config from 'payload-config'
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { CollectionList } from '@payloadcms/next/pages/CollectionList'
|
||||
import { ListView } from '@payloadcms/next/pages/List'
|
||||
import config from 'payload-config'
|
||||
|
||||
export default ({ params, searchParams }) =>
|
||||
CollectionList({
|
||||
ListView({
|
||||
collectionSlug: params.collection,
|
||||
searchParams,
|
||||
config,
|
||||
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
Form,
|
||||
Select,
|
||||
Number as NumberInput,
|
||||
EditViewProps,
|
||||
useConfig,
|
||||
MinimizeMaximize,
|
||||
useActions,
|
||||
useTranslation,
|
||||
useLocale,
|
||||
ServerSideEditViewProps,
|
||||
} from '@payloadcms/ui'
|
||||
import { RenderJSON } from './RenderJSON'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
@@ -24,7 +24,7 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'query-inspector'
|
||||
|
||||
export const APIViewClient: React.FC<EditViewProps> = (props) => {
|
||||
export const APIViewClient: React.FC<ServerSideEditViewProps> = (props) => {
|
||||
const { data: initialData } = props
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
@@ -133,8 +133,8 @@ export const APIViewClient: React.FC<EditViewProps> = (props) => {
|
||||
>
|
||||
<SetStepNav
|
||||
collectionSlug={collectionSlug}
|
||||
useAsTitle={collectionConfig?.admin?.useAsTitle}
|
||||
pluralLabel={collectionConfig?.labels.plural}
|
||||
useAsTitle={collectionConfig ? collectionConfig?.admin?.useAsTitle : undefined}
|
||||
pluralLabel={collectionConfig ? collectionConfig?.labels?.plural : undefined}
|
||||
globalLabel={globalConfig?.label}
|
||||
globalSlug={globalSlug}
|
||||
id={id}
|
||||
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
import { initPage } from '../../utilities/initPage'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { ListPreferences } from '../../../../ui/src/views/List/types'
|
||||
import { ListInfoProvider } from '../../../../ui/src/providers/ListInfo'
|
||||
|
||||
export const CollectionList = async ({
|
||||
export const ListView = async ({
|
||||
collectionSlug,
|
||||
config: configPromise,
|
||||
searchParams,
|
||||
@@ -74,23 +75,27 @@ export const CollectionList = async ({
|
||||
})
|
||||
|
||||
const componentProps: DefaultListViewProps = {
|
||||
data,
|
||||
hasCreatePermission: permissions?.collections?.[collectionSlug]?.create?.permission,
|
||||
limit,
|
||||
newDocumentURL: `${admin}/collections/${collectionSlug}/create`,
|
||||
collectionSlug,
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser user={user} permissions={permissions} />
|
||||
<TableColumnsProvider collectionSlug={collectionSlug} listPreferences={listPreferences}>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={ListToRender}
|
||||
DefaultComponent={DefaultList}
|
||||
componentProps={componentProps}
|
||||
/>
|
||||
</TableColumnsProvider>
|
||||
<ListInfoProvider
|
||||
data={data}
|
||||
hasCreatePermission={permissions?.collections?.[collectionSlug]?.create?.permission}
|
||||
limit={limit}
|
||||
newDocumentURL={`${admin}/collections/${collectionSlug}/create`}
|
||||
collectionSlug={collectionSlug}
|
||||
>
|
||||
<TableColumnsProvider collectionSlug={collectionSlug} listPreferences={listPreferences}>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={ListToRender}
|
||||
DefaultComponent={DefaultList}
|
||||
componentProps={componentProps}
|
||||
/>
|
||||
</TableColumnsProvider>
|
||||
</ListInfoProvider>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
// (!isEditing && (docPermissions as CollectionPermission)?.create?.permission)
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasInitializedState.current && data) {
|
||||
if (!hasInitializedState.current && (!initialID.current || (initialID.current && data))) {
|
||||
const getInitialState = async () => {
|
||||
const result = await getFormState({
|
||||
serverURL,
|
||||
@@ -106,7 +106,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
body: {
|
||||
id,
|
||||
operation: isEditing ? 'update' : 'create',
|
||||
data,
|
||||
data: data || {},
|
||||
docPreferences: null, // TODO: get this
|
||||
schemaPath,
|
||||
},
|
||||
@@ -119,9 +119,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
}
|
||||
}, [apiRoute, data, id, isEditing, schemaPath, serverURL])
|
||||
|
||||
const isLoading = !initialState || isLoadingDocument
|
||||
|
||||
if (isLoading) {
|
||||
if (!initialState || isLoadingDocument) {
|
||||
return <LoadingOverlay />
|
||||
}
|
||||
|
||||
@@ -155,7 +153,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
}
|
||||
initialData={data}
|
||||
disableActions
|
||||
// disableLeaveWithoutSaving
|
||||
disableLeaveWithoutSaving
|
||||
// hasSavePermission={hasSavePermission}
|
||||
// isEditing={isEditing}
|
||||
// isLoading,
|
||||
@@ -164,7 +162,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
collectionSlug={collectionConfig.slug}
|
||||
docPermissions={{} as CollectionPermission} // TODO; get this
|
||||
docPreferences={null} // TODO: get this
|
||||
initialState
|
||||
initialState={initialState}
|
||||
>
|
||||
{Edit}
|
||||
</DocumentInfoProvider>
|
||||
@@ -178,10 +176,10 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = (props) => {
|
||||
const { id: idFromProps, collectionSlug, onSave: onSaveFromProps } = props
|
||||
const [collectionConfig] = useRelatedCollections(collectionSlug)
|
||||
const [id, setId] = useState<null | string>(idFromProps)
|
||||
const [id, setId] = useState<null | string | number>(idFromProps)
|
||||
|
||||
const onSave = useCallback<DocumentDrawerProps['onSave']>(
|
||||
(args) => {
|
||||
async (args) => {
|
||||
setId(args.doc.id)
|
||||
|
||||
if (typeof onSaveFromProps === 'function') {
|
||||
|
||||
@@ -23,7 +23,7 @@ const formatDocumentDrawerSlug = ({
|
||||
}: {
|
||||
collectionSlug: string
|
||||
depth: number
|
||||
id: string
|
||||
id: string | number
|
||||
uuid: string // supply when creating a new document and no id is available
|
||||
}) => `doc-drawer_${collectionSlug}_${depth}${id ? `_${id}` : ''}_${uuid}`
|
||||
|
||||
@@ -69,6 +69,7 @@ export const useDocumentDrawer: UseDocumentDrawer = ({ id, collectionSlug }) =>
|
||||
const uuid = useId()
|
||||
const { closeModal, modalState, openModal, toggleModal } = useModal()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const drawerSlug = formatDocumentDrawerSlug({
|
||||
id,
|
||||
collectionSlug,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type React from 'react'
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
import type { EditViewProps } from '../../views/types'
|
||||
import type { Props as DrawerProps } from '../Drawer/types'
|
||||
import { DocumentInfoContext } from '../../providers/DocumentInfo/types'
|
||||
|
||||
export type DocumentDrawerProps = Pick<DrawerProps, 'Header'> & {
|
||||
collectionSlug: string
|
||||
drawerSlug?: string
|
||||
id?: string
|
||||
onSave?: EditViewProps['onSave']
|
||||
id?: string | number
|
||||
onSave?: DocumentInfoContext['onSave']
|
||||
}
|
||||
|
||||
export type DocumentTogglerProps = HTMLAttributes<HTMLButtonElement> & {
|
||||
|
||||
@@ -14,17 +14,15 @@ import Label from '../../forms/Label'
|
||||
import { X } from '../../icons/X'
|
||||
import { useAuth } from '../../providers/Auth'
|
||||
import { useConfig } from '../../providers/Config'
|
||||
import { DocumentInfoProvider } from '../../providers/DocumentInfo'
|
||||
import { usePreferences } from '../../providers/Preferences'
|
||||
import { RenderCustomComponent } from '../RenderCustomComponent'
|
||||
import { DefaultList } from '../../views/List'
|
||||
// import formatFields from '../../views/List/formatFields'
|
||||
import { useDocumentDrawer } from '../DocumentDrawer'
|
||||
import Pill from '../Pill'
|
||||
import ReactSelect from '../ReactSelect'
|
||||
import { TableColumnsProvider } from '../TableColumns'
|
||||
import ViewDescription from '../ViewDescription'
|
||||
import { DefaultListViewProps } from '../../views/List/types'
|
||||
import { useComponentMap } from '../..'
|
||||
import { ListInfoProvider } from '../../providers/ListInfo'
|
||||
import { LoadingOverlay } from '../Loading'
|
||||
|
||||
const hoistQueryParamsToAnd = (where: Where, queryParams: Where) => {
|
||||
if ('and' in where) {
|
||||
@@ -55,10 +53,12 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
const { setPreference } = usePreferences()
|
||||
const { closeModal, isModalOpen } = useModal()
|
||||
const [limit, setLimit] = useState<number>()
|
||||
const [sort, setSort] = useState(null)
|
||||
const [page, setPage] = useState(1)
|
||||
const [where, setWhere] = useState(null)
|
||||
const [search, setSearch] = useState('')
|
||||
const [sort, setSort] = useState<string>(null)
|
||||
const [page, setPage] = useState<number>(1)
|
||||
const [where, setWhere] = useState<Where>(null)
|
||||
const [search, setSearch] = useState<string>('')
|
||||
|
||||
const { componentMap } = useComponentMap()
|
||||
|
||||
const {
|
||||
collections,
|
||||
@@ -78,6 +78,8 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
)
|
||||
})
|
||||
|
||||
const { List } = componentMap.collections?.[selectedCollectionConfig?.slug] || {}
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState<{ label: string; value: string }>(() =>
|
||||
selectedCollectionConfig
|
||||
? {
|
||||
@@ -129,7 +131,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
const isOpen = isModalOpen(drawerSlug)
|
||||
const apiURL = isOpen ? `${serverURL}${api}/${selectedCollectionConfig.slug}` : null
|
||||
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0) // used to force a re-fetch even when apiURL is unchanged
|
||||
const [{ data, isError }, { setParams }] = usePayloadAPI(apiURL, {})
|
||||
const [{ data, isError, isLoading: isLoadingList }, { setParams }] = usePayloadAPI(apiURL, {})
|
||||
const moreThanOneAvailableCollection = enabledCollectionConfigs.length > 1
|
||||
|
||||
useEffect(() => {
|
||||
@@ -215,104 +217,93 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
return null
|
||||
}
|
||||
|
||||
const listComponent = selectedCollectionConfig?.admin?.components?.views?.List
|
||||
let ListToRender = null
|
||||
|
||||
if (listComponent && typeof listComponent === 'function') {
|
||||
ListToRender = listComponent
|
||||
} else if (typeof listComponent === 'object' && typeof listComponent.Component === 'function') {
|
||||
ListToRender = listComponent.Component
|
||||
}
|
||||
|
||||
const componentProps: DefaultListViewProps = {
|
||||
collectionSlug: selectedCollectionConfig.slug,
|
||||
Header: (
|
||||
<header className={`${baseClass}__header`}>
|
||||
<div className={`${baseClass}__header-wrap`}>
|
||||
<div className={`${baseClass}__header-content`}>
|
||||
<h2 className={`${baseClass}__header-text`}>
|
||||
{!customHeader
|
||||
? getTranslation(selectedCollectionConfig?.labels?.plural, i18n)
|
||||
: customHeader}
|
||||
</h2>
|
||||
{hasCreatePermission && (
|
||||
<DocumentDrawerToggler className={`${baseClass}__create-new-button`}>
|
||||
<Pill>{t('general:createNew')}</Pill>
|
||||
</DocumentDrawerToggler>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className={`${baseClass}__header-close`}
|
||||
onClick={() => {
|
||||
closeModal(drawerSlug)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
{selectedCollectionConfig?.admin?.description && (
|
||||
<div className={`${baseClass}__sub-header`}>
|
||||
<ViewDescription description={selectedCollectionConfig.admin.description} />
|
||||
</div>
|
||||
)}
|
||||
{moreThanOneAvailableCollection && (
|
||||
<div className={`${baseClass}__select-collection-wrap`}>
|
||||
<Label label={t('upload:selectCollectionToBrowse')} />
|
||||
<ReactSelect
|
||||
className={`${baseClass}__select-collection`}
|
||||
onChange={setSelectedOption} // this is only changing the options which is not rerunning my effect
|
||||
options={enabledCollectionConfigs.map((coll) => ({
|
||||
label: getTranslation(coll.labels.singular, i18n),
|
||||
value: coll.slug,
|
||||
}))}
|
||||
value={selectedOption}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
),
|
||||
data,
|
||||
handlePageChange: setPage,
|
||||
handlePerPageChange: setLimit,
|
||||
handleSearchChange: setSearch,
|
||||
handleSortChange: setSort,
|
||||
handleWhereChange: setWhere,
|
||||
hasCreatePermission,
|
||||
limit: limit || selectedCollectionConfig?.admin?.pagination?.defaultLimit,
|
||||
modifySearchParams: false,
|
||||
newDocumentURL: null,
|
||||
setLimit,
|
||||
setSort,
|
||||
titleField,
|
||||
if (isLoadingList) {
|
||||
return <LoadingOverlay />
|
||||
}
|
||||
|
||||
return (
|
||||
<TableColumnsProvider
|
||||
cellProps={[
|
||||
{
|
||||
className: `${baseClass}__first-cell`,
|
||||
link: false,
|
||||
onClick: ({ collectionSlug: rowColl, rowData }) => {
|
||||
if (typeof onSelect === 'function') {
|
||||
onSelect({
|
||||
collectionSlug: rowColl,
|
||||
docID: rowData.id as string,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
<ListInfoProvider
|
||||
collectionSlug={selectedCollectionConfig.slug}
|
||||
data={data}
|
||||
handlePageChange={setPage}
|
||||
handlePerPageChange={setLimit}
|
||||
handleSearchChange={setSearch}
|
||||
handleSortChange={setSort}
|
||||
handleWhereChange={setWhere}
|
||||
hasCreatePermission={hasCreatePermission}
|
||||
limit={limit || selectedCollectionConfig?.admin?.pagination?.defaultLimit}
|
||||
modifySearchParams={false}
|
||||
newDocumentURL={null}
|
||||
setLimit={setLimit}
|
||||
setSort={setSort}
|
||||
titleField={titleField}
|
||||
Header={
|
||||
<header className={`${baseClass}__header`}>
|
||||
<div className={`${baseClass}__header-wrap`}>
|
||||
<div className={`${baseClass}__header-content`}>
|
||||
<h2 className={`${baseClass}__header-text`}>
|
||||
{!customHeader
|
||||
? getTranslation(selectedCollectionConfig?.labels?.plural, i18n)
|
||||
: customHeader}
|
||||
</h2>
|
||||
{hasCreatePermission && (
|
||||
<DocumentDrawerToggler className={`${baseClass}__create-new-button`}>
|
||||
<Pill>{t('general:createNew')}</Pill>
|
||||
</DocumentDrawerToggler>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className={`${baseClass}__header-close`}
|
||||
onClick={() => {
|
||||
closeModal(drawerSlug)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
{selectedCollectionConfig?.admin?.description && (
|
||||
<div className={`${baseClass}__sub-header`}>
|
||||
<ViewDescription description={selectedCollectionConfig.admin.description} />
|
||||
</div>
|
||||
)}
|
||||
{moreThanOneAvailableCollection && (
|
||||
<div className={`${baseClass}__select-collection-wrap`}>
|
||||
<Label label={t('upload:selectCollectionToBrowse')} />
|
||||
<ReactSelect
|
||||
className={`${baseClass}__select-collection`}
|
||||
onChange={setSelectedOption} // this is only changing the options which is not rerunning my effect
|
||||
options={enabledCollectionConfigs.map((coll) => ({
|
||||
label: getTranslation(coll.labels.singular, i18n),
|
||||
value: coll.slug,
|
||||
}))}
|
||||
value={selectedOption}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
}
|
||||
>
|
||||
<DocumentInfoProvider collectionSlug={selectedCollectionConfig.slug}>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={ListToRender}
|
||||
DefaultComponent={DefaultList}
|
||||
componentProps={componentProps}
|
||||
/>
|
||||
</DocumentInfoProvider>
|
||||
<DocumentDrawer onSave={onCreateNew} />
|
||||
</TableColumnsProvider>
|
||||
<TableColumnsProvider
|
||||
cellProps={[
|
||||
{
|
||||
className: `${baseClass}__first-cell`,
|
||||
link: false,
|
||||
onClick: ({ collectionSlug: rowColl, rowData }) => {
|
||||
if (typeof onSelect === 'function') {
|
||||
onSelect({
|
||||
collectionSlug: rowColl,
|
||||
docID: rowData.id as string,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
collectionSlug={selectedCollectionConfig.slug}
|
||||
>
|
||||
{List}
|
||||
<DocumentDrawer onSave={onCreateNew} />
|
||||
</TableColumnsProvider>
|
||||
</ListInfoProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import type React from 'react'
|
||||
import type {
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedGlobalConfig,
|
||||
FormState,
|
||||
TypeWithTimestamps,
|
||||
TypeWithID,
|
||||
DocumentPermissions,
|
||||
@@ -12,6 +11,7 @@ import type {
|
||||
} from 'payload/types'
|
||||
|
||||
import { PaginatedDocs, TypeWithVersion } from 'payload/database'
|
||||
import { FormState } from '../../forms/Form/types'
|
||||
|
||||
export type DocumentInfoProps = {
|
||||
AfterDocument?: React.ReactNode
|
||||
@@ -24,12 +24,13 @@ export type DocumentInfoProps = {
|
||||
id?: number | string
|
||||
initialData?: Data
|
||||
initialState?: FormState
|
||||
onSave?: (data: Record<string, unknown>) => Promise<void>
|
||||
onSave?: (data: Data) => Promise<void> | void
|
||||
action?: string
|
||||
apiURL?: string
|
||||
docPreferences?: DocumentPreferences
|
||||
hasSavePermission?: boolean
|
||||
disableActions?: boolean
|
||||
disableLeaveWithoutSaving?: boolean
|
||||
}
|
||||
|
||||
export type DocumentInfo = DocumentInfoProps & {
|
||||
|
||||
15
packages/ui/src/providers/ListInfo/SetDocumentInfo/index.tsx
Normal file
15
packages/ui/src/providers/ListInfo/SetDocumentInfo/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useDocumentInfo } from '..'
|
||||
import { DocumentInfo } from '../types'
|
||||
|
||||
export const SetDocumentInfo: React.FC<DocumentInfo> = (props) => {
|
||||
const { setDocumentInfo } = useDocumentInfo()
|
||||
|
||||
useEffect(() => {
|
||||
setDocumentInfo(props)
|
||||
}, [props])
|
||||
|
||||
return null
|
||||
}
|
||||
32
packages/ui/src/providers/ListInfo/index.tsx
Normal file
32
packages/ui/src/providers/ListInfo/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
'use client'
|
||||
import React, { createContext, useContext, useState } from 'react'
|
||||
|
||||
import type { ListInfo, ListInfoContext, ListInfoProps } from './types'
|
||||
import { useConfig } from '../Config'
|
||||
|
||||
const Context = createContext({} as ListInfoContext)
|
||||
|
||||
export const useListInfo = (): ListInfoContext => useContext(Context)
|
||||
|
||||
export const ListInfoProvider: React.FC<
|
||||
ListInfoProps & {
|
||||
children: React.ReactNode
|
||||
}
|
||||
> = ({ children, ...rest }) => {
|
||||
const [listInfo, setListInfo] = useState<ListInfo>({
|
||||
...rest,
|
||||
})
|
||||
|
||||
const {
|
||||
routes: { api },
|
||||
serverURL,
|
||||
collections,
|
||||
globals,
|
||||
} = useConfig()
|
||||
|
||||
const value: ListInfoContext = {
|
||||
...listInfo,
|
||||
}
|
||||
|
||||
return <Context.Provider value={value}>{children}</Context.Provider>
|
||||
}
|
||||
30
packages/ui/src/providers/ListInfo/types.ts
Normal file
30
packages/ui/src/providers/ListInfo/types.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Data, FieldAffectingData, SanitizedCollectionConfig, Where } from 'payload/types'
|
||||
import type React from 'react'
|
||||
|
||||
export type ListInfoProps = {
|
||||
Header?: React.ReactNode
|
||||
collectionSlug: SanitizedCollectionConfig['slug']
|
||||
data: Data
|
||||
handlePageChange?: (page: number) => void
|
||||
handlePerPageChange?: (limit: number) => void
|
||||
handleSearchChange?: (search: string) => void
|
||||
handleSortChange?: (sort: string) => void
|
||||
handleWhereChange?: (where: Where) => void
|
||||
hasCreatePermission: boolean
|
||||
limit: number | SanitizedCollectionConfig['admin']['pagination']['defaultLimit']
|
||||
modifySearchParams?: false
|
||||
newDocumentURL: string
|
||||
setLimit?: (limit: number) => void
|
||||
setSort?: (sort: string) => void
|
||||
titleField?: FieldAffectingData
|
||||
}
|
||||
|
||||
export type ListInfo = ListInfoProps & {
|
||||
// add context properties here as needed
|
||||
// see `DocumentInfo` for an example
|
||||
}
|
||||
|
||||
export type ListInfoContext = ListInfo & {
|
||||
// add context methods here as needed
|
||||
// see `DocumentInfoContext` for an example
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import type { SanitizedConfig } from 'payload/types'
|
||||
import { mapFields } from './mapFields'
|
||||
import { CollectionComponentMap, ComponentMap, GlobalComponentMap } from './types'
|
||||
import { DefaultEditView } from '../../views/Edit'
|
||||
import { EditViewProps } from '../..'
|
||||
import { DefaultList } from '../../views/List'
|
||||
import { EditViewProps } from 'payload/config'
|
||||
|
||||
export const buildComponentMap = (args: {
|
||||
config: SanitizedConfig
|
||||
@@ -23,6 +24,7 @@ export const buildComponentMap = (args: {
|
||||
const { fields, slug } = collectionConfig
|
||||
|
||||
const editViewFromConfig = collectionConfig?.admin?.components?.views?.Edit
|
||||
const listViewFromConfig = collectionConfig?.admin?.components?.views?.List
|
||||
|
||||
const CustomEditView =
|
||||
typeof editViewFromConfig === 'function'
|
||||
@@ -35,7 +37,16 @@ export const buildComponentMap = (args: {
|
||||
? (editViewFromConfig.Default.Component as React.FC<EditViewProps>)
|
||||
: undefined
|
||||
|
||||
const CustomListView =
|
||||
typeof listViewFromConfig === 'function'
|
||||
? listViewFromConfig
|
||||
: typeof listViewFromConfig === 'object' &&
|
||||
typeof listViewFromConfig.Component === 'function'
|
||||
? listViewFromConfig.Component
|
||||
: undefined
|
||||
|
||||
const Edit = CustomEditView || DefaultEditView
|
||||
const List = CustomListView || DefaultList
|
||||
|
||||
const beforeList = collectionConfig?.admin?.components?.BeforeList
|
||||
|
||||
@@ -75,6 +86,7 @@ export const buildComponentMap = (args: {
|
||||
BeforeListTable,
|
||||
AfterListTable,
|
||||
Edit: <Edit collectionSlug={collectionConfig.slug} />,
|
||||
List: <List collectionSlug={collectionConfig.slug} />,
|
||||
fieldMap: mappedFields,
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ export type CollectionComponentMap = ConfigComponentMapBase & {
|
||||
AfterList: React.ReactNode
|
||||
BeforeListTable: React.ReactNode
|
||||
AfterListTable: React.ReactNode
|
||||
List: React.ReactNode
|
||||
}
|
||||
|
||||
export type GlobalComponentMap = ConfigComponentMapBase
|
||||
|
||||
@@ -128,7 +128,6 @@ export const Upload: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<div className={[fieldBaseClass, baseClass].filter(Boolean).join(' ')}>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
|
||||
{doc.filename && !replacingFile && (
|
||||
<FileDetails
|
||||
canEdit={showCrop || showFocalPoint}
|
||||
@@ -140,7 +139,6 @@ export const Upload: React.FC<Props> = (props) => {
|
||||
imageCacheTag={lastSubmittedTime}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(!doc.filename || replacingFile) && (
|
||||
<div className={`${baseClass}__upload`}>
|
||||
{!value && (
|
||||
@@ -184,7 +182,6 @@ export const Upload: React.FC<Props> = (props) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(value || doc.filename) && (
|
||||
<Drawer Header={null} slug={editDrawerSlug}>
|
||||
<EditUpload
|
||||
|
||||
@@ -28,7 +28,7 @@ const baseClass = 'collection-edit'
|
||||
// This component receives props only on _pages_
|
||||
// When rendered within a drawer, props are empty
|
||||
// This is solely to support custom edit views which get server-rendered
|
||||
export const DefaultEditView: React.FC = (props) => {
|
||||
export const DefaultEditView: React.FC = () => {
|
||||
const {
|
||||
action,
|
||||
BeforeDocument,
|
||||
@@ -44,6 +44,9 @@ export const DefaultEditView: React.FC = (props) => {
|
||||
id,
|
||||
hasSavePermission,
|
||||
disableActions,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
disableLeaveWithoutSaving,
|
||||
} = useDocumentInfo()
|
||||
|
||||
const { user } = useAuth()
|
||||
@@ -60,11 +63,9 @@ export const DefaultEditView: React.FC = (props) => {
|
||||
const { getFieldMap } = useComponentMap()
|
||||
|
||||
const collectionConfig =
|
||||
'collectionSlug' in props &&
|
||||
collections.find((collection) => collection.slug === props.collectionSlug)
|
||||
collectionSlug && collections.find((collection) => collection.slug === collectionSlug)
|
||||
|
||||
const globalConfig =
|
||||
'globalSlug' in props && globals.find((global) => global.slug === props.globalSlug)
|
||||
const globalConfig = globalSlug && globals.find((global) => global.slug === globalSlug)
|
||||
|
||||
const [schemaPath] = React.useState(collectionConfig?.slug || globalConfig?.slug)
|
||||
|
||||
@@ -81,7 +82,7 @@ export const DefaultEditView: React.FC = (props) => {
|
||||
const preventLeaveWithoutSaving =
|
||||
(!(collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.autosave) ||
|
||||
!(globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave)) &&
|
||||
!('disableLeaveWithoutSaving' in props && props.disableLeaveWithoutSaving)
|
||||
!disableLeaveWithoutSaving
|
||||
|
||||
const classes = [baseClass, id && `${baseClass}--is-editing`].filter(Boolean).join(' ')
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
|
||||
import type { DefaultListViewProps } from './types'
|
||||
import { SelectionProvider } from './SelectionProvider'
|
||||
import { Gutter } from '../../elements/Gutter'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
@@ -17,12 +16,13 @@ import { useComponentMap } from '../../providers/ComponentMapProvider'
|
||||
import { Table } from '../../elements/Table'
|
||||
import { ListControls } from '../../elements/ListControls'
|
||||
import { useStepNav } from '../../elements/StepNav'
|
||||
import { useListInfo } from '../../providers/ListInfo'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'collection-list'
|
||||
|
||||
export const DefaultList: React.FC<DefaultListViewProps> = (props) => {
|
||||
export const DefaultList: React.FC = () => {
|
||||
const {
|
||||
Header,
|
||||
data,
|
||||
@@ -35,10 +35,10 @@ export const DefaultList: React.FC<DefaultListViewProps> = (props) => {
|
||||
limit,
|
||||
modifySearchParams,
|
||||
newDocumentURL,
|
||||
resetParams,
|
||||
// resetParams,
|
||||
titleField,
|
||||
collectionSlug,
|
||||
} = props
|
||||
} = useListInfo()
|
||||
|
||||
const config = useConfig()
|
||||
|
||||
|
||||
@@ -1,37 +1,7 @@
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { PaginatedDocs } from 'payload/database'
|
||||
import type { FieldAffectingData, Where } from 'payload/types'
|
||||
import type { Props as ListControlsProps } from '../../elements/ListControls/types'
|
||||
import type { Props as PaginatorProps } from '../../elements/Pagination/types'
|
||||
import type { Props as PerPageProps } from '../../elements/PerPage'
|
||||
|
||||
export type DefaultListViewProps = {
|
||||
Header?: React.ReactNode
|
||||
data: PaginatedDocs<any>
|
||||
handleDelete?: () => void
|
||||
handlePageChange?: PaginatorProps['onChange']
|
||||
handlePerPageChange?: PerPageProps['handleChange']
|
||||
handleSearchChange?: ListControlsProps['handleSearchChange']
|
||||
handleSortChange?: ListControlsProps['handleSortChange']
|
||||
handleWhereChange?: ListControlsProps['handleWhereChange']
|
||||
hasCreatePermission: boolean
|
||||
limit: number
|
||||
modifySearchParams?: boolean
|
||||
newDocumentURL: string
|
||||
onCreateNewClick?: () => void
|
||||
resetParams?: (overrides?: {
|
||||
page?: number
|
||||
search?: string
|
||||
sort?: string
|
||||
where?: Where
|
||||
}) => void
|
||||
setLimit?: (limit: number) => void
|
||||
setListControls?: (controls: unknown) => void
|
||||
setSort?: (sort: string) => void
|
||||
titleField?: FieldAffectingData
|
||||
toggleColumn?: (column: string) => void
|
||||
collectionSlug: SanitizedCollectionConfig['slug']
|
||||
useAsTitle?: SanitizedCollectionConfig['admin']['useAsTitle']
|
||||
}
|
||||
|
||||
export type ListIndexProps = {
|
||||
|
||||
Reference in New Issue
Block a user