mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 16:12:30 +00:00
fix(webui): polish delete dialog and sidebar toggles
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
cbd5b06075
commit
451d740849
@ -8,6 +8,7 @@ import {
|
|||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
|
import { Trash2 } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface DeleteConfirmProps {
|
interface DeleteConfirmProps {
|
||||||
@ -26,22 +27,32 @@ export function DeleteConfirm({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<AlertDialog open={open} onOpenChange={(o) => (!o ? onCancel() : undefined)}>
|
<AlertDialog open={open} onOpenChange={(o) => (!o ? onCancel() : undefined)}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent
|
||||||
<AlertDialogHeader>
|
className="w-[min(calc(100vw-2rem),22.75rem)] gap-0 rounded-[28px] border border-white/70 bg-card/95 p-5 text-center shadow-[0_24px_80px_rgba(15,23,42,0.20)] backdrop-blur-xl data-[state=open]:zoom-in-95 sm:rounded-[28px]"
|
||||||
<AlertDialogTitle>
|
>
|
||||||
|
<AlertDialogHeader className="items-center space-y-0 text-center">
|
||||||
|
<div className="mb-5 grid h-16 w-16 place-items-center rounded-full bg-destructive/10 text-destructive">
|
||||||
|
<div className="grid h-9 w-9 place-items-center rounded-full border border-destructive/20 bg-destructive/5">
|
||||||
|
<Trash2 className="h-5 w-5" strokeWidth={2.4} aria-hidden />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AlertDialogTitle className="text-center text-[20px] font-semibold leading-tight tracking-[-0.02em] text-foreground">
|
||||||
{t("deleteConfirm.title", { title })}
|
{t("deleteConfirm.title", { title })}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription className="mt-3 max-w-[17rem] text-center text-[14px] leading-6 text-muted-foreground">
|
||||||
{t("deleteConfirm.description")}
|
{t("deleteConfirm.description")}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter className="mt-7 grid grid-cols-2 gap-3 space-x-0">
|
||||||
<AlertDialogCancel onClick={onCancel}>
|
<AlertDialogCancel
|
||||||
|
onClick={onCancel}
|
||||||
|
className="mt-0 h-11 rounded-full border-0 bg-muted/70 px-5 text-[15px] font-semibold text-foreground shadow-none hover:bg-muted"
|
||||||
|
>
|
||||||
{t("deleteConfirm.cancel")}
|
{t("deleteConfirm.cancel")}
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
className="h-11 rounded-full bg-destructive px-5 text-[15px] font-semibold text-destructive-foreground shadow-[0_10px_25px_rgba(239,68,68,0.28)] hover:bg-destructive/90"
|
||||||
>
|
>
|
||||||
{t("deleteConfirm.confirm")}
|
{t("deleteConfirm.confirm")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
PanelLeftClose,
|
Menu,
|
||||||
Search,
|
Search,
|
||||||
SquarePen,
|
SquarePen,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@ -65,7 +65,7 @@ export function Sidebar(props: SidebarProps) {
|
|||||||
onClick={props.onCollapse}
|
onClick={props.onCollapse}
|
||||||
className="h-7 w-7 rounded-lg text-muted-foreground/85 hover:bg-sidebar-accent/75 hover:text-sidebar-foreground"
|
className="h-7 w-7 rounded-lg text-muted-foreground/85 hover:bg-sidebar-accent/75 hover:text-sidebar-foreground"
|
||||||
>
|
>
|
||||||
<PanelLeftClose className="h-3.5 w-3.5" />
|
<Menu className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Menu, Moon, PanelLeftOpen, Settings, Sun } from "lucide-react";
|
import { Menu, Moon, Settings, Sun } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -80,7 +80,7 @@ export function ThreadHeader({
|
|||||||
hideSidebarToggleOnDesktop && "lg:pointer-events-none lg:opacity-0",
|
hideSidebarToggleOnDesktop && "lg:pointer-events-none lg:opacity-0",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<PanelLeftOpen className="h-3.5 w-3.5" />
|
<Menu className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex min-w-0 items-center rounded-md px-1.5 py-1 text-[12px] font-medium text-muted-foreground">
|
<div className="flex min-w-0 items-center rounded-md px-1.5 py-1 text-[12px] font-medium text-muted-foreground">
|
||||||
<span className="max-w-[min(60vw,32rem)] truncate">{title}</span>
|
<span className="max-w-[min(60vw,32rem)] truncate">{title}</span>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const AlertDialogOverlay = React.forwardRef<
|
|||||||
<AlertDialogPrimitive.Overlay
|
<AlertDialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
"fixed inset-0 z-50 bg-background/45 backdrop-blur-[3px] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -29,14 +29,16 @@ const AlertDialogContent = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<AlertDialogOverlay />
|
<AlertDialogOverlay />
|
||||||
<AlertDialogPrimitive.Content
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||||
ref={ref}
|
<AlertDialogPrimitive.Content
|
||||||
className={cn(
|
ref={ref}
|
||||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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 sm:rounded-lg",
|
className={cn(
|
||||||
className,
|
"grid w-full max-w-lg origin-center gap-4 border bg-background p-6 shadow-lg duration-200 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 sm:rounded-lg",
|
||||||
)}
|
className,
|
||||||
{...props}
|
)}
|
||||||
/>
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</AlertDialogPortal>
|
</AlertDialogPortal>
|
||||||
));
|
));
|
||||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
||||||
|
|||||||
@ -70,8 +70,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "Delete “{{title}}”?",
|
"title": "Delete this chat?",
|
||||||
"description": "The session file will be removed from disk. This cannot be undone.",
|
"description": "This action cannot be undone.",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"confirm": "Delete"
|
"confirm": "Delete"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -45,8 +45,8 @@
|
|||||||
"newChat": "Nuevo chat"
|
"newChat": "Nuevo chat"
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "¿Eliminar “{{title}}”?",
|
"title": "¿Eliminar este chat?",
|
||||||
"description": "El archivo de sesión se eliminará del disco. Esta acción no se puede deshacer.",
|
"description": "Esta acción no se puede deshacer.",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"confirm": "Eliminar"
|
"confirm": "Eliminar"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -45,8 +45,8 @@
|
|||||||
"newChat": "Nouvelle discussion"
|
"newChat": "Nouvelle discussion"
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "Supprimer « {{title}} » ?",
|
"title": "Supprimer cette discussion ?",
|
||||||
"description": "Le fichier de session sera supprimé du disque. Cette action est irréversible.",
|
"description": "Cette action est irréversible.",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"confirm": "Supprimer"
|
"confirm": "Supprimer"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -45,8 +45,8 @@
|
|||||||
"newChat": "Obrolan baru"
|
"newChat": "Obrolan baru"
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "Hapus “{{title}}”?",
|
"title": "Hapus obrolan ini?",
|
||||||
"description": "File sesi akan dihapus dari disk. Tindakan ini tidak dapat dibatalkan.",
|
"description": "Tindakan ini tidak dapat dibatalkan.",
|
||||||
"cancel": "Batal",
|
"cancel": "Batal",
|
||||||
"confirm": "Hapus"
|
"confirm": "Hapus"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -45,8 +45,8 @@
|
|||||||
"newChat": "新しいチャット"
|
"newChat": "新しいチャット"
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "「{{title}}」を削除しますか?",
|
"title": "このチャットを削除しますか?",
|
||||||
"description": "セッションファイルはディスクから削除されます。この操作は元に戻せません。",
|
"description": "この操作は元に戻せません。",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"confirm": "削除"
|
"confirm": "削除"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -45,8 +45,8 @@
|
|||||||
"newChat": "새 채팅"
|
"newChat": "새 채팅"
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "“{{title}}”을(를) 삭제할까요?",
|
"title": "이 채팅을 삭제할까요?",
|
||||||
"description": "세션 파일이 디스크에서 제거됩니다. 이 작업은 되돌릴 수 없습니다.",
|
"description": "이 작업은 되돌릴 수 없습니다.",
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"confirm": "삭제"
|
"confirm": "삭제"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -45,8 +45,8 @@
|
|||||||
"newChat": "Cuộc trò chuyện mới"
|
"newChat": "Cuộc trò chuyện mới"
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "Xóa “{{title}}”?",
|
"title": "Xóa cuộc trò chuyện này?",
|
||||||
"description": "Tệp phiên sẽ bị xóa khỏi đĩa. Không thể hoàn tác thao tác này.",
|
"description": "Không thể hoàn tác thao tác này.",
|
||||||
"cancel": "Hủy",
|
"cancel": "Hủy",
|
||||||
"confirm": "Xóa"
|
"confirm": "Xóa"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -58,8 +58,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "删除“{{title}}”?",
|
"title": "删除这个对话?",
|
||||||
"description": "这个会话文件会从磁盘中删除,且无法撤销。",
|
"description": "此操作无法撤销。",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"confirm": "删除"
|
"confirm": "删除"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -45,8 +45,8 @@
|
|||||||
"newChat": "新增對話"
|
"newChat": "新增對話"
|
||||||
},
|
},
|
||||||
"deleteConfirm": {
|
"deleteConfirm": {
|
||||||
"title": "刪除「{{title}}」?",
|
"title": "刪除這個對話?",
|
||||||
"description": "這個會話檔案會從磁碟中移除,而且無法復原。",
|
"description": "此操作無法復原。",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"confirm": "刪除"
|
"confirm": "刪除"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -139,7 +139,7 @@ describe("App layout", () => {
|
|||||||
fireEvent.click(await screen.findByRole("menuitem", { name: "Delete" }));
|
fireEvent.click(await screen.findByRole("menuitem", { name: "Delete" }));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.getByText('Delete “First chat”?')).toBeInTheDocument(),
|
expect(screen.getByText("Delete this chat?")).toBeInTheDocument(),
|
||||||
);
|
);
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Delete" }));
|
fireEvent.click(screen.getByRole("button", { name: "Delete" }));
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ describe("App layout", () => {
|
|||||||
within(sidebar).getByRole("button", { name: /^Second chat$/ }),
|
within(sidebar).getByRole("button", { name: /^Second chat$/ }),
|
||||||
).toBeInTheDocument(),
|
).toBeInTheDocument(),
|
||||||
);
|
);
|
||||||
expect(screen.queryByText('Delete “First chat”?')).not.toBeInTheDocument();
|
expect(screen.queryByText("Delete this chat?")).not.toBeInTheDocument();
|
||||||
expect(document.body.style.pointerEvents).not.toBe("none");
|
expect(document.body.style.pointerEvents).not.toBe("none");
|
||||||
}, 15_000);
|
}, 15_000);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user