mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
feat(ui): make the sidebar resizable (#102491)
* feat(ui): make the sidebar resizable * chore: defer release notes to release automation
This commit is contained in:
committed by
GitHub
parent
04b64ffc19
commit
67630e8973
@@ -11,6 +11,7 @@ import "../components/exec-approval.ts";
|
||||
import "../components/gateway-url-confirmation.ts";
|
||||
import "../components/github-link-hovercard.ts";
|
||||
import "../components/login-gate.ts";
|
||||
import "../components/resizable-divider.ts";
|
||||
import "../components/terminal/terminal-panel.ts";
|
||||
import "../components/tooltip.ts";
|
||||
import "../components/update-banner.ts";
|
||||
@@ -41,6 +42,7 @@ import { hasOperatorAdminAccess } from "./operator-access.ts";
|
||||
import type { ApplicationOverlaySnapshot } from "./overlays.ts";
|
||||
import { controlUiPublicAssetPath } from "./public-assets.ts";
|
||||
import { selectRenderedRouteMatch } from "./router-outlet.ts";
|
||||
import { NAV_WIDTH_DEFAULT, NAV_WIDTH_MAX, NAV_WIDTH_MIN } from "./settings.ts";
|
||||
|
||||
type ShellRouteState = {
|
||||
routeId?: RouteId;
|
||||
@@ -372,6 +374,7 @@ class OpenClawShell extends LitElement {
|
||||
private context?: ApplicationContext<RouteId>;
|
||||
|
||||
@state() private navCollapsed = false;
|
||||
@state() private navWidth = NAV_WIDTH_DEFAULT;
|
||||
@state() private sidebarPinnedRoutes: readonly SidebarNavRoute[] = [];
|
||||
@state() private sidebarMoreExpanded = false;
|
||||
@state() private navDrawerOpen = false;
|
||||
@@ -418,6 +421,7 @@ class OpenClawShell extends LitElement {
|
||||
this.startSubscriptions();
|
||||
this.addEventListener(COMMAND_PALETTE_TARGET_EVENT, this.handleCommandPaletteTarget);
|
||||
document.addEventListener("keydown", this.handleDocumentKeydown);
|
||||
window.addEventListener("resize", this.handleWindowResize);
|
||||
}
|
||||
|
||||
override updated() {
|
||||
@@ -486,6 +490,7 @@ class OpenClawShell extends LitElement {
|
||||
override disconnectedCallback() {
|
||||
this.removeEventListener(COMMAND_PALETTE_TARGET_EVENT, this.handleCommandPaletteTarget);
|
||||
document.removeEventListener("keydown", this.handleDocumentKeydown);
|
||||
window.removeEventListener("resize", this.handleWindowResize);
|
||||
this.stopAgentsSubscription?.();
|
||||
this.stopAgentsSubscription = undefined;
|
||||
this.stopConfigSubscription?.();
|
||||
@@ -576,6 +581,22 @@ class OpenClawShell extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private resizeNavigation(splitRatio: number) {
|
||||
const shell = this.querySelector<HTMLElement>(".shell");
|
||||
const context = this.context;
|
||||
if (!shell || !context) {
|
||||
return;
|
||||
}
|
||||
const navWidth = Math.round(
|
||||
Math.min(NAV_WIDTH_MAX, Math.max(NAV_WIDTH_MIN, splitRatio * shell.clientWidth)),
|
||||
);
|
||||
context.navigation.update({ navWidth });
|
||||
}
|
||||
|
||||
private readonly handleWindowResize = () => {
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
private readonly handleShellKeydown = (event: KeyboardEvent) => {
|
||||
if (event.defaultPrevented || event.key !== "Escape" || !this.navDrawerOpen) {
|
||||
return;
|
||||
@@ -731,6 +752,7 @@ class OpenClawShell extends LitElement {
|
||||
snapshot: ApplicationRuntime["context"]["navigation"]["snapshot"],
|
||||
) => {
|
||||
this.navCollapsed = snapshot.navCollapsed;
|
||||
this.navWidth = snapshot.navWidth;
|
||||
this.sidebarPinnedRoutes = snapshot.sidebarPinnedRoutes;
|
||||
this.sidebarMoreExpanded = snapshot.sidebarMoreExpanded;
|
||||
};
|
||||
@@ -751,6 +773,7 @@ class OpenClawShell extends LitElement {
|
||||
// Drawer navigation always opens expanded; the desktop collapse preference
|
||||
// stays persisted for when the viewport returns to the desktop layout.
|
||||
const navCollapsed = this.navCollapsed && !navDrawerOpen;
|
||||
const shellWidth = Math.max(globalThis.innerWidth || 0, NAV_WIDTH_MAX);
|
||||
return html`
|
||||
<openclaw-command-palette
|
||||
.onNavigate=${(routeId: RouteId) => this.navigate(routeId)}
|
||||
@@ -766,6 +789,7 @@ class OpenClawShell extends LitElement {
|
||||
: ""} ${navDrawerOpen ? "shell--nav-drawer-open" : ""} ${this.onboarding
|
||||
? "shell--onboarding"
|
||||
: ""}"
|
||||
style=${`--shell-nav-expanded-width: ${this.navWidth}px`}
|
||||
@keydown=${this.handleShellKeydown}
|
||||
@theme-change=${this.handleThemeChange}
|
||||
>
|
||||
@@ -817,6 +841,21 @@ class OpenClawShell extends LitElement {
|
||||
isRouteId(routeId) ? context.preload(routeId) : Promise.resolve()}
|
||||
></openclaw-app-sidebar>
|
||||
</div>
|
||||
${!navCollapsed && !this.onboarding
|
||||
? html`
|
||||
<resizable-divider
|
||||
class="sidebar-resizer"
|
||||
.label=${t("nav.resize")}
|
||||
.splitRatio=${this.navWidth / shellWidth}
|
||||
.minRatio=${NAV_WIDTH_MIN / shellWidth}
|
||||
.maxRatio=${NAV_WIDTH_MAX / shellWidth}
|
||||
aria-valuetext=${`${this.navWidth} pixels`}
|
||||
title=${t("nav.resize")}
|
||||
@resize=${(event: CustomEvent<{ splitRatio: number }>) =>
|
||||
this.resizeNavigation(event.detail.splitRatio)}
|
||||
></resizable-divider>
|
||||
`
|
||||
: nothing}
|
||||
<main
|
||||
class="content ${activeRoute === "chat" ? "content--chat" : ""} ${activeRoute ===
|
||||
"workboard"
|
||||
|
||||
@@ -158,6 +158,7 @@ function createApplicationNavigationPreferences(
|
||||
let settings = initialSettings;
|
||||
let snapshot: ApplicationNavigationPreferencesSnapshot = {
|
||||
navCollapsed: settings.navCollapsed,
|
||||
navWidth: settings.navWidth,
|
||||
sidebarPinnedRoutes: settings.sidebarPinnedRoutes,
|
||||
sidebarMoreExpanded: settings.sidebarMoreExpanded,
|
||||
};
|
||||
@@ -171,6 +172,7 @@ function createApplicationNavigationPreferences(
|
||||
const nextSnapshot = { ...snapshot, ...patch };
|
||||
if (
|
||||
nextSnapshot.navCollapsed === snapshot.navCollapsed &&
|
||||
nextSnapshot.navWidth === snapshot.navWidth &&
|
||||
nextSnapshot.sidebarPinnedRoutes === snapshot.sidebarPinnedRoutes &&
|
||||
nextSnapshot.sidebarMoreExpanded === snapshot.sidebarMoreExpanded
|
||||
) {
|
||||
@@ -178,6 +180,7 @@ function createApplicationNavigationPreferences(
|
||||
}
|
||||
settings = patchSettings({
|
||||
navCollapsed: nextSnapshot.navCollapsed,
|
||||
navWidth: nextSnapshot.navWidth,
|
||||
sidebarPinnedRoutes: [...nextSnapshot.sidebarPinnedRoutes],
|
||||
sidebarMoreExpanded: nextSnapshot.sidebarMoreExpanded,
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ export type ApplicationTheme = {
|
||||
|
||||
export type ApplicationNavigationPreferencesSnapshot = {
|
||||
navCollapsed: boolean;
|
||||
navWidth: number;
|
||||
sidebarPinnedRoutes: readonly SidebarNavRoute[];
|
||||
sidebarMoreExpanded: boolean;
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@ function makeSettings(gatewayUrl: string, overrides: Partial<UiSettings> = {}):
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -167,7 +167,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -202,7 +202,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -235,7 +235,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -253,7 +253,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -283,7 +283,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -303,7 +303,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -338,7 +338,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["sessions", "cron"],
|
||||
sidebarMoreExpanded: true,
|
||||
borderRadius: 50,
|
||||
@@ -347,6 +347,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
|
||||
expect(loadSettings().sidebarPinnedRoutes).toEqual(["sessions", "cron"]);
|
||||
expect(loadSettings().sidebarMoreExpanded).toBe(true);
|
||||
expect(loadSettings().navWidth).toBe(258);
|
||||
|
||||
// Corrupt the persisted list; load falls back to the default pinned set.
|
||||
const scopedKey = `openclaw.control.settings.v1:${gwUrl}`;
|
||||
@@ -356,10 +357,12 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
>;
|
||||
persisted.sidebarPinnedRoutes = "sessions";
|
||||
persisted.sidebarMoreExpanded = "yes";
|
||||
persisted.navWidth = 220;
|
||||
localStorage.setItem(scopedKey, JSON.stringify(persisted));
|
||||
|
||||
expect(loadSettings().sidebarPinnedRoutes).toEqual(["overview"]);
|
||||
expect(loadSettings().sidebarMoreExpanded).toBe(false);
|
||||
expect(loadSettings().navWidth).toBe(258);
|
||||
});
|
||||
|
||||
it("normalizes persisted text scale to the nearest supported stop", () => {
|
||||
@@ -475,7 +478,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -491,7 +494,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -592,7 +595,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -623,7 +626,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -668,7 +671,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -712,7 +715,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Control UI module implements storage behavior.
|
||||
const SETTINGS_KEY_PREFIX = "openclaw.control.settings.v1:";
|
||||
const LEGACY_SETTINGS_KEY = "openclaw.control.settings.v1";
|
||||
export const NAV_WIDTH_MIN = 240;
|
||||
export const NAV_WIDTH_MAX = 400;
|
||||
export const NAV_WIDTH_DEFAULT = 258;
|
||||
const CURRENT_GATEWAY_SELECTION_KEY_PREFIX = "openclaw.control.currentGateway.v1:";
|
||||
const LOCAL_USER_IDENTITY_KEY = "openclaw.control.user.v1";
|
||||
const LEGACY_TOKEN_SESSION_KEY = "openclaw.control.token.v1";
|
||||
@@ -532,7 +535,7 @@ export function loadSettings(): UiSettings {
|
||||
chatSendShortcut: "enter",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: NAV_WIDTH_DEFAULT,
|
||||
sidebarPinnedRoutes: [...DEFAULT_SIDEBAR_PINNED_ROUTES],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
@@ -599,7 +602,9 @@ export function loadSettings(): UiSettings {
|
||||
navCollapsed:
|
||||
typeof parsed.navCollapsed === "boolean" ? parsed.navCollapsed : defaults.navCollapsed,
|
||||
navWidth:
|
||||
typeof parsed.navWidth === "number" && parsed.navWidth >= 200 && parsed.navWidth <= 400
|
||||
typeof parsed.navWidth === "number" &&
|
||||
parsed.navWidth >= NAV_WIDTH_MIN &&
|
||||
parsed.navWidth <= NAV_WIDTH_MAX
|
||||
? parsed.navWidth
|
||||
: defaults.navWidth,
|
||||
sidebarPinnedRoutes:
|
||||
|
||||
@@ -23,6 +23,10 @@ async function trimmedTextContents(locator: Locator): Promise<string[]> {
|
||||
return (await locator.allTextContents()).map((text) => text.trim());
|
||||
}
|
||||
|
||||
async function roundedWidth(locator: Locator): Promise<number> {
|
||||
return Math.round((await locator.boundingBox())?.width ?? 0);
|
||||
}
|
||||
|
||||
async function captureUiProof(page: Page, fileName: string) {
|
||||
if (process.env.OPENCLAW_CAPTURE_UI_PROOF !== "1") {
|
||||
return;
|
||||
@@ -74,6 +78,44 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
||||
await expect.poll(() => page.locator(".topbar").isVisible()).toBe(true);
|
||||
await expect.poll(() => page.locator(".dashboard-header").isVisible()).toBe(true);
|
||||
await expect.poll(() => page.locator(".topbar-brand").isVisible()).toBe(false);
|
||||
const shellNav = page.locator(".shell-nav");
|
||||
const sidebarResizer = page.getByRole("separator", { name: "Resize sidebar" });
|
||||
await expect.poll(() => roundedWidth(shellNav)).toBe(258);
|
||||
await expect.poll(() => sidebarResizer.getAttribute("aria-valuetext")).toBe("258 pixels");
|
||||
await captureUiProof(page, "00-sidebar-default-width.png");
|
||||
|
||||
const resizerBounds = await sidebarResizer.boundingBox();
|
||||
if (!resizerBounds) {
|
||||
throw new Error("expected visible desktop sidebar resizer");
|
||||
}
|
||||
const resizerX = resizerBounds.x + resizerBounds.width / 2;
|
||||
const resizerY = resizerBounds.y + resizerBounds.height / 2;
|
||||
await page.mouse.move(resizerX, resizerY);
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(({ x, y }) => document.elementFromPoint(x, y)?.tagName.toLowerCase(), {
|
||||
x: resizerX,
|
||||
y: resizerY,
|
||||
}),
|
||||
)
|
||||
.toBe("resizable-divider");
|
||||
await page.mouse.down();
|
||||
await expect.poll(() => sidebarResizer.getAttribute("class")).toContain("dragging");
|
||||
await page.mouse.move(resizerX + 100, resizerY);
|
||||
await page.mouse.up();
|
||||
await expect.poll(() => roundedWidth(shellNav)).toBe(358);
|
||||
await expect.poll(() => sidebarResizer.getAttribute("aria-valuetext")).toBe("358 pixels");
|
||||
await captureUiProof(page, "00-sidebar-resized.png");
|
||||
|
||||
await page.reload();
|
||||
await expect.poll(() => roundedWidth(shellNav)).toBe(358);
|
||||
await page.setViewportSize({ height: 900, width: 1300 });
|
||||
await expect.poll(() => roundedWidth(shellNav)).toBe(358);
|
||||
await sidebarResizer.focus();
|
||||
await page.keyboard.press("Home");
|
||||
await expect.poll(() => roundedWidth(shellNav)).toBe(240);
|
||||
await page.keyboard.press("End");
|
||||
await expect.poll(() => roundedWidth(shellNav)).toBe(400);
|
||||
const settingsLink = sidebar.getByRole("link", { name: "Settings" });
|
||||
await expect.poll(() => settingsLink.isVisible()).toBe(true);
|
||||
await settingsLink.click();
|
||||
@@ -158,6 +200,14 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
||||
await expect
|
||||
.poll(() => page.locator(".shell").getAttribute("class"))
|
||||
.toContain("shell--nav-collapsed");
|
||||
await expect
|
||||
.poll(() =>
|
||||
page
|
||||
.locator(".shell")
|
||||
.evaluate((element) => getComputedStyle(element).getPropertyValue("--shell-nav-width")),
|
||||
)
|
||||
.toBe("78px");
|
||||
await expect.poll(() => sidebarResizer.count()).toBe(0);
|
||||
// Rail mode keeps the palette entry reachable as an icon-only control.
|
||||
await expect.poll(() => searchButton.isVisible()).toBe(true);
|
||||
await page.reload();
|
||||
@@ -181,6 +231,14 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
||||
)
|
||||
.toBe(false);
|
||||
await expect.poll(() => moreButton.isVisible()).toBe(true);
|
||||
await expect.poll(() => sidebarResizer.isVisible()).toBe(false);
|
||||
await expect
|
||||
.poll(() =>
|
||||
page
|
||||
.locator(".shell")
|
||||
.evaluate((element) => getComputedStyle(element).getPropertyValue("--shell-nav-width")),
|
||||
)
|
||||
.toBe("0px");
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.locator(".shell-nav").evaluate((element) => element.getBoundingClientRect().left),
|
||||
|
||||
@@ -23,7 +23,7 @@ function createOverviewProps(overrides: Partial<OverviewProps> = {}): OverviewPr
|
||||
chatShowToolCalls: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navWidth: 258,
|
||||
sidebarPinnedRoutes: ["overview"],
|
||||
sidebarMoreExpanded: false,
|
||||
borderRadius: 50,
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
.shell {
|
||||
--shell-pad: 16px;
|
||||
--shell-gap: 16px;
|
||||
--shell-nav-width: 258px;
|
||||
--shell-nav-expanded-width: 258px;
|
||||
--shell-nav-width: var(--shell-nav-expanded-width);
|
||||
--shell-nav-rail-width: 78px;
|
||||
--shell-topbar-height: 44px;
|
||||
--shell-focus-duration: 200ms;
|
||||
@@ -350,6 +351,19 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
grid-area: nav;
|
||||
align-self: stretch;
|
||||
justify-self: end;
|
||||
z-index: 20;
|
||||
width: 1px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.shell:has(.sidebar-resizer.dragging) {
|
||||
transition: grid-template-rows var(--shell-focus-duration) var(--shell-focus-ease);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -124,6 +124,10 @@
|
||||
opacity var(--shell-focus-duration) var(--shell-focus-ease);
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shell--nav-drawer-open .shell-nav,
|
||||
.shell--nav-collapsed.shell--nav-drawer-open .shell-nav {
|
||||
transform: translateX(0);
|
||||
|
||||
Reference in New Issue
Block a user