fix(web): preserve settings sidebar on first click (#1744)
This commit is contained in:
@@ -17,7 +17,6 @@ const state = {
|
||||
],
|
||||
},
|
||||
appSidebar: { settingsMode: false },
|
||||
toggleAppSidebarSettingsMode: mocks.toggleSettingsMode,
|
||||
};
|
||||
|
||||
let officeEnabled = false;
|
||||
@@ -64,7 +63,7 @@ import { AppSidebarFooter } from "./app-sidebar-footer";
|
||||
function renderFooter() {
|
||||
return render(
|
||||
<TooltipProvider>
|
||||
<AppSidebarFooter collapsed={false} />
|
||||
<AppSidebarFooter collapsed={false} onToggleSettingsMode={mocks.toggleSettingsMode} />
|
||||
</TooltipProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
|
||||
type AppSidebarFooterProps = {
|
||||
collapsed: boolean;
|
||||
onToggleSettingsMode: () => void;
|
||||
};
|
||||
|
||||
type FooterIconButtonProps = {
|
||||
@@ -95,7 +96,7 @@ function FooterIconButton({
|
||||
);
|
||||
}
|
||||
|
||||
export function AppSidebarFooter({ collapsed }: AppSidebarFooterProps) {
|
||||
export function AppSidebarFooter({ collapsed, onToggleSettingsMode }: AppSidebarFooterProps) {
|
||||
const router = useRouter();
|
||||
const workspaces = useAppStore((s) => s.workspaces);
|
||||
const workspaceId = workspaces.activeId;
|
||||
@@ -105,7 +106,6 @@ export function AppSidebarFooter({ collapsed }: AppSidebarFooterProps) {
|
||||
? resolveLastKanbanWorkspace(workspaces.items)
|
||||
: resolveLastOfficeWorkspace(workspaces.items);
|
||||
const settingsMode = useAppStore((s) => s.appSidebar.settingsMode);
|
||||
const toggleSettingsMode = useAppStore((s) => s.toggleAppSidebarSettingsMode);
|
||||
const officeEnabled = useFeature("office");
|
||||
const releaseNotes = useReleaseNotes();
|
||||
const [improveOpen, setImproveOpen] = useState(false);
|
||||
@@ -121,7 +121,7 @@ export function AppSidebarFooter({ collapsed }: AppSidebarFooterProps) {
|
||||
icon={IconSettings}
|
||||
label={settingsMode ? "Close settings" : "Settings"}
|
||||
collapsed={collapsed}
|
||||
onClick={toggleSettingsMode}
|
||||
onClick={onToggleSettingsMode}
|
||||
active={settingsMode}
|
||||
testId="sidebar-settings-gear"
|
||||
/>
|
||||
|
||||
@@ -103,6 +103,7 @@ export function AppSidebar() {
|
||||
const storedWidth = useAppStore((s) => s.appSidebar.width);
|
||||
const toggleSection = useAppStore((s) => s.toggleAppSidebarSection);
|
||||
const toggleCollapsed = useAppStore((s) => s.toggleAppSidebar);
|
||||
const toggleSettingsMode = useAppStore((s) => s.toggleAppSidebarSettingsMode);
|
||||
const setSettingsMode = useAppStore((s) => s.setAppSidebarSettingsMode);
|
||||
const setWidth = useAppStore((s) => s.setAppSidebarWidth);
|
||||
const pathname = usePathname();
|
||||
@@ -140,6 +141,14 @@ export function AppSidebar() {
|
||||
);
|
||||
|
||||
const expandedWidth = Math.max(APP_SIDEBAR_EXPANDED_WIDTH, storedWidth);
|
||||
const settingsModeTogglePathnameRef = useRef<string | null>(null);
|
||||
|
||||
const handleToggleSettingsMode = useCallback(() => {
|
||||
// History updates before React renders the new pathname; remember which
|
||||
// route the user actually clicked on so delayed route sync cannot undo it.
|
||||
settingsModeTogglePathnameRef.current = window.location.pathname;
|
||||
toggleSettingsMode();
|
||||
}, [toggleSettingsMode]);
|
||||
|
||||
// Keep the transient settings takeover aligned with route ownership. It is
|
||||
// intentionally not persisted, so direct reloads on `/settings/...` need to
|
||||
@@ -150,10 +159,11 @@ export function AppSidebar() {
|
||||
if (!pathname || prevPathnameRef.current === pathname) return;
|
||||
prevPathnameRef.current = pathname;
|
||||
if (isSettingsRoute(pathname)) {
|
||||
settingsModeTogglePathnameRef.current = null;
|
||||
if (!settingsMode) setSettingsMode(true);
|
||||
return;
|
||||
}
|
||||
if (settingsMode) {
|
||||
if (settingsMode && settingsModeTogglePathnameRef.current !== pathname) {
|
||||
setSettingsMode(false);
|
||||
}
|
||||
}, [pathname, settingsMode, setSettingsMode]);
|
||||
@@ -192,7 +202,7 @@ export function AppSidebar() {
|
||||
>
|
||||
<AppSidebarHeader collapsed={collapsed} onToggleCollapse={toggleCollapsed} />
|
||||
<AppSidebarNavigation collapsed={collapsed} inOffice={inOffice} settingsMode={settingsMode} />
|
||||
<AppSidebarFooter collapsed={collapsed} />
|
||||
<AppSidebarFooter collapsed={collapsed} onToggleSettingsMode={handleToggleSettingsMode} />
|
||||
{!collapsed && <AppSidebarResizeHandle onMouseDown={handleResize} />}
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -72,4 +72,23 @@ test.describe("Settings sidebar takeover", () => {
|
||||
await expect(sidebar).toHaveAttribute("data-collapsed", "false");
|
||||
await expect(takeover).toBeVisible();
|
||||
});
|
||||
|
||||
test("a route change cannot undo the first gear click", async ({ testPage }) => {
|
||||
await testPage.goto("/");
|
||||
|
||||
const takeover = testPage.getByTestId("app-sidebar-settings-mode");
|
||||
await expect(testPage.getByTestId("sidebar-settings-gear")).toBeVisible();
|
||||
|
||||
// Keep both actions in one browser task so the route-sync effect is still
|
||||
// pending when the user action arrives. The click must win.
|
||||
await testPage.evaluate(() => {
|
||||
window.history.pushState({}, "", "/tasks");
|
||||
window.dispatchEvent(new Event("kandev:navigation"));
|
||||
const gear = document.querySelector<HTMLElement>('[data-testid="sidebar-settings-gear"]');
|
||||
if (!gear) throw new Error("sidebar-settings-gear not found");
|
||||
gear.click();
|
||||
});
|
||||
|
||||
await expect(takeover).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user