Edit Site: Migrate Quick Edit panel from Modal to @wordpress/ui Drawer#78029
Edit Site: Migrate Quick Edit panel from Modal to @wordpress/ui Drawer#78029ciampo wants to merge 7 commits into
Modal to @wordpress/ui Drawer#78029Conversation
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.
|
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.
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. |
There was a problem hiding this comment.
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 withDrawer.Root/Drawer.Popup/Drawer.Content/Drawer.FooterandDrawer.Action. - Always mount
QuickEditModaland drive visibility via anopenprop 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
recordis set tonull(see selector logic), butDataFormalways receivesdata={ { ...record, ...localEdits } }. Spreadingnullthrows at runtime, so bulk Quick Edit will crash. Userecord ?? {}(or return{}forrecordin bulk mode).
{ hasFinishedResolution && (
<DataForm
data={ { ...record, ...localEdits } }
fields={ fields }
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // eslint-disable-next-line @wordpress/use-recommended-components | ||
| import { Drawer, VisuallyHidden } from '@wordpress/ui'; |
| postId.forEach( ( id ) => | ||
| saveEditedEntityRecord( 'postType', postType, id ) | ||
| ); | ||
| } else { | ||
| await saveEditedEntityRecord( 'postType', postType, postId[ 0 ] ); | ||
| saveEditedEntityRecord( 'postType', postType, postId[ 0 ] ); |
| const { record, hasFinishedResolution, canSwitchTemplate } = useSelect( | ||
| ( select ) => { | ||
| if ( postId.length === 0 ) { | ||
| return { | ||
| record: null, |
| * 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> | ||
| } | ||
| /> |
What
Migrates the Quick Edit panel in Edit Site's posts/pages list from
@wordpress/componentsModal(with custom drawer-like CSS) to the new@wordpress/uiDrawerprimitive.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
ModalwithDrawer.Root+Drawer.Popup, kept mounted in the React tree and driven byopen/onOpenChangeso animations play.onOpenChangeCompleteso the form doesn't flash empty during the exit animation; key the inner session onpostType + postIdso re-opening on a different row resetsuseSelectcleanly.PostCardPanelinsideDrawer.Content(rather thanDrawer.Header) and keep the header to aVisuallyHidden<span>Drawer.Titleplus the close icon, sincePostCardPanelalready renders its own<h2>and is laid out as a vertical stack. A follow-up will integratePostCardPanelproperly withDrawer.Header.post-list/style.scss.Testing instructions
Context
This is one of 5 PRs that split #76837. Independent of the others.