now left menu in admin panel can be minimized

This commit is contained in:
onysd 2026-09-08 02:24:58 +03:00
parent db40f100cd
commit eb69c8500c
8 changed files with 119 additions and 16 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -23,8 +23,8 @@
})();
</script>
<script type="module" crossorigin src="/assets/index-Cs_euGRI.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-mpMvGEXi.css">
<script type="module" crossorigin src="/assets/index-BnMOLFk0.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-pvHlBxfG.css">
</head>
<body>
<div id="root"></div>

View file

@ -5,17 +5,20 @@ export function AppLink({
href,
navigate,
className,
title,
children
}: {
href: string;
navigate: Navigate;
className?: string;
title?: string;
children: ReactNode;
}) {
return (
<a
className={className}
href={href}
title={title}
onClick={(event) => {
event.preventDefault();
navigate(href);

View file

@ -9,6 +9,8 @@ import {
LogOut,
Megaphone,
MessageSquareText,
PanelLeftClose,
PanelLeftOpen,
Settings,
UserCog,
UserRound,
@ -113,6 +115,29 @@ export function Shell({
// sections are granted independently, so one entry can be visible without the other.
const canReviewBotVerification = useCan(permissionBotVerificationReview);
const canManageServer = useCan(permissionServerManage);
// Remembered per browser: an operator who works in a narrow window should not
// have to re-collapse the navigation on every visit. A failed read (private
// mode, blocked storage) just means the default.
const [navCollapsed, setNavCollapsed] = useState(() => {
try {
return localStorage.getItem("owpengram.nav.collapsed") === "1";
} catch {
return false;
}
});
function toggleNav() {
setNavCollapsed((current) => {
const next = !current;
try {
localStorage.setItem("owpengram.nav.collapsed", next ? "1" : "0");
} catch {
// Not being able to remember the choice is not a reason to refuse it.
}
return next;
});
}
const [addServerLinkOpen, setAddServerLinkOpen] = useState(false);
const [connecting, setConnecting] = useState(false);
const [connectError, setConnectError] = useState("");
@ -221,7 +246,7 @@ export function Shell({
}
return (
<div className="shell">
<div className={`shell ${navCollapsed ? "shell--nav-collapsed" : ""}`.trim()}>
<aside className="sidebar">
<AppLink className="brand" href="/" navigate={navigate}>
<span className="brand-mark"><img src={brandIconSrc} alt={brandName} onError={() => setBrandIconFailed(true)} /></span>
@ -326,7 +351,17 @@ export function Shell({
</aside>
<div className="workspace">
<header className="topbar">
<div>
<div className="topbar-lead">
<button
className="icon-btn nav-toggle"
type="button"
onClick={toggleNav}
aria-expanded={!navCollapsed}
aria-label={navCollapsed ? "Expand navigation" : "Collapse navigation"}
title={navCollapsed ? "Expand navigation" : "Collapse navigation"}
>
{navCollapsed ? <PanelLeftOpen size={16} /> : <PanelLeftClose size={16} />}
</button>
<h1>{routeTitle(route.path)}</h1>
</div>
<div className="topbar-actions">
@ -363,9 +398,16 @@ function NavLink({
}) {
const active = activeWhen ? activeWhen(route.path) : href === "/" ? route.path === "/" : route.path.startsWith(href);
return (
<AppLink className={`nav-item ${active ? "active" : ""}`} href={href} navigate={navigate}>
<AppLink
className={`nav-item ${active ? "active" : ""}`}
href={href}
navigate={navigate}
// The label is hidden when the sidebar is collapsed, so it moves to the
// tooltip -- an icon rail with no names is a memory test.
title={typeof children === "string" ? children : undefined}
>
{icon ?? <span aria-hidden="true" className="nav-dot" />}
<span>{children}</span>
<span className="nav-item-label">{children}</span>
</AppLink>
);
}

View file

@ -506,3 +506,61 @@ a {
text-transform: uppercase;
letter-spacing: 0.04em;
}
/* Collapsible navigation.
Scoped to the width where the sidebar is actually a sidebar: below 1120px the
shell already stacks it into a horizontal strip (05-responsive.css), and
collapsing that would just hide the labels of a bar that has room for them. */
@media (min-width: 1121px) {
.shell--nav-collapsed {
grid-template-columns: 68px minmax(0, 1fr);
}
/* Everything that only makes sense with room for words goes away: the
wordmark beside the logo, the "Navigation" heading, the version block and
the Connect/Share buttons. The icons and their tooltips remain. */
.shell--nav-collapsed .brand > span:not(.brand-mark),
.shell--nav-collapsed .sidebar-label,
.shell--nav-collapsed .sidebar-status,
.shell--nav-collapsed .sidebar-server-actions,
.shell--nav-collapsed .nav-item-label {
display: none;
}
.shell--nav-collapsed .sidebar {
padding: 18px 10px;
align-items: center;
}
.shell--nav-collapsed .brand {
justify-content: center;
padding: 0;
}
/* One column instead of icon + label, so the glyph sits centred in the rail
rather than clinging to the left edge where the text used to start. */
.shell--nav-collapsed .nav-item {
grid-template-columns: 1fr;
justify-items: center;
padding: 0;
width: 44px;
}
.shell--nav-collapsed .nav-list {
justify-items: center;
}
}
/* The toggle sits with the page title rather than inside the sidebar: it has to
stay reachable at 68px wide, and a control that hides with the thing it
controls is a trap. */
.topbar-lead {
display: flex;
min-width: 0;
align-items: center;
gap: 12px;
}
.nav-toggle {
flex: 0 0 auto;
}