diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
index 4d5cd2303bf..34046ff8587 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
@@ -1,10 +1,11 @@
import {
AlertDialog,
- AlertDialogContent,
+ AlertDialogAction,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
+ EnterEnabledAlertDialogContent,
} from "@superset/ui/alert-dialog";
import { Button } from "@superset/ui/button";
@@ -23,14 +24,9 @@ export function CloseProjectDialog({
onOpenChange,
onConfirm,
}: CloseProjectDialogProps) {
- const handleConfirm = () => {
- onOpenChange(false);
- onConfirm();
- };
-
return (
-
+
Close project "{projectName}"?
@@ -58,16 +54,16 @@ export function CloseProjectDialog({
>
Cancel
-
+
-
+
);
}
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/UnsavedChangesDialog.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/UnsavedChangesDialog.tsx
index 0203efdd70f..38e9d03ef72 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/UnsavedChangesDialog.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/UnsavedChangesDialog.tsx
@@ -2,11 +2,11 @@ import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
- AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
+ EnterEnabledAlertDialogContent,
} from "@superset/ui/alert-dialog";
import { Button } from "@superset/ui/button";
import { LuLoader } from "react-icons/lu";
@@ -34,36 +34,32 @@ export function UnsavedChangesDialog({
discardLabel = "Discard & Continue",
saveLabel = "Save & Continue",
}: UnsavedChangesDialogProps) {
- const handleSaveAndSwitch = (e: React.MouseEvent) => {
- e.preventDefault();
+ const handleSaveAndSwitch = () => {
onSave();
- // Don't close dialog - parent will close on success
};
- const handleDiscardAndSwitch = (e: React.MouseEvent) => {
- e.preventDefault();
+ const handleDiscardAndSwitch = () => {
onDiscard();
- onOpenChange(false);
};
return (
-
+
{title}
{description}
Cancel
-
-
+
+
-
+
);
}
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/DiscardConfirmDialog/DiscardConfirmDialog.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/DiscardConfirmDialog/DiscardConfirmDialog.tsx
index db3841ad710..2055c5aaf83 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/DiscardConfirmDialog/DiscardConfirmDialog.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/DiscardConfirmDialog/DiscardConfirmDialog.tsx
@@ -1,10 +1,11 @@
import {
AlertDialog,
- AlertDialogContent,
+ AlertDialogAction,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
+ EnterEnabledAlertDialogContent,
} from "@superset/ui/alert-dialog";
import { Button } from "@superset/ui/button";
@@ -29,7 +30,7 @@ export function DiscardConfirmDialog({
}: DiscardConfirmDialogProps) {
return (
-
+
{title}
{description}
@@ -43,20 +44,17 @@ export function DiscardConfirmDialog({
>
Cancel
-
+
-
+
);
}
diff --git a/packages/ui/src/components/ui/alert-dialog.tsx b/packages/ui/src/components/ui/alert-dialog.tsx
index ced90f31e70..28bcedd6728 100644
--- a/packages/ui/src/components/ui/alert-dialog.tsx
+++ b/packages/ui/src/components/ui/alert-dialog.tsx
@@ -1,11 +1,16 @@
"use client";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+import type { VariantProps } from "class-variance-authority";
import type * as React from "react";
+import { focusEnterEnabledAlertDialogPrimaryAction } from "../../lib/focus-enter-enabled-alert-dialog-primary-action";
import { cn } from "../../lib/utils";
import { buttonVariants } from "./button";
+const alertDialogContentClassName =
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg";
+
function AlertDialog({
...props
}: React.ComponentProps) {
@@ -53,10 +58,28 @@ function AlertDialogContent({
+
+ );
+}
+
+function EnterEnabledAlertDialogContent({
+ className,
+ onOpenAutoFocus,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {
+ onOpenAutoFocus?.(event);
+ focusEnterEnabledAlertDialogPrimaryAction(event);
+ }}
{...props}
/>
@@ -120,11 +143,15 @@ function AlertDialogDescription({
function AlertDialogAction({
className,
+ size,
+ variant,
...props
-}: React.ComponentProps) {
+}: React.ComponentProps &
+ VariantProps) {
return (
);
@@ -148,6 +175,7 @@ export {
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
+ EnterEnabledAlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
diff --git a/packages/ui/src/lib/focus-enter-enabled-alert-dialog-primary-action.test.ts b/packages/ui/src/lib/focus-enter-enabled-alert-dialog-primary-action.test.ts
new file mode 100644
index 00000000000..efb22a67770
--- /dev/null
+++ b/packages/ui/src/lib/focus-enter-enabled-alert-dialog-primary-action.test.ts
@@ -0,0 +1,72 @@
+import { describe, expect, test } from "bun:test";
+
+import {
+ alertDialogPrimaryActionSelector,
+ focusEnterEnabledAlertDialogPrimaryAction,
+} from "./focus-enter-enabled-alert-dialog-primary-action";
+
+describe("focusEnterEnabledAlertDialogPrimaryAction", () => {
+ test("focuses the alert dialog action and prevents default autofocus", () => {
+ let prevented = false;
+ let focused = false;
+ let queriedSelector: string | null = null;
+
+ focusEnterEnabledAlertDialogPrimaryAction({
+ currentTarget: {
+ querySelector: (selector: string) => {
+ queriedSelector = selector;
+ return {
+ focus: () => {
+ focused = true;
+ },
+ };
+ },
+ },
+ defaultPrevented: false,
+ preventDefault: () => {
+ prevented = true;
+ },
+ });
+
+ expect(String(queriedSelector)).toBe(alertDialogPrimaryActionSelector);
+ expect(prevented).toBe(true);
+ expect(focused).toBe(true);
+ });
+
+ test("does nothing when no primary action is marked", () => {
+ let prevented = false;
+
+ focusEnterEnabledAlertDialogPrimaryAction({
+ currentTarget: {
+ querySelector: () => null,
+ },
+ defaultPrevented: false,
+ preventDefault: () => {
+ prevented = true;
+ },
+ });
+
+ expect(prevented).toBe(false);
+ });
+
+ test("respects an already prevented autofocus event", () => {
+ let queried = false;
+ let prevented = false;
+
+ focusEnterEnabledAlertDialogPrimaryAction({
+ currentTarget: {
+ querySelector: () => {
+ queried = true;
+ return null;
+ },
+ },
+ defaultPrevented: true,
+ preventDefault: () => {
+ prevented = true;
+ },
+ });
+
+ expect(queried).toBe(false);
+ expect(prevented).toBe(false);
+ });
+});
diff --git a/packages/ui/src/lib/focus-enter-enabled-alert-dialog-primary-action.ts b/packages/ui/src/lib/focus-enter-enabled-alert-dialog-primary-action.ts
new file mode 100644
index 00000000000..f5ffb8cf87b
--- /dev/null
+++ b/packages/ui/src/lib/focus-enter-enabled-alert-dialog-primary-action.ts
@@ -0,0 +1,52 @@
+export const alertDialogPrimaryActionSelector =
+ "[data-slot='alert-dialog-action']:not([disabled])";
+
+interface FocusableLike {
+ focus: () => void;
+}
+
+interface EnterEnabledAlertDialogCurrentTargetLike {
+ querySelector: (selector: string) => FocusableLike | null;
+}
+
+type EnterEnabledAlertDialogCurrentTarget =
+ | EnterEnabledAlertDialogCurrentTargetLike
+ | EventTarget
+ | null;
+
+interface EnterEnabledAlertDialogOpenAutoFocusEventLike {
+ currentTarget: EnterEnabledAlertDialogCurrentTarget;
+ defaultPrevented: boolean;
+ preventDefault: () => void;
+}
+
+function isEnterEnabledAlertDialogCurrentTargetLike(
+ target: EnterEnabledAlertDialogCurrentTarget,
+): target is EnterEnabledAlertDialogCurrentTargetLike {
+ return (
+ !!target &&
+ typeof (target as { querySelector?: unknown }).querySelector === "function"
+ );
+}
+
+export function focusEnterEnabledAlertDialogPrimaryAction(
+ event: EnterEnabledAlertDialogOpenAutoFocusEventLike,
+) {
+ if (
+ event.defaultPrevented ||
+ !isEnterEnabledAlertDialogCurrentTargetLike(event.currentTarget)
+ ) {
+ return;
+ }
+
+ const primaryAction = event.currentTarget.querySelector(
+ alertDialogPrimaryActionSelector,
+ );
+
+ if (!primaryAction) {
+ return;
+ }
+
+ event.preventDefault();
+ primaryAction.focus();
+}