Skip to content

feat(ui): redesign uploads document view#17044

Open
paulpopus wants to merge 20 commits into
mainfrom
feat/uploads-ui-redesign
Open

feat(ui): redesign uploads document view#17044
paulpopus wants to merge 20 commits into
mainfrom
feat/uploads-ui-redesign

Conversation

@paulpopus

@paulpopus paulpopus commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

What

Redesigns the admin UI for upload-enabled collections, moving from the stacked single-column layout to a side-by-side document view: file management on the right, document fields on the left.

Highlights

  • Side-by-side layout — upload collection documents now render a dedicated file side panel (__upload-layout) alongside the fields, instead of stacking the upload above the fields.
  • New FileManager element replaces the inline Upload in the edit view, encapsulating the dropzone, preview, and toolbar.
  • Mini carousel for switching between the original file and its generated image sizes.
  • Type-aware previews — dedicated preview components for images, audio, video, and PDFs, with a fallback thumbnail for everything else.
  • File toolbar with quick actions: open the current asset in a new tab, download, crop/edit image, replace, and rename.
  • Upload-from-URL and rename-file modals.
  • New Crop and Download icons.

New config API

Adds upload.admin.components.filePreview, letting collections override the side-panel preview:

// single component for all files
upload: {
  admin: {
    components: {
      filePreview: '/components/MyPreview#MyPreview',
    },
  },
}

// or a MIME-type keyed map
upload: {
  admin: {
    components: {
      filePreview: {
        'video/*': '/components/VideoPreview#VideoPreview',
        'application/pdf': '/components/PdfPreview#PdfPreview',
        '*': '/components/Fallback#Fallback',
      },
    },
  },
}

Resolution priority for the map is exact match → category wildcard (video/*) → universal fallback (*), falling back to the default thumbnail when nothing matches. A new matchMimeType helper (exported from payload/shared) implements this, a UploadFilePreview document slot and UploadFilePreviewClientProps type are added, and the import-map generator now picks up filePreview components.

i18n

Adds translation keys: general:original, upload:fromURL, upload:linkToFile, upload:renameFile, upload:replaceFile.

Notes

  • Custom Upload components (BeforeFields / CustomUpload) continue to render in the legacy single-column path, so existing overrides are unaffected.

@paulpopus paulpopus changed the title feat(ui): uploads UI redesign feat(ui): redesign uploads document view Jun 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 201.84 KB 🆕 Added
packages/payload/meta_index.json esbuild/index.js 1.40 MB 🆕 Added
packages/payload/meta_shared.json esbuild/exports/shared.js 192.76 KB 🆕 Added
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 304.96 KB 🆕 Added
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.39 MB 🆕 Added
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.66 KB 🆕 Added
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████████▋ }}}$ 98.9%, 197.86 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 663 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.3%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.3%, 526 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.2%, 409 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.2%, 960.52 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 43.88 KB
dist/collections/operations ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 42.68 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.63 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 14.93 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.36 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.22 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 12.63 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.57 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.82 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.21 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.07 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.80 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.77 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.64 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.55 KB
dist/collections/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 6.12 KB
dist/queues/config ${{\color{Goldenrod}{ }}}$ 0.4%, 5.59 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 5.43 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ }}}$ 0.4%, 5.31 KB
(other) ${{\color{Goldenrod}{ ███████▋ }}}$ 30.8%, 428.47 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████████████▉ }}}$ 79.5%, 150.12 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 10.57 KB
dist/config/orderable ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 2.69 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.42 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.29 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 1.04 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ }}}$ 0.4%, 794 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.4%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.4%, 713 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 651 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.3%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.3%, 561 B
dist/auth/sessions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 525 B
dist/fields/getFieldPaths.js ${{\color{Goldenrod}{ }}}$ 0.3%, 485 B
dist/utilities/appendDateTimezoneSelectFields.js ${{\color{Goldenrod}{ }}}$ 0.2%, 432 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.2%, 423 B
dist/utilities/deepMerge.js ${{\color{Goldenrod}{ }}}$ 0.2%, 413 B
(other) ${{\color{Goldenrod}{ █████▏ }}}$ 20.5%, 38.81 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███ }}}$ 12.3%, 37.13 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▊ }}}$ 11.3%, 34.16 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▋ }}}$ 10.9%, 32.88 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▎ }}}$ 9.0%, 27.16 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▌ }}}$ 6.2%, 18.83 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▍ }}}$ 5.5%, 16.58 KB
dist/features/upload ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 14.09 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 11.08 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 9.61 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.79 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 8.36 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 8.12 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.08 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.00 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.23 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.83 KB
(other) ${{\color{Goldenrod}{ █████████████████████▉ }}}$ 87.7%, 264.60 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████▌ }}}$ 42.2%, 580.42 KB
dist/elements/Hierarchy ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 43.06 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 28.33 KB
dist/views/Version ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 27.40 KB
dist/views/HierarchyList ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 20.43 KB
dist/elements/Table ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 19.38 KB
dist/views/Edit ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.95 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.73 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.39 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 15.89 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.48 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.06 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 14.40 KB
dist/elements/QueryPresets ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 10.22 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.07 KB
dist/elements/FileManager ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.97 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.78 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.77 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.38 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.26 KB
(other) ${{\color{Goldenrod}{ ██████████████▍ }}}$ 57.8%, 794.00 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▋ }}}$ 30.9%, 5.57 KB
../../node_modules ${{\color{Goldenrod}{ ███▋ }}}$ 14.7%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▏ }}}$ 8.5%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.3%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.8%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 745 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 419 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 157 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 148 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 146 B
(other) ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.1%, 12.47 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EditUpload element still has a library.scss file. Needs to be either converted or removed if those styles are not being used anymore

import { Button } from '../Button/index.js'
import { DialogBody, DialogFooter, DialogHeader, DialogModal } from '../Dialog/index.js'
import './index.css'
import './library.scss'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EditUpload is using a library.scss file - this should be converted to css or removed if the styles aren't being used anymore

.edit-upload__sidebar {
flex-shrink: 0;
width: 240px;
border-left: 1px solid var(--color-border);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to use var(--stroke-width-small) instead of 1px value here.

I'm noticing this throughout a lot of your edited css files so I'm going to just leave this one comment but make sure to search through all your edited files and replace there as well.

Really any usage of 1px should be replaced with var(--stroke-width-small) even if not being used for a border style necessarily.

.audio-preview {
width: 100%;
max-width: 480px;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For specific pixel values like 480px here and common pattern we have been following is defining it at the top of the file as a var, I'd recommend following this pattern here as well

font-weight: var(--text-body-medium-strong-font-weight);
line-height: var(--text-body-medium-strong-line-height);
letter-spacing: var(--text-body-medium-letter-spacing);
max-width: 200px;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as mentioned in the above comment - any hard coded pixel values if necessary should be defined at the top of the file as variables

align-items: center;
justify-content: center;
width: 24px;
height: 24px;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these widths & heights necessary? The icons themselves should already be of that dimension.

Our icons are either 16x16 or 24x24 by default and sometimes have options for both

readOnly={isReadOnlyForIncomingUser || !hasSavePermission || isTrashed}
schemaPathSegments={schemaPathSegments}
/>
{upload && !BeforeFields ? (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomUpload does not fall back to the legacy single-column layout it seems.

The PR description says custom Upload components "continue to render in the legacy single-column path." But the branch is upload && !BeforeFields, and CustomUpload is rendered inside the new __upload-layout (side-by-side) div. Only setting BeforeFields forces the legacy path.

So a collection with a custom components.edit.Upload (but no BeforeFields) now renders side-by-side, which contradicts the description and may break existing custom upload UIs.

<MiniCarouselItem
active={selectedSize === null}
imageCacheTag={imageCacheTag}
label="Original"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard coded label "Original" - need to use translation key

<DocumentFields
AfterFields={AfterFields}
BeforeFields={
auth ? (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <Auth> JSX is copy-pasted into both branches of the Edit ternary - should we extract it to a local const authComponent to avoid drift?

* native PDF viewer via an iframe.
*/
export const PdfPreview: React.FC<{ fileSrc: string; title?: string }> = ({ fileSrc, title }) => {
return <iframe className={baseClass} src={fileSrc} title={title ?? 'PDF preview'} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has no sandbox prop defined - served as application/pdf, same-origin admin

Might be worth a deliberate decision

Comment thread test/uploads/config.ts
name: 'photographer',
type: 'text',
admin: {
// position: 'sidebar',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover commented out code here

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppHeader, DocumentControls, and the new DocumentHeaderRoot each set documentElement CSS variables via ResizeObserver.

DocumentControls also renders inside document drawers, so multiple instances could clobber --doc-controls-height, and none of the three clear their variable on unmount.

Worth confirming drawer/edit-in-place scenarios still lay out correctly.

@PatrikKozak PatrikKozak self-assigned this Jun 18, 2026

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

These icons vary too much in size difference. I see that they're all 24 x 24 px but its possible you're pulling in the large variant from fpl - I'd double check

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the popup here meant to mirror the buttons width or at least be the intended design width?

Design shows 118px for width of the popup above

Bigger issue: The icon used here for replace file is incorrect according to the design

Image

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Border radius on .file-preview__info-label needs to be --radius-medium instead of --radius-small

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Drawer padding in upload edit drawer is inconsistent with some of the upload content

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uploads with no fields looks like this right now, looks odd. What should uploads with no fields look like? Question for design.

Image

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tooltip is sitting underneath the doc controls on hover of the icons in below image

Maybe we can adjust the tooltip to be positioned below the icon on hover

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants