64 lines
2.1 KiB
TypeScript
64 lines
2.1 KiB
TypeScript
import { useState } from 'react';
|
||
import { NavLink, Outlet } from 'react-router-dom';
|
||
import { navigationItems } from './navigation.js';
|
||
|
||
export function AppShell() {
|
||
const [isNavigationOpen, setIsNavigationOpen] = useState(false);
|
||
|
||
return (
|
||
<div className="app-shell">
|
||
<a className="skip-link" href="#main-content">Skip to content</a>
|
||
<aside className={isNavigationOpen ? 'sidebar is-open' : 'sidebar'}>
|
||
<div className="brand">
|
||
<span className="brand-mark" aria-hidden="true">R</span>
|
||
<div>
|
||
<strong>Reddit Bot</strong>
|
||
<span>Operator console</span>
|
||
</div>
|
||
</div>
|
||
<nav aria-label="Primary navigation" id="primary-navigation">
|
||
{navigationItems.map((item) => (
|
||
<NavLink
|
||
aria-label={item.label}
|
||
className={({ isActive }) => (isActive ? 'nav-link is-active' : 'nav-link')}
|
||
end={item.path === '/'}
|
||
key={item.path}
|
||
onClick={() => setIsNavigationOpen(false)}
|
||
to={item.path}
|
||
>
|
||
<span>{item.label}</span>
|
||
<small>{item.description}</small>
|
||
</NavLink>
|
||
))}
|
||
</nav>
|
||
</aside>
|
||
{isNavigationOpen ? (
|
||
<button
|
||
aria-label="Close navigation backdrop"
|
||
className="navigation-backdrop"
|
||
onClick={() => setIsNavigationOpen(false)}
|
||
type="button"
|
||
/>
|
||
) : null}
|
||
<div className="workspace">
|
||
<header className="mobile-header">
|
||
<span>Reddit Bot</span>
|
||
<button
|
||
aria-controls="primary-navigation"
|
||
aria-expanded={isNavigationOpen}
|
||
aria-label={isNavigationOpen ? 'Close navigation' : 'Open navigation'}
|
||
className="icon-button"
|
||
onClick={() => setIsNavigationOpen((current) => !current)}
|
||
type="button"
|
||
>
|
||
<span aria-hidden="true">{isNavigationOpen ? '×' : '☰'}</span>
|
||
</button>
|
||
</header>
|
||
<main id="main-content" tabIndex={-1}>
|
||
<Outlet />
|
||
</main>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|