Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ui): ssr document tabs #5116

Merged
merged 7 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/next/src/pages/Document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ export const Document = async ({
collectionSlug={collectionConfig?.slug}
globalSlug={globalConfig?.slug}
id={id}
versionsConfig={collectionConfig?.versions || globalConfig?.versions}
/>
<EditDepthProvider depth={1} key={`${collectionSlug || globalSlug}-${locale}`}>
<FormQueryParamsProvider formQueryParams={formQueryParams}>
Expand Down
34 changes: 34 additions & 0 deletions packages/next/src/pages/Edit/index.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'
import React from 'react'
import { DefaultEditView, EditViewProps, useDocumentInfo } from '@payloadcms/ui'
import { useCallback } from 'react'

export const DefaultEditViewClient: React.FC<EditViewProps> = (props) => {
const id = 'id' in props ? props.id : undefined
const collectionSlug = 'collectionSlug' in props ? props.collectionSlug : undefined
const isEditing = Boolean(id && collectionSlug)

const { getVersions, getDocPermissions } = useDocumentInfo()

const onSave = useCallback(
async (json: { doc }) => {
getVersions()
getDocPermissions()

if (!isEditing) {
// setRedirect(`${admin}/collections/${collection.slug}/${json?.doc?.id}`)
} else {
// buildState(json.doc, {
// fieldSchema: collection.fields,
// })
// setFormQueryParams((params) => ({
// ...params,
// uploadEdits: undefined,
// }))
}
},
[getVersions, isEditing, getDocPermissions, collectionSlug],
)

return <DefaultEditView {...props} onSave={onSave} />
}
4 changes: 2 additions & 2 deletions packages/next/src/pages/Edit/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react'
import { DefaultEditView } from '@payloadcms/ui'
import { ServerSideEditViewProps } from '../../../../ui/src/views/types'
import { sanitizedEditViewProps } from './sanitizedEditViewProps'
import { DefaultEditViewClient } from './index.client'

export const EditView: React.FC<ServerSideEditViewProps> = async (props) => {
const clientSideProps = sanitizedEditViewProps(props)
return <DefaultEditView {...clientSideProps} />
return <DefaultEditViewClient {...clientSideProps} />
}
16 changes: 5 additions & 11 deletions packages/payload/src/admin/elements/Tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import type { I18n } from '@payloadcms/translations'
import type { SanitizedCollectionConfig } from '../../collections/config/types'
import type { SanitizedConfig } from '../../config/types'
import type { SanitizedGlobalConfig } from '../../globals/config/types'
import type { Document } from '../../types'
import type { DocumentInfoContext } from '../providers/DocumentInfo'

export type DocumentTabProps = {
apiURL?: string
Expand All @@ -17,13 +15,13 @@ export type DocumentTabProps = {
export type DocumentTabCondition = (args: {
collectionConfig: SanitizedCollectionConfig
config: SanitizedConfig
documentInfo: DocumentInfoContext
globalConfig: SanitizedGlobalConfig
}) => boolean

// Everything is optional because we merge in the defaults
// i.e. the config may override the `Default` view with a `label` but not an `href`
export type DocumentTabConfig = {
Pill?: React.ComponentType
condition?: DocumentTabCondition
href?:
| ((args: {
Expand All @@ -34,17 +32,13 @@ export type DocumentTabConfig = {
routes: SanitizedConfig['routes']
}) => string)
| string
isActive?: boolean
// isActive?: ((args: { href: string }) => boolean) | boolean
isActive?: ((args: { href: string }) => boolean) | boolean
label?: ((args: { t: (key: string) => string }) => string) | string
newTab?: boolean
pillLabel?: ((args: { versions: Document }) => string) | string
}

export type DocumentTabComponent = React.ComponentType<
DocumentTabProps & {
path: string
}
>
export type DocumentTabComponent = React.ComponentType<{
path: string
}>

export type DocumentTab = DocumentTabComponent | DocumentTabConfig
3 changes: 1 addition & 2 deletions packages/payload/src/admin/providers/DocumentInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { SanitizedGlobalConfig } from '../../globals/config/types'

export type DocumentInfoContext = {
collectionSlug?: SanitizedCollectionConfig['slug']
docConfig?: SanitizedCollectionConfig | SanitizedGlobalConfig
docPermissions: DocumentPermissions
getDocPermissions: () => Promise<void>
getDocPreferences: () => Promise<{ [key: string]: unknown }>
Expand All @@ -23,14 +24,12 @@ export type DocumentInfoContext = {
collectionSlug: SanitizedCollectionConfig['slug']
globalSlug: SanitizedGlobalConfig['slug']
id: number | string
versionsConfig: SanitizedCollectionConfig['versions'] | SanitizedGlobalConfig['versions']
}>,
) => void
setDocumentTitle: (title: string) => void
slug?: string
title?: string
unpublishedVersions?: PaginatedDocs<TypeWithVersion<any>>
versions?: PaginatedDocs<TypeWithVersion<any>>
versionsConfig?: SanitizedCollectionConfig['versions'] | SanitizedGlobalConfig['versions']
versionsCount?: PaginatedDocs<TypeWithVersion<any>>
}
4 changes: 3 additions & 1 deletion packages/ui/src/elements/Autosave/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
routes: { admin, api },
serverURL,
} = useConfig()
const { getVersions, versionsConfig, versions } = useDocumentInfo()
const { getVersions, docConfig, versions } = useDocumentInfo()
const versionsConfig = docConfig?.versions

const [fields] = useAllFormFields()
const modified = useFormModified()
const { code: locale } = useLocale()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
'use client'
import React from 'react'
import { useParams } from '../../../providers/Params'
import { useDocumentInfo } from '../../../providers/DocumentInfo'

export const ShouldRenderTabs: React.FC<{
children: React.ReactNode
}> = ({ children }) => {
const {
collection: collectionSlug,
global: globalSlug,
segments: [idFromParam] = [],
} = useParams()
const { collectionSlug, globalSlug, id: idFromContext } = useDocumentInfo()

const id = idFromParam !== 'create' ? idFromParam : null
const id = idFromContext !== 'create' ? idFromContext : null

// Don't show tabs when creating new documents
if ((collectionSlug && id) || globalSlug) {
Expand Down
33 changes: 16 additions & 17 deletions packages/ui/src/elements/DocumentHeader/Tabs/Tab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,70 @@ import React, { Fragment } from 'react'

import { DocumentTabLink } from './TabLink'

import './index.scss'
import { DocumentTabConfig, DocumentTabProps } from 'payload/types'

const baseClass = 'doc-tab'
import './index.scss'

export const baseClass = 'doc-tab'

export const DocumentTab: React.FC<DocumentTabProps & DocumentTabConfig> = (props) => {
const {
id,
apiURL,
config,
collectionConfig,
condition,
globalConfig,
href: tabHref,
isActive: checkIsActive,
isActive: tabIsActive,
label,
newTab,
pillLabel,
Pill,
i18n,
} = props

const { routes } = config
// const { versions } = documentInfo

let href = typeof tabHref === 'string' ? tabHref : ''
let isActive = typeof tabIsActive === 'boolean' ? tabIsActive : false

if (typeof tabHref === 'function') {
href = tabHref({
id,
apiURL,
collection: collectionConfig,
global: globalConfig,
routes,
})
}

if (
!condition ||
(condition && condition({ collectionConfig, config, documentInfo: undefined, globalConfig }))
) {
if (typeof tabIsActive === 'function') {
isActive = tabIsActive({
href,
})
}

if (!condition || (condition && condition({ collectionConfig, config, globalConfig }))) {
const labelToRender =
typeof label === 'function'
? label({
t: i18n.t,
})
: label

const pillToRender =
typeof pillLabel === 'function' ? pillLabel({ versions: undefined }) : pillLabel

return (
<DocumentTabLink
href={href}
newTab={newTab}
baseClass={baseClass}
isActive={checkIsActive}
isActive={isActive}
adminRoute={routes.admin}
isCollection={!!collectionConfig && !globalConfig}
>
<span className={`${baseClass}__label`}>
{labelToRender}
{pillToRender && (
{Pill && (
<Fragment>
&nbsp;
<span className={`${baseClass}__count`}>{pillToRender}</span>
<Pill />
</Fragment>
)}
</span>
Expand Down
52 changes: 34 additions & 18 deletions packages/ui/src/elements/DocumentHeader/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import React from 'react'
import { DocumentTab } from './Tab'
import { getCustomViews } from './getCustomViews'
import { getViewConfig } from './getViewConfig'
import { tabs as defaultViews } from './tabs'
import { DocumentTabProps } from 'payload/types'
import { tabs as defaultTabs } from './tabs'
import { ShouldRenderTabs } from './ShouldRenderTabs'
import { SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig } from 'payload/types'
import { I18n } from '@payloadcms/translations'

import './index.scss'

const baseClass = 'doc-tabs'

export const DocumentTabs: React.FC<DocumentTabProps> = (props) => {
const { collectionConfig, globalConfig } = props
export const DocumentTabs: React.FC<{
config: SanitizedConfig
collectionConfig: SanitizedCollectionConfig
globalConfig: SanitizedGlobalConfig
i18n: I18n
}> = (props) => {
const { collectionConfig, globalConfig, config } = props

const customViews = getCustomViews({ collectionConfig, globalConfig })

Expand All @@ -21,7 +27,7 @@ export const DocumentTabs: React.FC<DocumentTabProps> = (props) => {
<div className={baseClass}>
<div className={`${baseClass}__tabs-container`}>
<ul className={`${baseClass}__tabs`}>
{Object.entries(defaultViews)
{Object.entries(defaultTabs)
// sort `defaultViews` based on `order` property from smallest to largest
// if no `order`, append the view to the end
// TODO: open `order` to the config and merge `defaultViews` with `customViews`
Expand All @@ -31,20 +37,30 @@ export const DocumentTabs: React.FC<DocumentTabProps> = (props) => {
else if (b.order === undefined) return -1
return a.order - b.order
})
?.map(([name, Tab], index) => {
?.map(([name, tab], index) => {
const viewConfig = getViewConfig({ name, collectionConfig, globalConfig })
const tabOverrides = viewConfig && 'Tab' in viewConfig ? viewConfig.Tab : undefined
const tabFromConfig = viewConfig && 'Tab' in viewConfig ? viewConfig.Tab : undefined
const tabConfig = typeof tabFromConfig === 'object' ? tabFromConfig : undefined

return (
<DocumentTab
{...{
...props,
...(Tab || {}),
...(tabOverrides || {}),
}}
key={`tab-${index}`}
/>
)
const { condition } = tabConfig || {}

const meetsCondition =
!condition || (condition && condition({ collectionConfig, config, globalConfig }))

if (meetsCondition) {
return (
<DocumentTab
key={`tab-${index}`}
{...{
...props,
...(tab || {}),
...(tabFromConfig || {}),
}}
/>
)
}

return null
})}
{customViews?.map((CustomView, index) => {
if ('Tab' in CustomView) {
Expand All @@ -56,11 +72,11 @@ export const DocumentTabs: React.FC<DocumentTabProps> = (props) => {

return (
<DocumentTab
key={`tab-custom-${index}`}
{...{
...props,
...Tab,
}}
key={`tab-custom-${index}`}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client'
import React from 'react'
import { useDocumentInfo } from '../../../../../providers/DocumentInfo'
import { baseClass } from '../../Tab'

export const VersionsPill: React.FC = () => {
const { versions } = useDocumentInfo()
return <span className={`${baseClass}__count`}>{versions?.totalDocs?.toString()}</span>
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DocumentTabConfig } from 'payload/types'
import { VersionsPill } from './VersionsPill'

export const documentViewKeys = [
'API',
Expand Down Expand Up @@ -70,9 +71,6 @@ export const tabs: Record<
href: '/versions',
label: ({ t }) => t('version:versions'),
order: 200,
pillLabel: ({ versions }) =>
typeof versions?.totalDocs === 'number' && versions?.totalDocs > 0
? versions?.totalDocs.toString()
: '',
Pill: VersionsPill,
},
}
7 changes: 2 additions & 5 deletions packages/ui/src/elements/ListDrawer/DrawerContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useReducer, useState } from 'react'
import { useTranslation } from '../../providers/Translation'

import type { SanitizedCollectionConfig } from 'payload/types'
import type { Where, Field } from 'payload/types'
import type { Where } from 'payload/types'
import type { ListDrawerProps } from './types'

import { baseClass } from '.'
Expand Down Expand Up @@ -305,10 +305,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
]}
collectionSlug={selectedCollectionConfig.slug}
>
<DocumentInfoProvider
collectionSlug={selectedCollectionConfig.slug}
versionsConfig={selectedCollectionConfig?.versions}
>
<DocumentInfoProvider collectionSlug={selectedCollectionConfig.slug}>
<RenderCustomComponent
CustomComponent={ListToRender}
DefaultComponent={DefaultList}
Expand Down
Loading
Loading