From 962cdb968dcb2a08f42a7219483e5afb44bfa6d3 Mon Sep 17 00:00:00 2001
From: Xubin Ren <52506698+Re-bin@users.noreply.github.com>
Date: Tue, 28 Jul 2026 03:17:55 +0800
Subject: [PATCH] style(webui): animate settings section transitions
---
.../src/components/settings/SettingsView.tsx | 6 +++-
webui/src/tests/app-layout.test.tsx | 30 ++++++++++++++++++-
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/webui/src/components/settings/SettingsView.tsx b/webui/src/components/settings/SettingsView.tsx
index 1dcf16d2c..f5801187c 100644
--- a/webui/src/components/settings/SettingsView.tsx
+++ b/webui/src/components/settings/SettingsView.tsx
@@ -2347,8 +2347,12 @@ export function SettingsView({
)}
>
{
expect(window.location.hash).toBe("#/settings?section=voice");
});
- it("opens Apps from the main sidebar without replacing the sidebar", async () => {
+ it("transitions between Apps and Skills without replacing the sidebar", async () => {
mockFetchRoutes({
"/api/settings": baseSettingsPayload(),
"/api/settings/cli-apps": { apps: [], installed_count: 0, catalog_updated_at: "2026-04-18" },
"/api/settings/mcp-presets": { presets: [], installed_count: 0 },
+ "/api/webui/skills": { skills: [] },
});
render(
);
@@ -2080,7 +2081,34 @@ describe("App layout", () => {
"aria-current",
"page",
);
+ expect(screen.getByTestId("settings-section-transition")).toHaveAttribute(
+ "data-settings-section",
+ "apps",
+ );
+ expect(screen.getByTestId("settings-section-transition")).toHaveClass(
+ "animate-in",
+ "fade-in-0",
+ "slide-in-from-bottom-1",
+ "duration-200",
+ "motion-reduce:animate-none",
+ );
expect(document.title).toBe("Apps · nanobot");
+
+ fireEvent.click(within(sidebar).getByRole("button", { name: "Skills" }));
+
+ expect(await screen.findByRole("heading", { name: "Skills" })).toBeInTheDocument();
+ await waitFor(() => {
+ expect(screen.getByTestId("settings-section-transition")).toHaveAttribute(
+ "data-settings-section",
+ "skills",
+ );
+ });
+ expect(screen.getByRole("navigation", { name: "Sidebar navigation" })).toBeInTheDocument();
+ expect(within(sidebar).getByRole("button", { name: "Skills" })).toHaveAttribute(
+ "aria-current",
+ "page",
+ );
+ expect(document.title).toBe("Skills · nanobot");
});
it("returns from settings to the blank start page when no session was active", async () => {