Refine Tabularium into a readable light documentation workspace
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 4m26s

This commit is contained in:
2026-10-27 15:55:59 +08:00
parent 5c48dec887
commit 75debc0068
29 changed files with 2116 additions and 54 deletions
+234
View File
@@ -0,0 +1,234 @@
import Link from "next/link";
import { useEffect, useRef, useState, type ReactNode } from "react";
import type { DocLink } from "@/lib/docs";
import { sectionFor } from "@/lib/navigation";
export function Brand() {
return (
<Link className="brand" href="/" aria-label="Tabularium 首页">
<span className="brand-mark" aria-hidden="true">
<i />
<i />
<i />
</span>
<span>
Endor <span className="brand-divider">/</span>{" "}
<strong>Tabularium</strong>
</span>
<span className="docs-badge">文档</span>
</Link>
);
}
export function Navigation({
docs,
current,
onNavigate,
}: {
docs: DocLink[];
current?: string;
onNavigate?: () => void;
}) {
const groups = [...new Set(docs.map((d) => sectionFor(d.slug).group))];
return (
<nav aria-label="文档导航">
<Link
onClick={onNavigate}
className={`nav-home ${!current ? "selected" : ""}`}
href="/"
>
▦ <span>文档概览</span>
</Link>
{groups.map((group) => (
<div className="nav-group" key={group}>
<p>{group}</p>
{docs
.filter((d) => sectionFor(d.slug).group === group)
.map((d) => (
<Link
onClick={onNavigate}
aria-current={d.slug === current ? "page" : undefined}
className="nav-item"
href={`/docs/${d.slug}`}
key={d.slug}
>
{d.title}
{d.slug.includes("draft") && (
<span className="tiny-dot" aria-label="草案" />
)}
</Link>
))}
</div>
))}
<div className="nav-note">
<span className="status-dot" /> 与代码一起演进
<p>文字、设计与图形的共同档案。</p>
</div>
</nav>
);
}
export function Shell({
docs,
current,
children,
}: {
docs: DocLink[];
current?: string;
children: ReactNode;
}) {
const dialog = useRef<HTMLDialogElement>(null);
const input = useRef<HTMLInputElement>(null);
const trigger = useRef<HTMLButtonElement>(null);
const [query, setQuery] = useState("");
const [selected, setSelected] = useState(0);
const [menu, setMenu] = useState(false);
const results = docs.filter((d) =>
`${d.title} ${d.searchText}`
.toLocaleLowerCase()
.includes(query.trim().toLocaleLowerCase()),
);
function openSearch() {
setQuery("");
setSelected(0);
dialog.current?.showModal();
input.current?.focus();
}
function closeSearch() {
dialog.current?.close();
trigger.current?.focus();
}
useEffect(() => {
function keyboard(e: KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
dialog.current?.showModal();
input.current?.focus();
}
}
document.addEventListener("keydown", keyboard);
return () => document.removeEventListener("keydown", keyboard);
}, []);
return (
<>
<a className="skip-link" href="#content">
跳到正文
</a>
<header className="site-header">
<Brand />
<nav className="top-nav" aria-label="主导航">
<Link href="/">文档概览</Link>
<Link href="/docs/02-mounting-draft">架构设计</Link>
<a href="https://gitea.entroia.com/Zayden-Jung/endorphin-tabularium-docs">
仓库 ↗
</a>
</nav>
<button
ref={trigger}
className="search-trigger"
aria-label="搜索文档"
onClick={openSearch}
>
<span aria-hidden="true">⌕</span>
<span>搜索文档…</span>
<kbd>⌘ K</kbd>
</button>
</header>
<div className="mobile-bar">
<button
onClick={() => setMenu(!menu)}
aria-expanded={menu}
aria-controls="mobile-navigation"
>
☰ <span>文档目录</span>
<span>{menu ? "−" : "+"}</span>
</button>
</div>
{menu && (
<div className="mobile-navigation" id="mobile-navigation">
<Navigation
docs={docs}
current={current}
onNavigate={() => setMenu(false)}
/>
</div>
)}
{children}
<dialog
ref={dialog}
className="search-dialog"
aria-label="搜索文档"
onClick={(e) => {
if (e.target === e.currentTarget) closeSearch();
}}
onClose={() => trigger.current?.focus()}
>
<div className="search-input-row">
<span aria-hidden="true">⌕</span>
<input
ref={input}
value={query}
placeholder="搜索文档、命令或概念…"
aria-label="搜索文档内容"
aria-controls="search-results"
onChange={(e) => {
setQuery(e.target.value);
setSelected(0);
}}
onKeyDown={(e) => {
if (e.key === "ArrowDown") {
e.preventDefault();
setSelected((i) =>
Math.max(0, Math.min(i + 1, results.length - 1)),
);
}
if (e.key === "ArrowUp") {
e.preventDefault();
setSelected((i) => Math.max(i - 1, 0));
}
if (e.key === "Enter") {
e.preventDefault();
dialog.current
?.querySelector<HTMLAnchorElement>(
`[data-result="${selected}"]`,
)
?.click();
}
}}
/>
<button onClick={closeSearch} aria-label="关闭搜索">
Esc
</button>
</div>
<div id="search-results" className="search-results">
<p className="eyebrow" role="status">
{query ? `${results.length} 篇匹配文档` : "浏览文档"}
</p>
{results.map((d, i) => (
<Link
data-result={i}
className={
selected === i ? "search-result selected" : "search-result"
}
onClick={closeSearch}
key={d.slug}
href={`/docs/${d.slug}`}
>
<span>{sectionFor(d.slug).group}</span>
<strong>
{d.title} <span aria-hidden="true">↗</span>
</strong>
<small>{sectionFor(d.slug).description}</small>
</Link>
))}
{!results.length && (
<p className="empty-search">
没有找到相关文档。试试「节点」「SVG」或「pnpm」。
</p>
)}
</div>
<div className="search-footer">
↑ ↓ 选择 <span>Enter 打开 · Esc 关闭</span>
</div>
</dialog>
</>
);
}