Skip to content

Edit Site: Migrate Quick Edit panel from Modal to @wordpress/ui Drawer#78029

Draft
ciampo wants to merge 7 commits into
trunkfrom
refactor/quick-edit-modal-to-drawer
Draft

Edit Site: Migrate Quick Edit panel from Modal to @wordpress/ui Drawer#78029
ciampo wants to merge 7 commits into
trunkfrom
refactor/quick-edit-modal-to-drawer

Conversation

@ciampo
Copy link
Copy Markdown
Contributor

@ciampo ciampo commented May 6, 2026

What

Migrates the Quick Edit panel in Edit Site's posts/pages list from @wordpress/components Modal (with custom drawer-like CSS) to the new @wordpress/ui Drawer primitive.

Why

Quick Edit was already designed as a side drawer; using the actual drawer primitive replaces a stack of overrides with idiomatic Drawer.Root / Drawer.Header / Drawer.Content / Drawer.Footer / Drawer.Action, gives us proper enter/exit and swipe-to-dismiss animations, and aligns with the rest of this migration sweep.

How

  • Replace Modal with Drawer.Root + Drawer.Popup, kept mounted in the React tree and driven by open / onOpenChange so animations play.
  • Bind session teardown (record + edits) to onOpenChangeComplete so the form doesn't flash empty during the exit animation; key the inner session on postType + postId so re-opening on a different row resets useSelect cleanly.
  • Render PostCardPanel inside Drawer.Content (rather than Drawer.Header) and keep the header to a VisuallyHidden <span> Drawer.Title plus the close icon, since PostCardPanel already renders its own <h2> and is laid out as a vertical stack. A follow-up will integrate PostCardPanel properly with Drawer.Header.
  • Remove the bespoke "drawer" CSS in post-list/style.scss.

Testing instructions

  1. Edit Site → Posts (or Pages) → click a row's "Quick edit" item action.
  2. Confirm the drawer animates in from the right, focus moves into the form, Escape and the close icon dismiss it.
  3. Bulk select a few rows and trigger Quick Edit — confirm it opens with the bulk-edit form and saves.
  4. Switch to a different row mid-animation and confirm contents update without stale data flashing.

Context

This is one of 5 PRs that split #76837. Independent of the others.

ciampo added 7 commits May 6, 2026 18:08
Replace the custom Modal-with-drawer-CSS implementation of the Quick Edit
panel in Edit Site with the new @wordpress/ui Drawer primitive. Removes
~90 lines of custom SCSS overrides that were recreating drawer behavior
(slide-in/out animations, right-edge positioning, sticky header/footer)
on top of a Modal, and delegates all of that to the Drawer component.

- Drawer.Root handles open state, swipe-to-dismiss, and initial focus
- Drawer.Popup replaces the Modal + __experimentalHideHeader + custom CSS
- Drawer.Footer + Drawer.Action replace the HStack + Button + closeModal
  for the Cancel action
- VisuallyHidden Drawer.Title provides the accessible name for the
  dialog, replacing the previously-hidden Modal header

Made-with: Cursor
- Wrap the DataForm in `Drawer.Content` so long forms scroll properly while
  the `PostCardPanel` header and `Drawer.Footer` stay pinned (sticky default).
- Use `closeModal()` directly since the prop is required by the only caller.
- Document that `swipeDirection="right"` is intentionally physical (matches
  the previous Modal-based implementation in both LTR and RTL).
Extract the editable form portion of `QuickEditModal` into a new
`QuickEditSession` component rendered as a child of `Drawer.Popup`,
and remove the explicit `open` gate on the `useSelect` resolver.

Base UI's `Drawer.Portal` returns `null` once the exit animation
completes, so anything rendered inside `Drawer.Popup` is naturally
mounted on open and unmounted on close-complete. By moving the
entity-record subscription and the local edits state into the
session, those concerns are now tied to the drawer's on-screen
lifecycle: they engage when the drawer opens, stay alive through
the exit animation (so the form keeps rendering while the drawer
slides out, instead of blanking out as the resolver flips to its
"closed" branch), and tear down cleanly once the drawer is fully
closed. The perf benefit of skipping entity-record fetches while
the drawer isn't on screen is preserved — it's just delegated to
React's mount lifecycle rather than a manual `open`-prop gate.
Restructure the Quick Edit drawer to match the canonical
`Drawer.Header` → (no Drawer.Description) → `Drawer.Footer` shape from
the @wordpress/ui Storybook, and route close/save through Drawer
primitives instead of imperative callbacks.

- `<Drawer.Header>` now wraps the visually-hidden `<Drawer.Title>`,
  `<PostCardPanel hideActions />`, and `<Drawer.CloseIcon />`. The
  drawer now gets the primitive's pinned-top header positioning and
  scroll-edge separator behavior; before this change those wins were
  forfeited because PostCardPanel was a `Drawer.Popup`-direct sibling.
- Drop the `onClose={ closeModal }` prop on `PostCardPanel` — close is
  now a single, idiomatic affordance via `<Drawer.CloseIcon />` instead
  of a duplicate ad-hoc close button rendered by PostCardPanel.
- Cancel button → `<Drawer.Action variant="outline">`. Drops legacy
  `__next40pxDefaultSize` and `variant="secondary"` props (the latter is
  a `@wordpress/components` Button variant; `@wordpress/ui` Button uses
  `outline` for the equivalent).
- Done button → `<Drawer.Action onClick={ onSave }>`. Drawer closes
  synchronously through Base UI's `Drawer.Close`, then the
  `editEntityRecord` / `saveEditedEntityRecord` calls run in the
  background — more responsive UX. Errors continue to surface via the
  existing core-data notice path.
- `QuickEditSession` no longer needs `closeModal` as a prop; both
  buttons close through the Drawer primitive.

The Quick Edit drawer keeps controlled `open` / `closeModal` props on
its top-level `<QuickEditModal>` because the trigger lives in the
post-list view (a row-level action driving a single shared drawer over
an array of `postId`s) and doesn't fit `Drawer.Trigger`'s
sibling-of-popup model.

Note for review: `<PostCardPanel>` inside `<Drawer.Header>` is the
"D1a" route from the audit plan — PostCardPanel's VStack sits as a
flex-row child alongside the close icon, leveraging the header's
shared-padding / scroll-edge separator. Worth a quick visual
verification; a fallback ("D1b") leaves PostCardPanel as a Popup-direct
sibling and only puts the title + close icon inside the header.
Short-term layout fix for two issues with rendering `PostCardPanel`
inside `Drawer.Header`:

1. PostCardPanel renders its own `<h2>`, which competed with the
   `<h2>` produced by `Drawer.Title` and resulted in two top-level
   headings inside the popup.
2. PostCardPanel is laid out as a vertical stack, which doesn't
   compose with `Drawer.Header`'s flex-row title-and-close-icon
   shape.

Move `PostCardPanel` into `Drawer.Content` (just above the form) and
leave `Drawer.Header` minimal: a visually-hidden `Drawer.Title`
rendered as a `<span>` (so it doesn't introduce a second `<h2>`) plus
`Drawer.CloseIcon`. Base UI still wires `aria-labelledby`
automatically, so the drawer keeps an explicit accessible name.

Tracked as a follow-up: integrate `PostCardPanel` properly with
`Drawer.Header` so it can act as the visible drawer title.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: [Package] Edit Site.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates the Edit Site “Quick Edit” overlay in the posts/pages list from an @wordpress/components Modal (styled to behave like a drawer) to the @wordpress/ui Drawer primitive, removing the legacy drawer-emulation CSS and wiring the panel into the new overlay API.

Changes:

  • Replace Modal-based Quick Edit with Drawer.Root / Drawer.Popup / Drawer.Content / Drawer.Footer and Drawer.Action.
  • Always mount QuickEditModal and drive visibility via an open prop to allow open/close animations.
  • Remove the bespoke Quick Edit modal/drawer styles and update lint/changelog entries accordingly.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tools/eslint/suppressions.json Removes the prior suppression entry for Quick Edit’s component import rule.
packages/edit-site/src/components/post-list/style.scss Deletes the custom “drawer-like” Modal CSS and keyframe animations.
packages/edit-site/src/components/post-list/quick-edit-modal.js Implements Quick Edit as a @wordpress/ui Drawer with updated session/rendering logic.
packages/edit-site/src/components/post-list/index.js Switches Quick Edit rendering to an always-mounted component controlled via open.
packages/edit-site/CHANGELOG.md Adds an Unreleased changelog entry documenting the migration.
Comments suppressed due to low confidence (1)

packages/edit-site/src/components/post-list/quick-edit-modal.js:186

  • In bulk mode record is set to null (see selector logic), but DataForm always receives data={ { ...record, ...localEdits } }. Spreading null throws at runtime, so bulk Quick Edit will crash. Use record ?? {} (or return {} for record in bulk mode).
				{ hasFinishedResolution && (
					<DataForm
						data={ { ...record, ...localEdits } }
						fields={ fields }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +9
// eslint-disable-next-line @wordpress/use-recommended-components
import { Drawer, VisuallyHidden } from '@wordpress/ui';
Comment on lines +167 to +171
postId.forEach( ( id ) =>
saveEditedEntityRecord( 'postType', postType, id )
);
} else {
await saveEditedEntityRecord( 'postType', postType, postId[ 0 ] );
saveEditedEntityRecord( 'postType', postType, postId[ 0 ] );
Comment on lines 41 to +45
const { record, hasFinishedResolution, canSwitchTemplate } = useSelect(
( select ) => {
if ( postId.length === 0 ) {
return {
record: null,
Comment on lines +236 to +251
* minimal: a visually-hidden `Drawer.Title` (rendered as
* a `<span>` so we don't introduce a second `<h2>` that
* would compete with PostCardPanel's heading) plus the
* close icon. Base UI still wires `aria-labelledby`
* automatically. Tracked follow-up: integrate
* `PostCardPanel` properly with `Drawer.Header`.
*/ }
<VisuallyHidden
render={
<Drawer.Title render={ <span /> }>
{ isBulk
? __( 'Bulk quick edit' )
: __( 'Quick edit' ) }
</Drawer.Title>
}
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Edit Site /packages/edit-site

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants