diff --git a/.changeset/clean-cameras-hunt.md b/.changeset/clean-cameras-hunt.md new file mode 100644 index 000000000..e796094ed --- /dev/null +++ b/.changeset/clean-cameras-hunt.md @@ -0,0 +1,38 @@ +--- +"@cube-dev/ui-kit": minor +--- + +**Breaking Change:** AlertDialog API cancel button behavior changed + +The `cancel` button in AlertDialog now rejects the promise instead of resolving with `'cancel'` status, aligning it with the dismiss (Escape key) behavior. + +**Migration Guide:** + +**Before:** +```typescript +alertDialogAPI.open({...}) + .then((status) => { + if (status === 'cancel') { + // Handle cancel + } else if (status === 'confirm') { + // Handle confirm + } + }) +``` + +**After:** +```typescript +alertDialogAPI.open({...}) + .then((status) => { + if (status === 'confirm') { + // Handle confirm + } else if (status === 'secondary') { + // Handle secondary action + } + }) + .catch(() => { + // Handle cancel or dismiss + }) +``` + +**Note:** `AlertDialogResolveStatus` type no longer includes `'cancel'` - it now only contains `'confirm' | 'secondary'`. diff --git a/src/components/overlays/AlertDialog/AlertDialogApiProvider.tsx b/src/components/overlays/AlertDialog/AlertDialogApiProvider.tsx index 1af8af6db..7ce52d455 100644 --- a/src/components/overlays/AlertDialog/AlertDialogApiProvider.tsx +++ b/src/components/overlays/AlertDialog/AlertDialogApiProvider.tsx @@ -129,7 +129,19 @@ export function AlertDialogApiProvider(props) { * cancelToken: abortDialog.signal * }); * - * openedDialog.then(() => console.log('closed')) + * openedDialog + * .then((status) => { + * // User confirmed or used secondary action + * if (status === 'confirm') { + * // Handle confirm + * } else if (status === 'secondary') { + * // Handle secondary action + * } + * }) + * .catch(() => { + * // User cancelled or dismissed the dialog + * // Handle cancel/dismiss + * }) * * return () => { * abortDialog.abort(); @@ -141,6 +153,14 @@ export function AlertDialogApiProvider(props) { * * const onPress = useCallback(() => { * alertDialogAPI.open({...}) + * .then((status) => { + * if (status === 'confirm') { + * // Handle confirm + * } + * }) + * .catch(() => { + * // Handle cancel/dismiss + * }) * }, []) * * return diff --git a/src/components/overlays/AlertDialog/AlertDialogZone.tsx b/src/components/overlays/AlertDialog/AlertDialogZone.tsx index 4d0ca86c7..1ad34d040 100644 --- a/src/components/overlays/AlertDialog/AlertDialogZone.tsx +++ b/src/components/overlays/AlertDialog/AlertDialogZone.tsx @@ -64,7 +64,20 @@ export function AlertDialogZone(props: DialogZoneProps) { return { confirm: mergeActionProps(actions.confirm, 'confirm'), secondary: mergeActionProps(actions.secondary, 'secondary'), - cancel: mergeActionProps(actions.cancel, 'cancel'), + cancel: + typeof actions.cancel === 'undefined' + ? undefined + : typeof actions.cancel === 'boolean' + ? actions.cancel + ? { onPress: () => reject(undefined) } + : false + : { + ...(actions.cancel as CubeButtonProps), + onPress: (e) => { + (actions.cancel as CubeButtonProps).onPress?.(e); + reject(undefined); + }, + }, }; })(); diff --git a/src/components/overlays/AlertDialog/tests/use-alert-dialog-api.test.tsx b/src/components/overlays/AlertDialog/tests/use-alert-dialog-api.test.tsx index 223810db5..5d411a0ff 100644 --- a/src/components/overlays/AlertDialog/tests/use-alert-dialog-api.test.tsx +++ b/src/components/overlays/AlertDialog/tests/use-alert-dialog-api.test.tsx @@ -89,4 +89,70 @@ describe('useAlertDialogApi()', () => { expect(onReject).toHaveBeenCalled(); await expect(dialogPromise).rejects.toEqual(undefined); }); + + it('should reject when cancel button (boolean) is clicked', async () => { + const { getByRole } = renderWithRoot( + , + ); + const showDialogButton = getByRole('button', { name: 'Open Dialog' }); + + await userEvent.click(showDialogButton); + + const cancelButton = getByRole('button', { name: 'Cancel' }); + + await userEvent.click(cancelButton); + + expect(onReject).toHaveBeenCalled(); + await expect(dialogPromise).rejects.toEqual(undefined); + }); + + it('should reject when cancel button (object) is clicked', async () => { + const { getByRole } = renderWithRoot( + , + ); + const showDialogButton = getByRole('button', { name: 'Open Dialog' }); + + await userEvent.click(showDialogButton); + + const cancelButton = getByRole('button', { name: 'No thanks' }); + + await userEvent.click(cancelButton); + + expect(onReject).toHaveBeenCalled(); + await expect(dialogPromise).rejects.toEqual(undefined); + }); + + it('should resolve when confirm button is clicked', async () => { + const { getByRole } = renderWithRoot( + , + ); + const showDialogButton = getByRole('button', { name: 'Open Dialog' }); + + await userEvent.click(showDialogButton); + + const confirmButton = getByRole('button', { name: 'Ok' }); + + await userEvent.click(confirmButton); + + expect(onResolve).toHaveBeenCalledWith('confirm'); + await expect(dialogPromise).resolves.toEqual('confirm'); + }); + + it('should resolve when secondary button is clicked', async () => { + const { getByRole } = renderWithRoot( + , + ); + const showDialogButton = getByRole('button', { name: 'Open Dialog' }); + + await userEvent.click(showDialogButton); + + const secondaryButton = getByRole('button', { name: 'Later' }); + + await userEvent.click(secondaryButton); + + expect(onResolve).toHaveBeenCalledWith('secondary'); + await expect(dialogPromise).resolves.toEqual('secondary'); + }); }); diff --git a/src/components/overlays/AlertDialog/types.ts b/src/components/overlays/AlertDialog/types.ts index 4dbf08e43..0c5029959 100644 --- a/src/components/overlays/AlertDialog/types.ts +++ b/src/components/overlays/AlertDialog/types.ts @@ -22,7 +22,7 @@ export interface DialogProps content: ReactNode | (({ resolve, reject }) => ReactNode); } -export type AlertDialogResolveStatus = 'confirm' | 'cancel' | 'secondary'; +export type AlertDialogResolveStatus = 'confirm' | 'secondary'; interface AlertDialogMeta { id: number;