fix(web): wire responsive sidebar — add toggle to AppShell, open prop on Sidebar, CSS !important overrides
This commit is contained in:
parent
e362f2aefe
commit
89edf59a4e
@ -239,22 +239,31 @@ button:active:not(:disabled), [role="button"]:active:not(:disabled) {
|
|||||||
/* Responsive sidebar */
|
/* Responsive sidebar */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.app-sidebar {
|
.app-sidebar {
|
||||||
position: fixed;
|
position: fixed !important;
|
||||||
left: -260px;
|
left: -280px !important;
|
||||||
top: 0;
|
top: 0 !important;
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
width: 240px;
|
width: 260px !important;
|
||||||
height: 100vh;
|
height: 100vh !important;
|
||||||
transition: left 0.2s ease;
|
transition: left 0.2s ease;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.app-sidebar.open { left: 0; }
|
.app-sidebar.open { left: 0 !important; }
|
||||||
.sidebar-overlay { display: none; position: fixed; inset: 0; z-index: 39; background: rgba(0,0,0,0.5); }
|
.sidebar-overlay { display: none; position: fixed; inset: 0; z-index: 39; background: rgba(0,0,0,0.5); }
|
||||||
.sidebar-overlay.open { display: block; }
|
.sidebar-overlay.open { display: block; }
|
||||||
.sidebar-toggle { display: flex; }
|
.sidebar-toggle {
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
position: fixed; top: 12px; left: 12px; z-index: 38;
|
||||||
|
width: 40px; height: 40px; border-radius: 8px;
|
||||||
|
border: 1px solid var(--nl-border-default, #2a2a4a);
|
||||||
|
background: var(--nl-bg-elevated, #12151c);
|
||||||
|
color: var(--nl-text-primary, #fff);
|
||||||
|
cursor: pointer; font-size: 20px; line-height: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
.sidebar-toggle { display: none; }
|
.sidebar-toggle { display: none !important; }
|
||||||
.sidebar-overlay { display: none; }
|
.sidebar-overlay { display: none !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Respect user motion preference */
|
/* Respect user motion preference */
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
import type { ReactNode } from "react";
|
"use client";
|
||||||
|
|
||||||
|
import { type ReactNode, useState, useCallback, useEffect } from "react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import { Sidebar } from "@/components/Sidebar";
|
import { Sidebar } from "@/components/Sidebar";
|
||||||
|
|
||||||
export function AppShell({
|
export function AppShell({
|
||||||
@ -12,12 +15,35 @@ export function AppShell({
|
|||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSidebarOpen(false);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const toggle = useCallback(() => setSidebarOpen((o) => !o), []);
|
||||||
|
const close = useCallback(() => setSidebarOpen(false), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<a href="#main-content" className="skip-link">
|
<a href="#main-content" className="skip-link">
|
||||||
Skip to main content
|
Skip to main content
|
||||||
</a>
|
</a>
|
||||||
<Sidebar />
|
<button
|
||||||
|
className="sidebar-toggle"
|
||||||
|
onClick={toggle}
|
||||||
|
aria-label={sidebarOpen ? "Close menu" : "Open menu"}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{sidebarOpen ? "\u2715" : "\u2630"}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className={`sidebar-overlay${sidebarOpen ? " open" : ""}`}
|
||||||
|
onClick={close}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<Sidebar open={sidebarOpen} />
|
||||||
<main id="main-content" className="main-panel" tabIndex={-1} aria-labelledby="page-title">
|
<main id="main-content" className="main-panel" tabIndex={-1} aria-labelledby="page-title">
|
||||||
<div className="page-grid">
|
<div className="page-grid">
|
||||||
<header
|
<header
|
||||||
|
|||||||
@ -14,11 +14,11 @@ const navItems: { href: string; label: string; icon: typeof House; flag?: string
|
|||||||
{ href: "/settings", label: "Settings", icon: Settings },
|
{ href: "/settings", label: "Settings", icon: Settings },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar({ open }: { open?: boolean }) {
|
||||||
const pathname = usePathname() ?? "";
|
const pathname = usePathname() ?? "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar" style={{ padding: "var(--nl-space-6)" }} aria-label="Primary">
|
<aside className={`sidebar app-sidebar${open ? ' open' : ''}`} style={{ padding: "var(--nl-space-6)" }} aria-label="Primary">
|
||||||
<div style={{ display: "grid", gap: "var(--nl-space-6)" }}>
|
<div style={{ display: "grid", gap: "var(--nl-space-6)" }}>
|
||||||
<div className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
|
<div className="surface-card" style={{ padding: "var(--nl-space-5)", display: "grid", gap: "var(--nl-space-3)" }}>
|
||||||
<div className="badge" style={{ width: "fit-content" }}>
|
<div className="badge" style={{ width: "fit-content" }}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user