mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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 <img width="449" height="100" alt="image" src="https://github.com/user-attachments/assets/c3faa6c1-e244-40ee-b251-44ab1e785c6e" /> - This also aligns with the CTA placements in the review panel <img width="502" height="71" alt="image" src="https://github.com/user-attachments/assets/35b7de0a-dbf4-4e8a-acef-53508c9b13b9" /> - 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
This commit is contained in:
1 parent
98b1b79909
commit
19aea11aa9
3 files changed
+65
-44
No files matched your search
@@ -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 = (
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<div className="fixed bottom-12 z-50 left-1/2 -translate-x-1/2">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 16 }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 420,
|
||||
damping: 30,
|
||||
mass: 0.4,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-8 pl-4 pr-2 py-2 bg-surface-100 border rounded-lg shadow-lg">
|
||||
<span className="text-xs text-foreground-light max-w-40 truncate">
|
||||
{operationCount} pending change{operationCount !== 1 ? 's' : ''}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button type="default" size="tiny" onClick={() => snap.toggleViewOperationQueue()}>
|
||||
Review{' '}
|
||||
<span className="text-[10px] text-foreground/40 ml-1.5">{`${modKey}.`}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="tiny"
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
loading={isSaving}
|
||||
>
|
||||
Save{operationCount > 1 && ' all'}
|
||||
<span className="text-[10px] text-foreground/40 ml-1.5">{`${modKey}S`}</span>
|
||||
</Button>
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<div className="fixed bottom-12 z-50 left-1/2 -translate-x-1/2">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 16 }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 420,
|
||||
damping: 30,
|
||||
mass: 0.4,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-x-12 pl-4 pr-2 py-2 bg-surface-100 border rounded-lg shadow-lg">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<span className="text-xs text-foreground-light max-w-40 truncate">
|
||||
{operationCount} pending change{operationCount !== 1 ? 's' : ''}
|
||||
</span>
|
||||
<Button
|
||||
type="default"
|
||||
size="tiny"
|
||||
disabled={isSaving}
|
||||
onClick={() => snap.toggleViewOperationQueue()}
|
||||
>
|
||||
Review{' '}
|
||||
<span className="text-[10px] text-foreground/40 ml-1.5">{`${modKey}.`}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Button type="default" onClick={confirmOnClose} disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="tiny"
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
loading={isSaving}
|
||||
>
|
||||
Save
|
||||
<span className="text-[10px] text-foreground/40 ml-1.5">{`${modKey}S`}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<DiscardChangesConfirmationDialog {...closeConfirmationModalProps} />
|
||||
</>
|
||||
)
|
||||
|
||||
if (typeof document === 'undefined' || !document.body) return null
|
||||
|
||||
+2
-2
@@ -63,14 +63,14 @@ export const OperationQueueSidePanel = () => {
|
||||
onClick={confirmOnClose}
|
||||
disabled={isSaving || operations.length === 0}
|
||||
>
|
||||
Revert{operations.length > 1 && ' all'}
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || operations.length === 0}
|
||||
loading={isSaving}
|
||||
>
|
||||
Save{operations.length > 1 && ' all'}
|
||||
Save
|
||||
<span className="text-foreground/40 text-[10px] ml-1.5">{modKey}S</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user