diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.test.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.test.tsx
index 23a5c633d0e..3f57c4f3ee0 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.test.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.test.tsx
@@ -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(
+
+ )
+ })
+ act(() => container.querySelector('button[aria-label="Views"]')?.click())
+
+ const getDeleteButton = () =>
+ [...document.body.querySelectorAll('button[aria-label="Delete"]')].find(
+ (button) => button.getAttribute('aria-disabled') !== 'true'
+ )
+ const getConfirmationDialog = () =>
+ [...document.body.querySelectorAll('[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('button') ?? []),
+ ].find((button) => button.textContent === 'Cancel')
+ act(() => cancelButton?.click())
+
+ expect(getConfirmationDialog()).toBeUndefined()
+ expect(onDelete).not.toHaveBeenCalled()
+
+ act(() => container.querySelector('button[aria-label="Views"]')?.click())
+ act(() => getDeleteButton()?.click())
+
+ const dialog = getConfirmationDialog()
+ const confirmButton = [...(dialog?.querySelectorAll('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')
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.tsx
index 04cb945c8fb..cffa4a09d1f 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.tsx
@@ -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(null)
const closeTimeoutRef = useRef | 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({
>
)}
+ {
+ 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)
+ },
+ }}
+ />
)
})