Files
NianAIGC/components/app-shell.tsx
2026-05-29 10:26:02 +08:00

63 lines
2.1 KiB
TypeScript

"use client";
import { useEffect, useRef } from "react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
Archive,
Settings,
Sparkles
} from "lucide-react";
import clsx from "clsx";
import { revealChildren, runScopedMotion } from "@/lib/ui/motion";
const nav = [
{ href: "/create", label: "创作", icon: Sparkles },
{ href: "/assets", label: "结果", icon: Archive },
{ href: "/settings", label: "设置", icon: Settings }
];
export function AppShell({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const shellRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
return runScopedMotion(shellRef, (scope) => revealChildren(scope, "[data-shell-animate]"));
}, []);
return (
<div className="app-shell" ref={shellRef}>
<a className="skip-link" href="#main-content"></a>
<header className="topbar" data-shell-animate>
<Link className="brand" href="/create" aria-label="智念AIGC平台">
<span className="brand-mark" aria-hidden="true">
<Image className="brand-logo-img" src="/logo/zhinian-logo.png" alt="" width={124} height={28} priority />
</span>
<div>
<div className="brand-title">AIGC平台</div>
</div>
</Link>
<nav className="nav top-nav" aria-label="主导航">
{nav.map((item) => {
const Icon = item.icon;
const active = item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
return (
<Link
key={item.href}
href={item.href}
className={clsx("nav-link", active && "active")}
aria-current={active ? "page" : undefined}
>
<Icon aria-hidden="true" />
<span>{item.label}</span>
</Link>
);
})}
</nav>
</header>
<main className="main" id="main-content" tabIndex={-1}>{children}</main>
</div>
);
}