Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore!: move confirm dialog to admin package and refactor
- Loading branch information
1 parent
a77bb0a
commit 9b76487
Showing
33 changed files
with
440 additions
and
566 deletions.
There are no files selected for viewing
147 changes: 147 additions & 0 deletions
147
packages/core/admin/admin/src/components/ConfirmDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import * as React from 'react'; | ||
|
||
import { | ||
Button, | ||
ButtonProps, | ||
Dialog, | ||
DialogBody, | ||
DialogFooter, | ||
Flex, | ||
Typography, | ||
DialogBodyProps, | ||
DialogProps, | ||
DialogFooterProps, | ||
} from '@strapi/design-system'; | ||
import { ExclamationMarkCircle } from '@strapi/icons'; | ||
import { useIntl } from 'react-intl'; | ||
|
||
/* ------------------------------------------------------------------------------------------------- | ||
* ConfirmDialog | ||
* -----------------------------------------------------------------------------------------------*/ | ||
interface ConfirmDialogProps | ||
extends Omit<DialogProps, 'title'>, | ||
Partial<Pick<DialogProps, 'title'>>, | ||
Pick<ButtonProps, 'variant'>, | ||
Partial<DialogFooterProps>, | ||
Pick<DialogBodyProps, 'icon'> { | ||
onConfirm?: () => Promise<void> | void; | ||
} | ||
|
||
/** | ||
* @beta | ||
* @public | ||
* @description A simple confirm dialog that out of the box can be used to confirm an action. | ||
* The component can additionally be customised if required e.g. the footer actions can be | ||
* completely replaced, but cannot be removed. Passing a string as the children prop will render | ||
* the string as the body of the dialog. If you need more control over the body, you can pass a | ||
* custom component as the children prop. | ||
* @example | ||
* ```tsx | ||
* const DeleteAction = ({ id }) => { | ||
* const [isOpen, setIsOpen] = React.useState(false); | ||
* | ||
* const [delete] = useDeleteMutation() | ||
* const handleConfirm = async () => { | ||
* await delete(id) | ||
* } | ||
* | ||
* return ( | ||
* <> | ||
* <Button onClick={() => setIsOpen(true)}>Delete</Button> | ||
* <ConfirmDialog onConfirm={handleConfirm} onClose={() => setIsOpen(false)} isOpen={isOpen} /> | ||
* </> | ||
* ) | ||
* } | ||
* ``` | ||
*/ | ||
const ConfirmDialog = ({ | ||
children, | ||
icon = <ExclamationMarkCircle />, | ||
onClose, | ||
onConfirm, | ||
variant = 'danger', | ||
startAction, | ||
endAction, | ||
...props | ||
}: ConfirmDialogProps) => { | ||
const { formatMessage } = useIntl(); | ||
const [isConfirming, setIsConfirming] = React.useState(false); | ||
|
||
const content = | ||
children || | ||
formatMessage({ | ||
id: 'app.confirm.body', | ||
defaultMessage: 'Are you sure?', | ||
}); | ||
|
||
const handleConfirm = async () => { | ||
if (!onConfirm) { | ||
return; | ||
} | ||
|
||
try { | ||
setIsConfirming(true); | ||
await onConfirm(); | ||
onClose(); | ||
} finally { | ||
setIsConfirming(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<Dialog | ||
title={formatMessage({ | ||
id: 'app.components.ConfirmDialog.title', | ||
defaultMessage: 'Confirmation', | ||
})} | ||
onClose={onClose} | ||
{...props} | ||
> | ||
<DialogBody icon={icon}> | ||
{typeof content === 'string' ? <DefaultBodyWrapper>{content}</DefaultBodyWrapper> : content} | ||
</DialogBody> | ||
<DialogFooter | ||
startAction={ | ||
startAction || ( | ||
<Button onClick={onClose} variant="tertiary"> | ||
{formatMessage({ | ||
id: 'app.components.Button.cancel', | ||
defaultMessage: 'Cancel', | ||
})} | ||
</Button> | ||
) | ||
} | ||
endAction={ | ||
endAction || ( | ||
<Button onClick={handleConfirm} variant={variant} loading={isConfirming}> | ||
{formatMessage({ | ||
id: 'app.components.Button.confirm', | ||
defaultMessage: 'Confirm', | ||
})} | ||
</Button> | ||
) | ||
} | ||
/> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
/* ------------------------------------------------------------------------------------------------- | ||
* DefaultBodyWrapper | ||
* -----------------------------------------------------------------------------------------------*/ | ||
interface DefaultBodyWrapperProps { | ||
children: React.ReactNode; | ||
} | ||
|
||
const DefaultBodyWrapper = ({ children }: DefaultBodyWrapperProps) => { | ||
return ( | ||
<Flex direction="column" alignItems="stretch" gap={2}> | ||
<Flex justifyContent="center"> | ||
<Typography variant="omega">{children}</Typography> | ||
</Flex> | ||
</Flex> | ||
); | ||
}; | ||
|
||
export { ConfirmDialog }; | ||
export type { ConfirmDialogProps }; |
72 changes: 72 additions & 0 deletions
72
packages/core/admin/admin/src/components/tests/ConfirmDialog.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { render, screen } from '@tests/utils'; | ||
|
||
import { ConfirmDialog } from '../ConfirmDialog'; | ||
|
||
describe('ConfirmDialog', () => { | ||
it('should render the ConfirmDialog with bare minimal props', () => { | ||
render(<ConfirmDialog onConfirm={() => {}} onClose={() => {}} isOpen={true} />); | ||
|
||
expect(screen.getByRole('dialog', { name: 'Confirmation' })).toBeInTheDocument(); | ||
expect(screen.getByRole('heading', { name: 'Confirmation' })).toBeInTheDocument(); | ||
expect(screen.getByText('Are you sure?')).toBeInTheDocument(); | ||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); | ||
expect(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument(); | ||
}); | ||
|
||
it("should call onConfirm and onClose when the 'Confirm' button is clicked", async () => { | ||
const onConfirm = jest.fn(); | ||
const onClose = jest.fn(); | ||
const { user } = render( | ||
<ConfirmDialog onConfirm={onConfirm} onClose={onClose} isOpen={true} /> | ||
); | ||
|
||
await user.click(screen.getByRole('button', { name: 'Confirm' })); | ||
|
||
expect(onConfirm).toBeCalled(); | ||
expect(onClose).toBeCalled(); | ||
}); | ||
|
||
it("should call onClose when the 'Cancel' button is clicked", async () => { | ||
const onClose = jest.fn(); | ||
const { user } = render(<ConfirmDialog onConfirm={() => {}} onClose={onClose} isOpen={true} />); | ||
|
||
await user.click(screen.getByRole('button', { name: 'Cancel' })); | ||
|
||
expect(onClose).toBeCalled(); | ||
}); | ||
|
||
it('should not render if isOpen is false', () => { | ||
render(<ConfirmDialog onConfirm={() => {}} onClose={() => {}} isOpen={false} />); | ||
|
||
expect(screen.queryByRole('dialog', { name: 'Confirmation' })).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('should let me change the title', () => { | ||
render( | ||
<ConfirmDialog onConfirm={() => {}} onClose={() => {}} isOpen={true} title="Woh there kid!" /> | ||
); | ||
|
||
expect(screen.getByRole('dialog', { name: 'Woh there kid!' })).toBeInTheDocument(); | ||
expect(screen.getByRole('heading', { name: 'Woh there kid!' })).toBeInTheDocument(); | ||
}); | ||
|
||
it('should let me change the content', () => { | ||
render( | ||
<ConfirmDialog onConfirm={() => {}} onClose={() => {}} isOpen={true}> | ||
{"Well, i'd be careful if I were you."} | ||
</ConfirmDialog> | ||
); | ||
|
||
expect(screen.getByText("Well, i'd be careful if I were you.")).toBeInTheDocument(); | ||
}); | ||
|
||
it('should let me render a complete custom body', () => { | ||
render( | ||
<ConfirmDialog onConfirm={() => {}} onClose={() => {}} isOpen={true}> | ||
<h2>WARNING</h2> | ||
</ConfirmDialog> | ||
); | ||
|
||
expect(screen.getByRole('heading', { name: 'WARNING' })).toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.