Files
FootPrint/app/src/components/layout/Header.tsx
T
root 3613b9f5e1 polish(ui): full-system layout audit, header titles for all routes, dialog title truncation
- headless DOM audit (Edge dump, 1440/390 x 10 routes): no real horizontal page overflow on any screen size; timeline bleed & drawer offsets are intentional and verified benign
- header now labels profile/resume/assessment/admin routes (was blank h1 on those pages)
- dialog header truncates long titles instead of pushing controls out
- page containers audited for consistent rhythm (1080/1280/880 widths share identical paddings)
- www rebuilt, temp instrumentation removed
2026-10-17 19:51:52 +08:00

72 lines
3.1 KiB
TypeScript

import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { Search, Sun, Moon, Plus, Menu } from 'lucide-react';
import { Button, Input } from '@/components/ui';
const TITLES: Record<string, { title: string; sub?: string }> = {
'/timeline': { title: '时间线', sub: '按学年与月份回顾全部足迹' },
'/records': { title: '记录', sub: '全部足迹的检索与管理' },
'/grades': { title: '成绩', sub: '学年学科成绩 · 梯度与总览' },
'/dashboard': { title: '统计', sub: '量化个人成长收获' },
'/types': { title: '记录类型', sub: '类型与字段模板管理' },
'/taxonomy': { title: '字典与维度', sub: '级别、角色与成长维度' },
'/profile': { title: '个人信息', sub: '学籍与联系方式维护' },
'/resume': { title: '我的简历', sub: '模板配置 · Word 导出' },
'/assessment': { title: '综合测评', sub: '学年素质测评与材料导出' },
'/admin': { title: '管理', sub: '用户与邀请码 · 数据维护' },
};
export function Header({ onMenu }: { onMenu?: () => void }) {
const { pathname } = useLocation();
const navigate = useNavigate();
const [q, setQ] = useState('');
const [dark, setDark] = useState(document.documentElement.classList.contains('dark'));
const meta = TITLES[pathname] ?? { title: '' };
const toggleTheme = () => {
const next = !dark;
setDark(next);
document.documentElement.classList.toggle('dark', next);
localStorage.setItem('fp-theme', next ? 'dark' : 'light');
window.dispatchEvent(new CustomEvent('fp-theme-change'));
};
return (
<header className="no-print flex h-14 shrink-0 items-center gap-2 border-b border-line bg-surface px-3 sm:gap-4 sm:px-5">
<Button variant="ghost" size="icon" className="lg:hidden" onClick={onMenu} aria-label="打开导航">
<Menu size={17} />
</Button>
<div className="min-w-0">
<h1 className="truncate text-[15px] font-semibold leading-tight text-ink-1">{meta.title}</h1>
{meta.sub && <p className="truncate text-[11px] leading-tight text-ink-3">{meta.sub}</p>}
</div>
<div className="relative ml-auto hidden w-[240px] md:block lg:w-[280px]">
<Search size={14} className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-ink-3" />
<Input
className="pl-8"
placeholder="搜索标题、内容、结果、标签"
value={q}
onChange={e => setQ(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter' && q.trim()) {
navigate('/records?q=' + encodeURIComponent(q.trim()));
}
}}
/>
</div>
<Button variant="ghost" size="icon" onClick={toggleTheme} aria-label="切换主题">
{dark ? <Sun size={15} /> : <Moon size={15} />}
</Button>
<Button variant="primary" onClick={() => navigate('/records?new=1')} aria-label="新建记录">
<Plus size={14} />
<span className="hidden sm:inline">新建记录</span>
</Button>
</header>
);
}