feat(ui): confirmation modal (#11271)

There are nearly a dozen independent implementations of the same modal
spread throughout the admin panel and various plugins. These modals are
used to confirm or cancel an action, such as deleting a document, bulk
publishing, etc. Each of these instances is nearly identical, leading to
unnecessary development efforts when creating them, inconsistent UI, and
duplicative stylesheets.

Everything is now standardized behind a new `ConfirmationModal`
component. This modal comes with a standard API that is flexible enough
to replace nearly every instance. This component has also been exported
for reuse.

Here is a basic example of how to use it:

```tsx
'use client'
import { ConfirmationModal, useModal } from '@payloadcms/ui'
import React, { Fragment } from 'react'

const modalSlug = 'my-confirmation-modal'

export function MyComponent() {
  const { openModal } = useModal()

  return (
    <Fragment>
      <button
        onClick={() => {
          openModal(modalSlug)
        }}
        type="button"
      >
        Do something
      </button>
      <ConfirmationModal
        heading="Are you sure?"
        body="Confirm or cancel before proceeding."
        modalSlug={modalSlug}
        onConfirm={({ closeConfirmationModal, setConfirming }) => {
          // do something
          setConfirming(false)
          closeConfirmationModal()
        }}
      />
    </Fragment>
  )
}
```
This commit is contained in:
Jacob Fletcher
2025-02-19 02:27:03 -05:00
committed by GitHub
parent 132852290a
commit bd8ced1b60
41 changed files with 819 additions and 1364 deletions

View File

@@ -223,7 +223,7 @@ describe('Locked Documents', () => {
await page.goto(postsUrl.list)
await page.locator('input#select-all').check()
await page.locator('.delete-documents__toggle').click()
await expect(page.locator('.delete-documents__content p')).toHaveText(
await expect(page.locator('#delete-posts .confirmation-modal__content p')).toHaveText(
'You are about to delete 2 Posts',
)
})
@@ -243,7 +243,7 @@ describe('Locked Documents', () => {
await page.locator('input#select-all').check()
await page.locator('.list-selection .list-selection__button').click()
await page.locator('.delete-documents__toggle').click()
await page.locator('#confirm-delete').click()
await page.locator('#delete-posts #confirm-action').click()
await expect(page.locator('.cell-_select')).toHaveCount(1)
})
@@ -257,12 +257,11 @@ describe('Locked Documents', () => {
await page.reload()
await page.goto(postsUrl.list)
await page.waitForURL(new RegExp(postsUrl.list))
await page.locator('input#select-all').check()
await page.locator('.list-selection .list-selection__button').click()
await page.locator('.publish-many__toggle').click()
await page.locator('#confirm-publish').click()
await page.locator('#publish-posts #confirm-action').click()
const paginator = page.locator('.paginator')
@@ -273,12 +272,11 @@ describe('Locked Documents', () => {
test('should only allow bulk unpublish on unlocked documents on all pages', async () => {
await page.goto(postsUrl.list)
await page.waitForURL(new RegExp(postsUrl.list))
await page.locator('input#select-all').check()
await page.locator('.list-selection .list-selection__button').click()
await page.locator('.unpublish-many__toggle').click()
await page.locator('#confirm-unpublish').click()
await page.locator('#unpublish-posts #confirm-action').click()
await expect(page.locator('.payload-toast-container .toast-success')).toHaveText(
'Updated 10 Posts successfully.',
)
@@ -568,7 +566,9 @@ describe('Locked Documents', () => {
await expect(modalContainer).toBeVisible()
// Click the "Leave anyway" button
await page.locator('.leave-without-saving__controls .btn--style-primary').click()
await page
.locator('#leave-without-saving .confirmation-modal__controls .btn--style-primary')
.click()
// eslint-disable-next-line payload/no-wait-function
await wait(500)
@@ -620,7 +620,9 @@ describe('Locked Documents', () => {
await expect(modalContainer).toBeVisible()
// Click the "Leave anyway" button
await page.locator('.leave-without-saving__controls .btn--style-primary').click()
await page
.locator('#leave-without-saving .confirmation-modal__controls .btn--style-primary')
.click()
// eslint-disable-next-line payload/no-wait-function
await wait(500)