Initial commit: accountbook project
- Node.js backend server - Frontend application - Backup script - Project specification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Icon } from './icons';
|
||||
|
||||
const tabs = [
|
||||
{ href: '/', label: '首页', icon: 'home' },
|
||||
{ href: '/stats', label: '统计', icon: 'chart' },
|
||||
{ href: '/recurring', label: '周期', icon: 'repeat' },
|
||||
{ href: '/categories', label: '分类', icon: 'tags' },
|
||||
];
|
||||
|
||||
export default function AppShell({ title, subtitle, actions, headerContent, children, contentClassName = '', hideNav = false, compactHeader = false }) {
|
||||
const router = useRouter();
|
||||
|
||||
const activeTab = useMemo(() => {
|
||||
const matched = tabs.find((tab) => tab.href === router.pathname);
|
||||
return matched?.href || null;
|
||||
}, [router.pathname]);
|
||||
|
||||
const headerNode = headerContent || (title ? (
|
||||
<div className="app-header__text">
|
||||
<h1>{title}</h1>
|
||||
{subtitle ? <p>{subtitle}</p> : null}
|
||||
</div>
|
||||
) : null);
|
||||
|
||||
const headerClassName = [
|
||||
'app-header',
|
||||
compactHeader ? 'app-header--compact' : '',
|
||||
headerNode ? 'app-header--with-content' : 'app-header--actions-only',
|
||||
actions ? 'app-header--with-actions' : '',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div className={`app-shell${hideNav ? ' app-shell--standalone' : ''}`}>
|
||||
<header className={headerClassName}>
|
||||
<div className="app-shell__container app-header__inner">
|
||||
{headerNode ? <div className="app-header__main">{headerNode}</div> : <div className="app-header__spacer" aria-hidden="true" />}
|
||||
{actions ? <div className="app-header__actions">{actions}</div> : null}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className={`app-content ${contentClassName}`.trim()}>
|
||||
<div className="app-shell__container app-content__inner">{children}</div>
|
||||
</main>
|
||||
|
||||
{!hideNav ? (
|
||||
<nav className="bottom-nav" aria-label="主导航">
|
||||
<div className="app-shell__container bottom-nav__inner">
|
||||
{tabs.map((tab) => {
|
||||
const isActive = activeTab === tab.href;
|
||||
return (
|
||||
<button
|
||||
key={tab.href}
|
||||
type="button"
|
||||
className={`bottom-nav__item${isActive ? ' is-active' : ''}`}
|
||||
onClick={() => router.push(tab.href)}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
>
|
||||
<Icon name={tab.icon} size={20} />
|
||||
<span>{tab.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user