mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-30 19:32:46 +08:00
feat(tables): confirm view deletion (#7041)
This commit is contained in:
+63
@@ -125,6 +125,69 @@ describe('ViewsMenu', () => {
|
||||
container.remove()
|
||||
})
|
||||
|
||||
it('confirms before deleting a saved view', () => {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
const root = createRoot(container)
|
||||
const onDelete = vi.fn()
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<ViewsMenu
|
||||
views={[PRIMARY_VIEW, SECOND_VIEW]}
|
||||
activeViewId={PRIMARY_VIEW.id}
|
||||
onSelect={vi.fn()}
|
||||
onRename={vi.fn()}
|
||||
onSetDefault={vi.fn()}
|
||||
onDelete={onDelete}
|
||||
onNewView={vi.fn()}
|
||||
canEdit
|
||||
/>
|
||||
)
|
||||
})
|
||||
act(() => container.querySelector<HTMLButtonElement>('button[aria-label="Views"]')?.click())
|
||||
|
||||
const getDeleteButton = () =>
|
||||
[...document.body.querySelectorAll<HTMLButtonElement>('button[aria-label="Delete"]')].find(
|
||||
(button) => button.getAttribute('aria-disabled') !== 'true'
|
||||
)
|
||||
const getConfirmationDialog = () =>
|
||||
[...document.body.querySelectorAll<HTMLElement>('[role="dialog"]')].find((dialog) =>
|
||||
dialog.textContent?.includes('This action cannot be undone.')
|
||||
)
|
||||
|
||||
act(() => getDeleteButton()?.click())
|
||||
|
||||
expect(onDelete).not.toHaveBeenCalled()
|
||||
const firstDialog = getConfirmationDialog()
|
||||
expect(firstDialog).toHaveTextContent('Delete View')
|
||||
expect(firstDialog).toHaveTextContent('Second view')
|
||||
expect(firstDialog).toHaveTextContent('This action cannot be undone.')
|
||||
|
||||
const cancelButton = [
|
||||
...(firstDialog?.querySelectorAll<HTMLButtonElement>('button') ?? []),
|
||||
].find((button) => button.textContent === 'Cancel')
|
||||
act(() => cancelButton?.click())
|
||||
|
||||
expect(getConfirmationDialog()).toBeUndefined()
|
||||
expect(onDelete).not.toHaveBeenCalled()
|
||||
|
||||
act(() => container.querySelector<HTMLButtonElement>('button[aria-label="Views"]')?.click())
|
||||
act(() => getDeleteButton()?.click())
|
||||
|
||||
const dialog = getConfirmationDialog()
|
||||
const confirmButton = [...(dialog?.querySelectorAll<HTMLButtonElement>('button') ?? [])].find(
|
||||
(button) => button.textContent === 'Delete'
|
||||
)
|
||||
act(() => confirmButton?.click())
|
||||
|
||||
expect(onDelete).toHaveBeenCalledOnce()
|
||||
expect(onDelete).toHaveBeenCalledWith(SECOND_VIEW.id)
|
||||
|
||||
act(() => root.unmount())
|
||||
container.remove()
|
||||
})
|
||||
|
||||
it('keeps the menu open when keyboard focus moves from the trigger to the default pin', () => {
|
||||
vi.useFakeTimers()
|
||||
const container = document.createElement('div')
|
||||
|
||||
+26
-1
@@ -4,6 +4,7 @@ import { memo, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
ChipChevronDown,
|
||||
ChipConfirmModal,
|
||||
chipContentLabelClass,
|
||||
chipVariants,
|
||||
cn,
|
||||
@@ -62,9 +63,11 @@ export const ViewsMenu = memo(function ViewsMenu({
|
||||
canEdit,
|
||||
}: ViewsMenuProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
|
||||
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const { activeView, defaultView } = resolveTableViewSelection(views, activeViewId)
|
||||
const deleteTarget = views.find((view) => view.id === deleteTargetId)
|
||||
const hasDefaultView = defaultView !== null
|
||||
const label = activeView?.name ?? ALL_ROWS_VIEW_LABEL
|
||||
|
||||
@@ -176,7 +179,7 @@ export const ViewsMenu = memo(function ViewsMenu({
|
||||
disabledReason: view.isDefault
|
||||
? 'Default view cannot be deleted'
|
||||
: undefined,
|
||||
onClick: () => runAndClose(() => onDelete(view.id)),
|
||||
onClick: () => runAndClose(() => setDeleteTargetId(view.id)),
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
@@ -199,6 +202,28 @@ export const ViewsMenu = memo(function ViewsMenu({
|
||||
</>
|
||||
)}
|
||||
</PopoverContent>
|
||||
<ChipConfirmModal
|
||||
open={deleteTargetId !== null}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!nextOpen) setDeleteTargetId(null)
|
||||
}}
|
||||
srTitle='Delete View'
|
||||
title='Delete View'
|
||||
text={[
|
||||
'Are you sure you want to delete ',
|
||||
{ text: deleteTarget?.name ?? 'this view', bold: true },
|
||||
'? ',
|
||||
{ text: 'This action cannot be undone.', error: true },
|
||||
]}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: () => {
|
||||
if (!deleteTargetId) return
|
||||
onDelete(deleteTargetId)
|
||||
setDeleteTargetId(null)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user