88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
import Link from "next/link";
|
|
import { ChevronLeft, Home, MessageCircle, Smartphone, UserRound } from "lucide-react";
|
|
import type { ReactNode } from "react";
|
|
|
|
const navigationItems = [
|
|
{ href: "/", label: "概览", icon: Home },
|
|
{ href: "/messages", label: "留言", icon: MessageCircle },
|
|
{ href: "/devices", label: "设备", icon: Smartphone },
|
|
{ href: "/settings", label: "我的", icon: UserRound },
|
|
];
|
|
|
|
function isActivePath(currentPath: string, href: string) {
|
|
if (href === "/") return currentPath === "/";
|
|
return currentPath === href || currentPath.startsWith(`${href}/`);
|
|
}
|
|
|
|
type PanelShellProps = {
|
|
currentPath: string;
|
|
title: string;
|
|
description?: string;
|
|
caregiverLabel: string;
|
|
children: ReactNode;
|
|
actions?: ReactNode;
|
|
backHref?: string;
|
|
showTabs?: boolean;
|
|
unreadCount?: number;
|
|
};
|
|
|
|
export function PanelShell({
|
|
currentPath,
|
|
title,
|
|
description,
|
|
caregiverLabel,
|
|
children,
|
|
actions,
|
|
backHref,
|
|
showTabs = !backHref,
|
|
unreadCount = 0,
|
|
}: PanelShellProps) {
|
|
const initial = caregiverLabel.trim().slice(-1) || "安";
|
|
|
|
return (
|
|
<main className="app-frame">
|
|
<div className={`app-content app-stack${backHref ? " detail" : ""}`}>
|
|
{backHref ? (
|
|
<header className="detail-header">
|
|
<Link href={backHref} className="back-button" aria-label="返回">
|
|
<ChevronLeft size={20} strokeWidth={2} />
|
|
</Link>
|
|
<h1>{title}</h1>
|
|
{actions}
|
|
</header>
|
|
) : (
|
|
<header className="page-heading">
|
|
<div>
|
|
<h1>{title}</h1>
|
|
{description ? <p>{description}</p> : null}
|
|
</div>
|
|
{actions ?? (
|
|
<Link href="/settings" className="avatar" aria-label="我的">
|
|
{initial}
|
|
</Link>
|
|
)}
|
|
</header>
|
|
)}
|
|
|
|
{children}
|
|
</div>
|
|
|
|
{showTabs ? (
|
|
<nav className="bottom-nav" aria-label="主导航">
|
|
{navigationItems.map((item) => {
|
|
const active = isActivePath(currentPath, item.href);
|
|
const Icon = item.icon;
|
|
return (
|
|
<Link key={item.href} href={item.href} className={`nav-item${active ? " active" : ""}`}>
|
|
<Icon size={22} strokeWidth={1.8} />
|
|
<span>{item.label}</span>
|
|
{item.href === "/messages" && unreadCount > 0 ? <span className="nav-badge">{unreadCount}</span> : null}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
) : null}
|
|
</main>
|
|
);
|
|
}
|