From 19aea11aa9dc51580141a11b65ca58d8cd6c53ff Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 24 Mar 2026 16:33:20 +0800 Subject: [PATCH] Add cancel action in save queue action bar (#44065) ## Context Related to the queue table operations feature preview Adding a "cancel" action in the save queue operation bar for convenience to clear all changes (instead of having to go into the review panel) Also aligning the positioning of the CTAs to match the review panel - "Review" imo is a secondary action, while "Save" or "Cancel" are the primary ones - Hence am shifting the "review" CTA to the left, contextually beside the number of pending changes text image - This also aligns with the CTA placements in the review panel image - Also removed plural grammar for the button CTAs - thinking thats not necessary, wanna keep button CTA texts short and sweet + The "x pending change(s)" also captures the plurality --- .../footer/operations/SaveQueueActionBar.tsx | 99 +++++++++++-------- .../OperationQueueSidePanel.tsx | 4 +- .../features/queue-table-operations.spec.ts | 6 +- 3 files changed, 65 insertions(+), 44 deletions(-) diff --git a/apps/studio/components/grid/components/footer/operations/SaveQueueActionBar.tsx b/apps/studio/components/grid/components/footer/operations/SaveQueueActionBar.tsx index ee5d6cf1f66..158dd38a5d0 100644 --- a/apps/studio/components/grid/components/footer/operations/SaveQueueActionBar.tsx +++ b/apps/studio/components/grid/components/footer/operations/SaveQueueActionBar.tsx @@ -7,13 +7,15 @@ import { createPortal } from 'react-dom' import { useTableEditorStateSnapshot } from 'state/table-editor' import { Button } from 'ui' +import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog' +import { useConfirmOnClose } from '@/hooks/ui/useConfirmOnClose' import { getModKeyLabel } from '@/lib/helpers' export const SaveQueueActionBar = () => { const modKey = getModKeyLabel() const snap = useTableEditorStateSnapshot() const isQueueOperationsEnabled = useIsQueueOperationsEnabled() - const { handleSave } = useOperationQueueActions() + const { handleSave, handleCancel } = useOperationQueueActions() useOperationQueueShortcuts() @@ -24,46 +26,65 @@ export const SaveQueueActionBar = () => { const isVisible = isQueueOperationsEnabled && snap.hasPendingOperations && !isOperationQueuePanelOpen + const { confirmOnClose, modalProps: closeConfirmationModalProps } = useConfirmOnClose({ + checkIsDirty: () => true, + onClose: () => handleCancel(), + }) + const content = ( - - {isVisible && ( -
- -
- - {operationCount} pending change{operationCount !== 1 ? 's' : ''} - -
- - + <> + + {isVisible && ( +
+ +
+
+ + {operationCount} pending change{operationCount !== 1 ? 's' : ''} + + +
+
+ + +
-
- -
- )} - + +
+ )} + + + + ) if (typeof document === 'undefined' || !document.body) return null diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/OperationQueueSidePanel/OperationQueueSidePanel.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/OperationQueueSidePanel/OperationQueueSidePanel.tsx index f0314fd96e4..5d154be3307 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/OperationQueueSidePanel/OperationQueueSidePanel.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/OperationQueueSidePanel/OperationQueueSidePanel.tsx @@ -63,14 +63,14 @@ export const OperationQueueSidePanel = () => { onClick={confirmOnClose} disabled={isSaving || operations.length === 0} > - Revert{operations.length > 1 && ' all'} + Cancel
diff --git a/e2e/studio/features/queue-table-operations.spec.ts b/e2e/studio/features/queue-table-operations.spec.ts index b237580bbe5..ea835fb77c5 100644 --- a/e2e/studio/features/queue-table-operations.spec.ts +++ b/e2e/studio/features/queue-table-operations.spec.ts @@ -103,7 +103,7 @@ test.describe('Queue Table Operations', () => { await expect(page.getByText('1 pending change')).toBeVisible() await page.getByRole('button', { name: /Review/ }).click() - await page.getByRole('button', { name: 'Revert', exact: true }).click() + await page.getByRole('button', { name: 'Cancel', exact: true }).click() const confirmDialog = page.getByRole('alertdialog') await expect(confirmDialog.getByRole('heading', { name: 'Unsaved changes' })).toBeVisible() @@ -145,7 +145,7 @@ test.describe('Queue Table Operations', () => { await expect(page.getByText('1 pending change')).toBeVisible() await page.getByRole('button', { name: /Review/ }).click() - await page.getByRole('button', { name: 'Revert', exact: true }).click() + await page.getByRole('button', { name: 'Cancel', exact: true }).click() const confirmDialog = page.getByRole('alertdialog') await expect(confirmDialog.getByRole('heading', { name: 'Unsaved changes' })).toBeVisible() @@ -561,7 +561,7 @@ test.describe('Queue Table Operations', () => { await expect(page.getByText('1 pending change')).toBeVisible() await page.getByRole('button', { name: /Review/ }).click() - await page.getByRole('button', { name: 'Revert', exact: true }).click() + await page.getByRole('button', { name: 'Cancel', exact: true }).click() const confirmDialog = page.getByRole('alertdialog') await expect(confirmDialog.getByRole('heading', { name: 'Unsaved changes' })).toBeVisible()