From 458f3d371734d28eeecbd1a04ed6d68aeb890628 Mon Sep 17 00:00:00 2001 From: tuzi <1753710808@qq.com> Date: Mon, 29 Jun 2026 12:41:19 +0800 Subject: [PATCH] ui --- apps/desktop/src/main.rs | 2 +- apps/desktop/tauri.conf.json | 4 +-- packages/ui/src/App.empty-state.test.tsx | 41 ++++++++++++++++++++++++ packages/ui/src/App.tsx | 39 ++++++++++++++++++++-- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/main.rs b/apps/desktop/src/main.rs index 2856ab3..71f694b 100644 --- a/apps/desktop/src/main.rs +++ b/apps/desktop/src/main.rs @@ -88,7 +88,7 @@ fn start_backend_and_window(app: &tauri::App) -> Result<(), String> { } WebviewWindowBuilder::new(app, "main", WebviewUrl::default()) - .title("Lumen Desktop") + .title("Lumen") .inner_size(1440.0, 900.0) .initialization_script(&format!("window.__BACKEND_PORT__ = {};", port)) .build() diff --git a/apps/desktop/tauri.conf.json b/apps/desktop/tauri.conf.json index 2da61ca..dbe3c85 100644 --- a/apps/desktop/tauri.conf.json +++ b/apps/desktop/tauri.conf.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "Lumen Desktop", + "productName": "Lumen", "version": "0.1.0", "identifier": "com.example.mercury", "build": { @@ -20,4 +20,4 @@ }, "useLocalToolsDir": true } -} \ No newline at end of file +} diff --git a/packages/ui/src/App.empty-state.test.tsx b/packages/ui/src/App.empty-state.test.tsx index a3bd9c7..b568042 100644 --- a/packages/ui/src/App.empty-state.test.tsx +++ b/packages/ui/src/App.empty-state.test.tsx @@ -51,4 +51,45 @@ describe("App empty state", () => { expect(screen.getByRole("dialog")).toBeTruthy(); expect(screen.getByText("Choose an OPML file to merge subscriptions into your library. Existing feeds are preserved.")).toBeTruthy(); }); + + it("closes the feed add menu when clicking outside", async () => { + const { App } = await import("./App"); + + render(); + + await waitFor(() => { + expect(document.querySelector(".reader-workspace")?.getAttribute("aria-busy")).toBe("false"); + }); + + fireEvent.click(screen.getByTitle("Add Feed...")); + expect(screen.getByText("Import OPML...")).toBeTruthy(); + + fireEvent.mouseDown(document.body); + + await waitFor(() => { + expect(screen.queryByText("Import OPML...")).toBeNull(); + }); + }); + + it("keeps only one feeds menu open at a time", async () => { + const { App } = await import("./App"); + + render(); + + await waitFor(() => { + expect(document.querySelector(".reader-workspace")?.getAttribute("aria-busy")).toBe("false"); + }); + + fireEvent.click(screen.getByTitle("Add Feed...")); + expect(screen.getByText("Import OPML...")).toBeTruthy(); + + const feedsMenuButton = screen.getByTitle("Feeds"); + fireEvent.mouseDown(feedsMenuButton); + fireEvent.click(feedsMenuButton); + + await waitFor(() => { + expect(screen.queryByText("Import OPML...")).toBeNull(); + }); + expect(screen.getByText("Sync Now")).toBeTruthy(); + }); }); diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 4f60062..a1c1658 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -2292,9 +2292,44 @@ function MenuButton(props: { items: Array<{ label: string; action: () => void; disabled?: boolean; destructive?: boolean }>; }) { const [open, setOpen] = useState(false); + const rootRef = useRef(null); + + useEffect(() => { + if (!open) { + return; + } + + const handleMouseDown = (event: MouseEvent) => { + if (rootRef.current?.contains(event.target as Node)) { + return; + } + setOpen(false); + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setOpen(false); + } + }; + + window.addEventListener("mousedown", handleMouseDown); + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("mousedown", handleMouseDown); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [open]); + return ( - - setOpen(!open)} title={props.label}> + + setOpen(!open)} + title={props.label} + > {props.icon ?? } {open && (