feat: Update sidebar controls and navigation for improved UX and accessibility
This commit is contained in:
1 parent
fea9940774
commit
45a6cb2173
4 files changed
+83
-59
No files matched your search
+38
-1
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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={{
|
||||
|
||||
Reference in new issue
Block a user