Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand All @@ -20,4 +20,4 @@
},
"useLocalToolsDir": true
}
}
}
41 changes: 41 additions & 0 deletions packages/ui/src/App.empty-state.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<App />);

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(<App />);

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();
});
});
39 changes: 37 additions & 2 deletions packages/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>(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 (
<div className="menu-root">
<button className="icon-button" type="button" onClick={() => setOpen(!open)} title={props.label}>
<div ref={rootRef} className="menu-root">
<button
className="icon-button"
type="button"
aria-expanded={open}
onClick={() => setOpen(!open)}
title={props.label}
>
{props.icon ?? <MoreHorizontal size={17} aria-hidden />}
</button>
{open && (
Expand Down
Loading