feat: Update sidebar controls and navigation for improved UX and accessibility

This commit is contained in:
KodekaTeam committed 2026-09-04 00:37:03 +07:00
1 parent fea9940774
commit 45a6cb2173
4 files changed
+83 -59

No files matched your search

+38 -1
View File
@@ -978,7 +978,44 @@ body {
transition: width 0.5s ease;
}
/* Sidebar nav items */
/* Sidebar controls and navigation */
.sidebar-trigger {
width: 32px;
height: 32px;
flex: 0 0 32px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid transparent;
border-radius: 7px;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
transition:
background 0.15s ease,
border-color 0.15s ease,
color 0.15s ease;
}
.sidebar-trigger:hover {
background: var(--bg-input);
border-color: var(--border);
color: var(--text-primary);
}
.sidebar-trigger:focus-visible {
outline: 2px solid var(--accent-blue);
outline-offset: 2px;
}
.topbar-section-divider {
width: 1px;
height: 20px;
flex: 0 0 1px;
background: var(--border);
}
.nav-item {
display: flex;
align-items: center;
+4 -1
View File
@@ -74,7 +74,6 @@ export default function DashboardLayout({
{/* Sidebar — handles its own backdrop overlay for mobile */}
<Sidebar
collapsed={collapsed}
onToggle={() => setCollapsed(!collapsed)}
isMobile={isMobile}
mobileOpen={mobileOpen}
onMobileClose={() => setMobileOpen(false)}
@@ -96,6 +95,10 @@ export default function DashboardLayout({
onMobileMenuToggle={
isMobile ? () => setMobileOpen(!mobileOpen) : undefined
}
onSidebarToggle={
isMobile ? undefined : () => setCollapsed((value) => !value)
}
sidebarCollapsed={collapsed}
/>
<main
className="p-4 md:p-6"
+14 -55
View File
@@ -10,13 +10,11 @@ import OrganizationSwitcher from "@/components/OrganizationSwitcher";
import { navigation } from "@/lib/navigation";
import { addPreferencesListener, getStoredPanelName } from "@/lib/preferences";
import { formatRoleLabel, hasMinimumRole } from "@/lib/permissions";
import { LogOut, ChevronsUpDown, ChevronLeft, ChevronRight, X, Crown } from "lucide-react";
import { LogOut, ChevronsUpDown, X, Crown } from "lucide-react";
interface SidebarProps {
/** Desktop: whether sidebar is in collapsed (icon-only) state */
collapsed: boolean;
/** Desktop: called when user clicks the collapse toggle */
onToggle: () => void;
/** Mobile: whether screen is narrow */
isMobile: boolean;
/** Mobile: whether the drawer is open */
@@ -27,7 +25,6 @@ interface SidebarProps {
export default function Sidebar({
collapsed,
onToggle,
isMobile,
mobileOpen,
onMobileClose,
@@ -124,6 +121,7 @@ export default function Sidebar({
const sidebar = (
<aside
id="dashboard-sidebar"
style={{
position: "relative",
width: isMobile ? "240px" : isCollapsed ? "60px" : "220px",
@@ -136,7 +134,7 @@ export default function Sidebar({
flexShrink: 0,
}}
>
{/* ── Logo + collapse/close button ──────────────────────── */}
{/* ── Logo + mobile close button ────────────────────────── */}
<div
style={{
padding: isCollapsed ? "0 12px" : "0 12px 0 16px",
@@ -195,57 +193,18 @@ export default function Sidebar({
</div>
)}
{/* Collapse toggle (desktop) or close button (mobile) */}
<button
onClick={isMobile ? onMobileClose : onToggle}
title={
isMobile
? "Close menu"
: isCollapsed
? "Expand sidebar"
: "Collapse sidebar"
}
style={{
position: isMobile ? "static" : "absolute",
right: isMobile ? undefined : -14,
top: isMobile ? undefined : 18,
zIndex: 50,
flexShrink: 0,
width: 28,
height: 28,
borderRadius: "50%",
background: "var(--bg-sidebar)",
border: "1px solid var(--border)",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
color: "var(--text-muted)",
transition: "background 0.15s, color 0.15s, border-color 0.15s",
marginLeft: isMobile ? "auto" : undefined,
boxShadow: isMobile ? "none" : "0 2px 8px rgba(0,0,0,0.2)",
}}
onMouseEnter={(e) => {
const btn = e.currentTarget;
btn.style.background = "rgba(59,130,246,0.12)";
btn.style.borderColor = "rgba(59,130,246,0.4)";
btn.style.color = "#3b82f6";
}}
onMouseLeave={(e) => {
const btn = e.currentTarget;
btn.style.background = "var(--bg-sidebar)";
btn.style.borderColor = "var(--border)";
btn.style.color = "var(--text-muted)";
}}
>
{isMobile ? (
{isMobile && (
<button
type="button"
onClick={onMobileClose}
title="Close menu"
aria-label="Close menu"
className="sidebar-trigger"
style={{ marginLeft: "auto" }}
>
<X size={13} />
) : isCollapsed ? (
<ChevronRight size={13} />
) : (
<ChevronLeft size={13} />
)}
</button>
</button>
)}
</div>
<OrganizationSwitcher
+27 -2
View File
@@ -19,6 +19,7 @@ import {
LoaderCircle,
Menu,
Moon,
PanelLeft,
RefreshCw,
Search,
SunMedium,
@@ -56,12 +57,16 @@ interface TopbarProps {
title: string;
subtitle?: string;
onMobileMenuToggle?: () => void;
onSidebarToggle?: () => void;
sidebarCollapsed?: boolean;
}
export default function Topbar({
title,
subtitle,
onMobileMenuToggle,
onSidebarToggle,
sidebarCollapsed = false,
}: TopbarProps) {
const currentUser = useCurrentUser();
const pathname = usePathname();
@@ -424,9 +429,9 @@ export default function Topbar({
}}
className="md:px-6 md:gap-4"
>
{/* Left: hamburger (mobile) + title */}
{/* Left: navigation control + title */}
<div
style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}
style={{ display: "flex", alignItems: "center", gap: 10, minWidth: 0 }}
>
{onMobileMenuToggle && (
<button
@@ -447,6 +452,26 @@ export default function Topbar({
<Menu size={20} />
</button>
)}
{onSidebarToggle && (
<>
<button
type="button"
onClick={onSidebarToggle}
className="sidebar-trigger"
aria-controls="dashboard-sidebar"
aria-expanded={!sidebarCollapsed}
aria-label={
sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"
}
title={
sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"
}
>
<PanelLeft size={18} strokeWidth={1.8} />
</button>
<span className="topbar-section-divider" aria-hidden="true" />
</>
)}
<div style={{ minWidth: 0, overflow: "hidden", whiteSpace: "nowrap" }}>
<h1
style={{