fix(ui): move collection description below title in document view (#13946)

### What?

Moved description rendering from DocumentFields to DocumentHeader
component.

### List view

#### Before 
<img width="1315" height="696" alt="Screenshot 2025-09-26 at 10 12
14 AM"
src="https://github.com/user-attachments/assets/9c102f4b-ed71-4e3d-85d6-87464e6c8568"
/>

#### After
<img width="1647" height="762" alt="Screenshot 2025-09-26 at 1 24 12 PM"
src="https://github.com/user-attachments/assets/1c2f4eae-5bf8-43ad-af65-23f333b01ba8"
/>


### Document View

#### Before
<img width="1321" height="673" alt="Screenshot 2025-09-26 at 10 57
01 AM"
src="https://github.com/user-attachments/assets/3c6c7218-a8f6-4e52-af27-f0c4ffa0a6ef"
/>

#### After
<img width="1645" height="682" alt="Screenshot 2025-09-26 at 1 24 29 PM"
src="https://github.com/user-attachments/assets/1ac774c7-8820-4d41-afef-c60044383474"
/>


### Document Drawer

<img width="1631" height="631" alt="Screenshot 2025-09-26 at 1 24 49 PM"
src="https://github.com/user-attachments/assets/42285d23-a37d-4419-9644-f9c27358f2bf"
/>

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211478222789332
This commit is contained in:
Patrik
2025-09-26 15:15:45 -04:00
committed by GitHub
parent 17520439e5
commit ae34b6d6d1
13 changed files with 80 additions and 38 deletions

View File

@@ -5,11 +5,7 @@
width: 100%;
margin-top: base(0.4);
padding-bottom: calc(var(--base) * 1.2);
display: flex;
align-items: center;
position: relative;
display: flex;
gap: calc(var(--base) / 2);
&::after {
content: '';
@@ -22,6 +18,12 @@
top: calc(100% - 1px);
}
&__header {
display: flex;
align-items: center;
gap: calc(var(--base) / 2);
}
&__title {
flex-grow: 1;
white-space: nowrap;
@@ -31,18 +33,29 @@
padding-bottom: base(0.4);
line-height: 1;
vertical-align: top;
padding-bottom: 0;
}
&__after-header {
padding-top: var(--base);
}
@include mid-break {
margin-top: base(0.25);
padding-bottom: calc(var(--base) / 1.5);
flex-direction: column;
gap: calc(var(--base) / 2);
padding-bottom: calc(var(--base) / 2);
&__header {
flex-direction: column;
gap: calc(var(--base) / 2);
}
&__title {
width: 100%;
}
&__after-header {
padding-top: calc(var(--base) / 4);
}
}
@include small-break {

View File

@@ -1,4 +1,3 @@
import type { I18n } from '@payloadcms/translations'
import type {
PayloadRequest,
SanitizedCollectionConfig,
@@ -15,25 +14,29 @@ import './index.scss'
const baseClass = `doc-header`
export const DocumentHeader: React.FC<{
AfterHeader?: React.ReactNode
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
hideTabs?: boolean
permissions: SanitizedPermissions
req: PayloadRequest
}> = (props) => {
const { collectionConfig, globalConfig, hideTabs, permissions, req } = props
const { AfterHeader, collectionConfig, globalConfig, hideTabs, permissions, req } = props
return (
<Gutter className={baseClass}>
<RenderTitle className={`${baseClass}__title`} />
{!hideTabs && (
<DocumentTabs
collectionConfig={collectionConfig}
globalConfig={globalConfig}
permissions={permissions}
req={req}
/>
)}
<div className={`${baseClass}__header`}>
<RenderTitle className={`${baseClass}__title`} />
{!hideTabs && (
<DocumentTabs
collectionConfig={collectionConfig}
globalConfig={globalConfig}
permissions={permissions}
req={req}
/>
)}
</div>
{AfterHeader ? <div className={`${baseClass}__after-header`}>{AfterHeader}</div> : null}
</Gutter>
)
}

View File

@@ -341,6 +341,9 @@ export const renderDocument = async ({
req,
})
// Extract Description from documentSlots to pass to DocumentHeader
const { Description } = documentSlots
const clientProps: DocumentViewClientProps = {
formState,
...documentSlots,
@@ -395,6 +398,7 @@ export const renderDocument = async ({
>
{showHeader && !drawerSlug && (
<DocumentHeader
AfterHeader={Description}
collectionConfig={collectionConfig}
globalConfig={globalConfig}
permissions={permissions}

View File

@@ -8,7 +8,6 @@
display: flex;
flex-direction: column;
gap: calc(var(--base) * 0.5);
align-items: flex-start;
border-bottom: 1px solid var(--theme-elevation-100);
padding-bottom: var(--base);
}
@@ -63,6 +62,10 @@
}
}
&__after-header {
padding-top: calc(var(--base) / 4);
}
&__divider {
height: 1px;
background: var(--theme-elevation-100);

View File

@@ -12,9 +12,10 @@ import { documentDrawerBaseClass } from '../index.js'
import './index.scss'
export const DocumentDrawerHeader: React.FC<{
AfterHeader?: React.ReactNode
drawerSlug: string
showDocumentID?: boolean
}> = ({ drawerSlug, showDocumentID = true }) => {
}> = ({ AfterHeader, drawerSlug, showDocumentID = true }) => {
const { closeModal } = useModal()
const { t } = useTranslation()
@@ -34,6 +35,9 @@ export const DocumentDrawerHeader: React.FC<{
</button>
</div>
{showDocumentID && <DocumentID />}
{AfterHeader ? (
<div className={`${documentDrawerBaseClass}__after-header`}>{AfterHeader}</div>
) : null}
</Gutter>
)
}

View File

@@ -26,7 +26,6 @@ type Args = {
export const DocumentFields: React.FC<Args> = ({
AfterFields,
BeforeFields,
Description,
docPermissions,
fields,
forceSidebarWrap,
@@ -68,11 +67,6 @@ export const DocumentFields: React.FC<Args> = ({
<div className={`${baseClass}__main`}>
<Gutter className={`${baseClass}__edit`}>
{isTrashed && <TrashBanner />}
{Description ? (
<header className={`${baseClass}__header`}>
<div className={`${baseClass}__sub-header`}>{Description}</div>
</header>
) : null}
{BeforeFields}
<RenderFields
className={`${baseClass}__fields`}

View File

@@ -10,5 +10,6 @@
padding: base(0.2) base(0.4);
border-radius: $style-radius-m;
display: inline-flex;
width: fit-content;
}
}

View File

@@ -520,7 +520,11 @@ export function DefaultEditView({
onSuccess={onSave}
>
{isInDrawer && (
<DocumentDrawerHeader drawerSlug={drawerSlug} showDocumentID={!isFolderCollection} />
<DocumentDrawerHeader
AfterHeader={Description}
drawerSlug={drawerSlug}
showDocumentID={!isFolderCollection}
/>
)}
{isLockingEnabled && shouldShowDocumentLockedModal && (
<DocumentLocked

View File

@@ -20,6 +20,7 @@
&__sub-header {
flex-basis: 100%;
padding-top: calc(var(--base) * 0.75);
}
&__tables {

View File

@@ -1,19 +1,15 @@
import { Banner as PayloadBanner } from '@payloadcms/ui'
export function Banner(props: {
children?: React.ReactNode
className?: string
description?: string
message?: string
}) {
const { children, description, message } = props
const { children, className, description, message } = props
return (
<div
style={{
backgroundColor: 'var(--theme-warning-100)',
border: '1px dashed',
color: 'var(--theme-warning-750)',
padding: '1rem',
}}
>
<PayloadBanner className={className} type="success">
{children || message || description || 'A custom banner component'}
</div>
</PayloadBanner>
)
}

View File

@@ -0,0 +1,3 @@
.view-description {
margin-bottom: 0;
}

View File

@@ -6,12 +6,15 @@ import { ViewDescription as DefaultViewDescription } from '@payloadcms/ui'
import React from 'react'
import { Banner } from '../Banner/index.js'
import './index.scss'
const baseClass = 'view-description'
export function ViewDescription({
description = 'This is a custom view description component.',
}: ViewDescriptionClientProps) {
return (
<Banner>
<Banner className={baseClass}>
<DefaultViewDescription description={description} />
</Banner>
)

View File

@@ -496,6 +496,19 @@ describe('Document View', () => {
// Ensure the original page did not change
expect(page.url()).toBe(currentUrl)
})
test('document drawer displays AfterHeader components', async () => {
await navigateToDoc(page, postsUrl)
await page
.locator('.field-type.relationship .relationship--single-value__drawer-toggler')
.click()
await wait(500)
const drawer1Content = page.locator('[id^=doc-drawer_posts_1_] .drawer__content')
await expect(drawer1Content).toBeVisible()
const afterHeader = page.locator('[id^=doc-drawer_posts_1_] .doc-drawer__after-header')
await expect(afterHeader).toBeVisible()
})
})
describe('descriptions', () => {