Migrate Action.RenderModal Cancel buttons from Button to @wordpress/ui Dialog.Action#78032
Migrate Action.RenderModal Cancel buttons from Button to @wordpress/ui Dialog.Action#78032ciampo wants to merge 2 commits into
Action.RenderModal Cancel buttons from Button to @wordpress/ui Dialog.Action#78032Conversation
Now that every consumer of `action.RenderModal` (DataViews item / bulk
actions, edit-site Page Templates, editor PostActions) hosts the
`RenderModal` inside a real `<Dialog.Root>`, internal `RenderModal`s can
swap their `<Button variant="tertiary" onClick={ closeModal }>` Cancel
button for the idiomatic `<Dialog.Action variant="outline">`. The
primary submit/destructive button stays a plain `<Button>` because it
runs async work, surfaces `isBusy`, and decides when to close based on
success/error.
Migrated `RenderModal`s:
- `@wordpress/fields`: `delete-post`, `permanently-delete-post`,
`trash-post`, `reset-post`, `rename-post`, `reorder-page`,
`duplicate-post`.
- `@wordpress/user-taxonomies`: `delete`.
- `@wordpress/user-post-types`: `delete`.
- `@wordpress/editor`: `set-as-homepage`, `set-as-posts-page`.
No public API changes — `RenderModalProps.closeModal` keeps its
plain-callback contract; the inner `Dialog.Action` simply closes via
the surrounding `Dialog.Root`'s `onOpenChange`, which the consumer's
state setter already wires to `closeModal`.
Phase B of the Modal→Dialog migration replaced the Cancel button in
every internal `Action.RenderModal` body with `Dialog.Action`, which
requires a `@wordpress/ui` `Dialog.Root` ancestor at render time.
Every in-tree consumer of `<action.RenderModal>` (DataViews
item-actions, editor `PostActions`, edit-site page-templates) wraps
its content in `Dialog.Root`, so all in-tree call paths are safe.
External consumers that import an action and render its `RenderModal`
outside a `Dialog.Root` will crash with "Dialog parts must be placed
within `<Dialog.Root>`".
Make this contract refinement loud in the diff so it gets explicitly
discussed in PR review:
- Update the `RenderModalProps.RenderModal` JSDoc in
`@wordpress/dataviews` types to spell out the new host requirement.
- Add a short, identical header comment above every migrated
`RenderModal:` declaration (11 files across @wordpress/fields,
@wordpress/user-taxonomies, @wordpress/user-post-types,
@wordpress/editor).
- Add explicit `Breaking Changes` entries in
@wordpress/dataviews, @wordpress/fields, and @wordpress/editor
CHANGELOGs describing the contract narrowing and the upgrade path
("wrap in Dialog.Root or render through an in-tree consumer").
No behaviour change in this commit — purely documentation. The PR
description (see #76837) carries a "heads-up for reviewers" section
calling out the trade-off so it can be discussed and, if needed,
reverted in favour of host-agnostic `<Button onClick={ closeModal }>`
Cancel buttons.
|
Size Change: +81 kB (+1.02%) Total Size: 8.03 MB 📦 View Changed
ℹ️ View Unchanged
|
|
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 “Cancel” controls inside several registered Action.RenderModal implementations from @wordpress/components Button + closeModal() to @wordpress/ui Dialog.Action, and documents a refined host requirement for RenderModal bodies.
Changes:
- Replaced Cancel buttons in multiple in-tree
RenderModalimplementations withDialog.Action(droppingcloseModal()wiring for Cancel). - Documented a new implicit contract:
RenderModaloutput is expected to render under an@wordpress/uiDialog.Roothost (types + changelogs + inline comments). - Added Breaking Change notes in
@wordpress/dataviews,@wordpress/fields, and@wordpress/editorchangelogs.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/user-taxonomies/src/actions/delete.tsx | Uses Dialog.Action for Cancel in taxonomy delete modal. |
| packages/user-post-types/src/actions/delete.tsx | Uses Dialog.Action for Cancel in post type delete modal. |
| packages/fields/src/actions/trash-post.tsx | Uses Dialog.Action for Cancel in trash action modal. |
| packages/fields/src/actions/reset-post.tsx | Uses Dialog.Action for Cancel in reset action modal. |
| packages/fields/src/actions/reorder-page.tsx | Uses Dialog.Action for Cancel in reorder modal. |
| packages/fields/src/actions/rename-post.tsx | Uses Dialog.Action for Cancel in rename modal. |
| packages/fields/src/actions/permanently-delete-post.tsx | Uses Dialog.Action for Cancel in permanent delete modal. |
| packages/fields/src/actions/duplicate-post.tsx | Uses Dialog.Action for Cancel in duplicate modal. |
| packages/fields/src/actions/delete-post.tsx | Uses Dialog.Action for Cancel in delete modal. |
| packages/fields/CHANGELOG.md | Adds Breaking Change + Code Quality notes about the new host requirement and Cancel migration. |
| packages/editor/src/components/post-actions/set-as-posts-page.js | Uses Dialog.Action for Cancel in set-as-posts-page modal. |
| packages/editor/src/components/post-actions/set-as-homepage.js | Uses Dialog.Action for Cancel in set-as-homepage modal. |
| packages/editor/CHANGELOG.md | Adds Breaking Change + Code Quality notes about the new host requirement and Cancel migration. |
| packages/dataviews/src/types/dataviews.ts | Updates RenderModalProps docs and adds RenderModal host requirement JSDoc. |
| packages/dataviews/CHANGELOG.md | Adds Breaking Change note about the Action.RenderModal host contract refinement. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * **Host requirement (since the migration from `@wordpress/components` | ||
| * `Modal` to `@wordpress/ui` `Dialog`):** the rendered output is | ||
| * expected to be mounted inside an `@wordpress/ui` `Dialog.Root` | ||
| * ancestor. All in-tree hosts (`@wordpress/dataviews` action menus, | ||
| * `@wordpress/editor` `PostActions`, `@wordpress/edit-site` page | ||
| * templates) satisfy this. Custom hosts that render `RenderModal` | ||
| * outside a `Dialog.Root` will crash if the body uses Dialog parts | ||
| * such as `Dialog.Action` (Base UI throws "Dialog parts must be | ||
| * placed within `<Dialog.Root>`"). When in doubt, render through one | ||
| * of the in-tree hosts or wrap your custom host in `Dialog.Root`. |
| // `Dialog.Action` (used inside this body) requires a `@wordpress/ui` | ||
| // `Dialog.Root` ancestor at render time. Every in-tree consumer of | ||
| // `Action.RenderModal` provides one; external hosts rendering it | ||
| // outside `Dialog.Root` will crash. See `RenderModalProps` JSDoc. |
| return ( | ||
| <form onSubmit={ onSetPageAsHomepage }> | ||
| <VStack spacing="5"> | ||
| <WCText>{ modalText }</WCText> | ||
| <HStack justify="right"> | ||
| <Button | ||
| __next40pxDefaultSize | ||
| variant="tertiary" | ||
| onClick={ () => { | ||
| closeModal?.(); | ||
| } } | ||
| disabled={ isSaving } | ||
| accessibleWhenDisabled | ||
| > | ||
| <Dialog.Action variant="outline" disabled={ isSaving }> | ||
| { __( 'Cancel' ) } | ||
| </Button> | ||
| </Dialog.Action> |
| <Stack direction="row" justify="flex-end" gap="sm"> | ||
| <Button | ||
| __next40pxDefaultSize | ||
| variant="tertiary" | ||
| onClick={ closeModal } | ||
| disabled={ isDeleting } | ||
| accessibleWhenDisabled | ||
| > | ||
| <Dialog.Action variant="outline" disabled={ isDeleting }> | ||
| { __( 'Cancel' ) } | ||
| </Button> | ||
| </Dialog.Action> | ||
| <Button |
|
|
||
| ### Breaking Changes | ||
|
|
||
| - The `RenderModal` implementations exported from this package (`deletePostAction`, `permanentlyDeletePostAction`, `trashPostAction`, `resetPostAction`, `renamePostAction`, `reorderPageAction`, `duplicatePostAction`) now use `@wordpress/ui` `Dialog.Action` for their Cancel buttons and therefore require a `Dialog.Root` ancestor at render time. All in-tree consumers (`@wordpress/dataviews` item-action menus, `@wordpress/editor` `PostActions`, `@wordpress/edit-site` page-templates) provide one, but plugins or custom UIs that register these actions and render `Action.RenderModal` outside a `Dialog.Root` will crash with "Dialog parts must be placed within `<Dialog.Root>`". Wrap the rendered output in `<Dialog.Root>` or migrate to one of the in-tree consumers. ([#76837](https://github.com/WordPress/gutenberg/pull/76837)) |
| <form onSubmit={ onSetPageAsPostsPage }> | ||
| <VStack spacing="5"> | ||
| <WCText>{ modalText }</WCText> | ||
| <HStack justify="right"> | ||
| <Button | ||
| __next40pxDefaultSize | ||
| variant="tertiary" | ||
| onClick={ () => { | ||
| closeModal?.(); | ||
| } } | ||
| disabled={ isSaving } | ||
| accessibleWhenDisabled | ||
| > | ||
| <Dialog.Action variant="outline" disabled={ isSaving }> | ||
| { __( 'Cancel' ) } | ||
| </Button> | ||
| </Dialog.Action> |
| <Stack direction="row" justify="flex-end" gap="sm"> | ||
| <Button | ||
| __next40pxDefaultSize | ||
| variant="tertiary" | ||
| onClick={ closeModal } | ||
| disabled={ isDeleting } | ||
| accessibleWhenDisabled | ||
| > | ||
| <Dialog.Action variant="outline" disabled={ isDeleting }> | ||
| { __( 'Cancel' ) } | ||
| </Button> | ||
| </Dialog.Action> | ||
| <Button |
|
|
||
| ### Breaking Changes | ||
|
|
||
| - The `RenderModal` implementations registered by this package (`useSetAsHomepageAction`, `useSetAsPostsPageAction`) now use `@wordpress/ui` `Dialog.Action` for their Cancel buttons and therefore require a `Dialog.Root` ancestor at render time. The editor's own `PostActions` component (which is the only in-tree consumer) supplies one. Plugins or custom UIs that consume these action objects and render `Action.RenderModal` outside a `Dialog.Root` will crash with "Dialog parts must be placed within `<Dialog.Root>`". Wrap the rendered output in `<Dialog.Root>` or render through the editor's `PostActions` component. ([#76837](https://github.com/WordPress/gutenberg/pull/76837)) |
|
|
||
| ### Breaking Changes | ||
|
|
||
| - DataViews: `Action.RenderModal` host contract refinement — implementations are now expected to be rendered inside a `@wordpress/ui` `Dialog.Root` ancestor. All in-tree DataViews item-action and DataForm panel-modal hosts already provide one, but custom hosts (e.g. plugins that consume registered entity actions through their own UI shell) that render `RenderModal` outside a `Dialog.Root` will crash with "Dialog parts must be placed within `<Dialog.Root>`" if the body uses Dialog parts such as `Dialog.Action`. The implicit pre-migration contract was "render inside any modal-like host" (`<Modal>` or `<Dialog>` both qualified); the new contract narrows that to "Dialog-shaped host". External callers must wrap their custom host in `<Dialog.Root>`, or migrate to using the in-tree DataViews/editor surfaces. See the `RenderModalProps` JSDoc for details. ([#76837](https://github.com/WordPress/gutenberg/pull/76837)) |
What
Migrates the Cancel buttons inside every in-tree `Action.RenderModal` body (`@wordpress/fields`, `@wordpress/user-taxonomies`, `@wordpress/user-post-types`, plus the editor's set-as-homepage / set-as-posts-page actions) from a `@wordpress/components` `Button` calling `closeModal()` to a `@wordpress/ui` `Dialog.Action`.
`Dialog.Action` requires a `Dialog.Root` ancestor at render time (Base UI throws `"Dialog parts must be placed within `<Dialog.Root>`"` otherwise). All in-tree consumers — DataViews item-action menus, editor `PostActions`, edit-site page-templates duplicate dialog — provide one once #78028, #78030 and #78031 land. Plugins or custom UIs that consume these registered actions and render `Action.RenderModal` outside a `Dialog.Root` will crash.
This refines the implicit contract from "render inside any modal-like host" to "render inside a `Dialog.Root` host", and is documented as a Breaking Change in the `packages/dataviews`, `packages/fields`, and `packages/editor` CHANGELOGs, plus the `RenderModalProps` JSDoc and a 4-line header comment above each migrated `RenderModal` body. Happy to revisit if reviewers prefer the safer "keep `Button onClick`" approach.
Why
Idiomatic `Dialog.Action` is preferable to a manual `Button onClick={closeModal}`: it gets the right default sizing/variant for a dialog footer, integrates with `Dialog`'s focus management, and removes one of the few remaining places where `closeModal()` is invoked from inside `RenderModal` bodies that are by construction always rendered inside a `Dialog` host.
How
For each migrated `RenderModal` (delete-post, permanently-delete-post, trash-post, reset-post, rename-post, reorder-page, duplicate-post, the user-taxonomy delete, the user-post-type delete, set-as-homepage, set-as-posts-page):
Testing instructions
ActionModalandPanelModalfromModalto@wordpress/uiDialog#78028 / Edit Site: Migrate Page Templates duplicate dialog fromModalto@wordpress/uiDialog#78030 / Editor: MigratePostActionsmodal fromModalto@wordpress/uiDialog#78031 merged (or this branch rebased on top of them), exercise each migrated action — Trash, Delete permanently, Reset, Rename, Reorder, Duplicate, Set as homepage / posts page, plus the user taxonomy / post-type delete — from each in-tree surface (DataViews, editor `PostActions`, page-templates duplicate dialog).Context
This is the last of 5 PRs that split #76837. It depends on #78028, #78030 and #78031 being merged first (those land the in-tree `Dialog.Root` hosts that this PR's `Dialog.Action` calls require).