feat: setting to let the hover side menu push content again

This commit is contained in:
LukeGus committed 2026-10-15 22:00:24 -05:00
1 parent d2851982a6
commit 3a851ad189
7 files changed
+58 -4

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@
### Changed
- The side menu now opens over the page when it expands on hover, so open sessions like remote desktop are not resized each time
- The side menu now opens over the page when it expands on hover, so open sessions like remote desktop are not resized each time. Turn on Push content when it expands in Settings to get the old behavior back
### Fixed
+2
View File
@@ -1674,6 +1674,8 @@
"pinAppRailDesc": "Keep the left sidebar app rail always expanded instead of expanding on hover",
"expandAppRailOnHover": "Expand App Rail on Hover",
"expandAppRailOnHoverDesc": "Allow the left sidebar app rail to expand when the pointer moves over it",
"appRailHoverPushesContent": "Push content when it expands",
"appRailHoverPushesContentDesc": "Make room for the expanded rail instead of opening it over the page. Open sessions, like remote desktop, resize each time",
"showPinAppRailButton": "Show Pin App Rail Button",
"showPinAppRailButtonDesc": "Show a button in the app rail to pin it open. Off by default.",
"settingsNavigation": "Navigation",
+2
View File
@@ -323,6 +323,8 @@ export const SETTINGS_SEARCH_INDEX: Record<string, string[]> = {
],
"appearance": [
"newUi.sidebar.userProfile.accentColorLabel",
"newUi.sidebar.userProfile.appRailHoverPushesContent",
"newUi.sidebar.userProfile.appRailHoverPushesContentDesc",
"newUi.sidebar.userProfile.colorPickerTooltip",
"newUi.sidebar.userProfile.commandPalette",
"newUi.sidebar.userProfile.commandPaletteDesc",
+13 -2
View File
@@ -159,6 +159,9 @@ export function AppRail({
const [showPinButton, setShowPinButton] = useState(() =>
readRailPreference("showPinAppRailButton"),
);
const [hoverPushes, setHoverPushes] = useState(() =>
readRailPreference("appRailHoverPushesContent"),
);
const [menuPos, setMenuPos] = useState<{ x: number; y: number } | null>(null);
const [menuTarget, setMenuTarget] = useState<MenuTarget | null>(null);
const [managing, setManaging] = useState(false);
@@ -170,8 +173,11 @@ export function AppRail({
setExpandOnHover(readRailPreference("expandAppRailOnHover"));
const showPinButtonHandler = () =>
setShowPinButton(readRailPreference("showPinAppRailButton"));
const pushHandler = () =>
setHoverPushes(readRailPreference("appRailHoverPushesContent"));
window.addEventListener("pinAppRailChanged", pinHandler);
window.addEventListener("expandAppRailOnHoverChanged", hoverHandler);
window.addEventListener("appRailHoverPushesContentChanged", pushHandler);
window.addEventListener(
"showPinAppRailButtonChanged",
showPinButtonHandler,
@@ -179,6 +185,10 @@ export function AppRail({
return () => {
window.removeEventListener("pinAppRailChanged", pinHandler);
window.removeEventListener("expandAppRailOnHoverChanged", hoverHandler);
window.removeEventListener(
"appRailHoverPushesContentChanged",
pushHandler,
);
window.removeEventListener(
"showPinAppRailButtonChanged",
showPinButtonHandler,
@@ -313,8 +323,9 @@ export function AppRail({
};
// Expanding on hover draws over the content instead of pushing it, so open
// sessions like remote desktop are not resized each time.
const takesSpace = pinned || managing;
// sessions like remote desktop are not resized each time, unless the user
// asked for the old behavior.
const takesSpace = hoverPushes ? railExpanded : pinned || managing;
return (
<div
+18
View File
@@ -553,6 +553,9 @@ export function UserProfilePanel({
const [expandAppRailOnHover, setExpandAppRailOnHover] = useState(() =>
readRailPreference("expandAppRailOnHover"),
);
const [appRailHoverPushes, setAppRailHoverPushes] = useState(() =>
readRailPreference("appRailHoverPushesContent"),
);
const [showPinAppRailButton, setShowPinAppRailButton] = useState(() =>
readRailPreference("showPinAppRailButton"),
);
@@ -1684,6 +1687,21 @@ export function UserProfilePanel({
}}
/>
</SettingRow>
<SettingRow
label={t("newUi.sidebar.userProfile.appRailHoverPushesContent")}
description={t(
"newUi.sidebar.userProfile.appRailHoverPushesContentDesc",
)}
>
<FakeSwitch
checked={appRailHoverPushes}
disabled={!expandAppRailOnHover}
onChange={(v) => {
setAppRailHoverPushes(v);
setRailPreference("appRailHoverPushesContent", v);
}}
/>
</SettingRow>
<SettingRow
label={t("newUi.sidebar.userProfile.showPinAppRailButton")}
description={t(
+11 -1
View File
@@ -1,14 +1,23 @@
import { getUserPreferences, saveUserPreferences } from "@/api/open-tabs-api";
export type RailPreference =
"pinAppRail" | "expandAppRailOnHover" | "showPinAppRailButton";
| "pinAppRail"
| "expandAppRailOnHover"
| "showPinAppRailButton"
| "appRailHoverPushesContent";
const CHANGE_EVENT: Record<RailPreference, string> = {
pinAppRail: "pinAppRailChanged",
expandAppRailOnHover: "expandAppRailOnHoverChanged",
showPinAppRailButton: "showPinAppRailButtonChanged",
appRailHoverPushesContent: "appRailHoverPushesContentChanged",
};
/** Kept on this device only; the server has no column for it. */
const LOCAL_ONLY: ReadonlySet<RailPreference> = new Set([
"appRailHoverPushesContent",
]);
// pinAppRail and showPinAppRailButton default off, expandAppRailOnHover
// defaults on, so each key needs its own read rather than a shared
// === "true" check.
@@ -28,6 +37,7 @@ export function readRailPreference(key: RailPreference): boolean {
export function setRailPreference(key: RailPreference, value: boolean): void {
localStorage.setItem(key, String(value));
window.dispatchEvent(new Event(CHANGE_EVENT[key]));
if (LOCAL_ONLY.has(key)) return;
void getUserPreferences()
.then((preferences) => {
if (preferences.storageMode === "cloud") {
+11
View File
@@ -66,6 +66,17 @@ describe("AppRail", () => {
expect(slot.style.width).toBe(collapsed);
});
it("pushes the content on hover when the user asks for it", () => {
localStorage.setItem("appRailHoverPushesContent", "true");
renderRail();
const rail = screen
.getByLabelText("common.logout")
.closest(".absolute") as HTMLElement;
const slot = rail.parentElement as HTMLElement;
fireEvent.mouseEnter(rail);
expect(slot.style.width).toBe(rail.style.width);
});
it("puts a footer item above the profile with its badge", async () => {
registerRailItem({
id: "inbox",