M1 左导航响应式抽屉:抽出 lib/nav/items.ts(条目数据+激活判定,纯逻辑+vitest) 共用于桌面静态侧栏(hidden lg:block)与移动汉堡抽屉。新增通用 Drawer(遮罩/ Esc 关闭/打开聚焦,复用 CommandPalette a11y 模式)+ NavDrawer + NavItems; LeftNav 改 <lg 隐藏,AppShell 顶栏加汉堡(aria-label 打开导航)。 M2 写作工具条不溢出 + 移动端可达章节/助手:Toolbar 改 flex-wrap,390px 不再 横切「尚未保存」;ChapterList/ChapterAssistant 抽出 *Content,Workbench 经 左/右 Drawer 在 <lg 触达目录/助手。 实景 390px 复验(browse):汉堡显示/静态导航隐藏/抽屉开合聚焦+Esc、工具条 两行不溢出、助手抽屉真注入面板。门禁:lint/typecheck 干净 · vitest 177 · build OK。
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
|
||
import {
|
||
kindLabel,
|
||
reasonLabel,
|
||
type InjectionEntity,
|
||
} from "@/lib/workbench/injection";
|
||
import { useInjection } from "@/lib/workbench/useInjection";
|
||
|
||
interface ChapterAssistantProps {
|
||
projectId: string;
|
||
chapterNo: number;
|
||
}
|
||
|
||
// 右栏「本章助手」(UX §6.3)。
|
||
// 「本章注入(透明)」读 B0 端点,列出确定性选中的设定/角色 + 入选理由徽标——
|
||
// 这是「看到的=写章用的」的信任牌(不变量 #6)。「四审」指向审稿页入口。
|
||
// 桌面 aside 包裹;移动端经 Workbench 抽屉复用 AssistantContent。
|
||
export function ChapterAssistant({
|
||
projectId,
|
||
chapterNo,
|
||
}: ChapterAssistantProps) {
|
||
return (
|
||
<aside className="hidden border-l border-line bg-panel py-4 lg:block">
|
||
<AssistantContent projectId={projectId} chapterNo={chapterNo} />
|
||
</aside>
|
||
);
|
||
}
|
||
|
||
export function AssistantContent({
|
||
projectId,
|
||
chapterNo,
|
||
}: ChapterAssistantProps) {
|
||
const injection = useInjection(projectId, chapterNo);
|
||
const selected = injection.data?.selected ?? [];
|
||
|
||
return (
|
||
<div className="px-4">
|
||
<h2 className="text-sm font-semibold text-ink">本章助手</h2>
|
||
|
||
<section className="mt-4">
|
||
<h3 className="text-xs font-semibold uppercase tracking-wide text-ink-soft">
|
||
本章注入(透明)
|
||
</h3>
|
||
|
||
{injection.loading ? (
|
||
<p className="mt-2 text-xs text-ink-soft">加载注入信息…</p>
|
||
) : injection.error ? (
|
||
<p className="mt-2 rounded border border-dashed border-line bg-bg p-3 text-xs text-ink-soft">
|
||
{injection.error}
|
||
</p>
|
||
) : selected.length === 0 ? (
|
||
<p className="mt-2 rounded border border-dashed border-line bg-bg p-3 text-xs text-ink-soft">
|
||
本章暂无注入实体(先在大纲点名设定/角色)。
|
||
</p>
|
||
) : (
|
||
<ul className="mt-2 space-y-2">
|
||
{selected.map((entity) => (
|
||
<EntityRow key={`${entity.kind}:${entity.name}`} entity={entity} />
|
||
))}
|
||
</ul>
|
||
)}
|
||
</section>
|
||
|
||
<section className="mt-6">
|
||
<h3 className="text-xs font-semibold uppercase tracking-wide text-ink-soft">
|
||
四审
|
||
</h3>
|
||
<p className="mt-2 text-xs text-ink-soft">
|
||
一致性 / 伏笔 / 文风 / 节奏,在审稿页逐条裁决。
|
||
</p>
|
||
<Link
|
||
href={`/projects/${projectId}/review?chapter=${chapterNo}`}
|
||
className="mt-2 inline-block rounded border border-cinnabar px-3 py-1.5 text-xs text-cinnabar hover:bg-[var(--color-cinnabar-wash)]"
|
||
>
|
||
去审稿 →
|
||
</Link>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function EntityRow({ entity }: { entity: InjectionEntity }) {
|
||
return (
|
||
<li className="rounded border border-line bg-bg p-2">
|
||
<div className="flex items-center gap-2">
|
||
<span className="rounded bg-panel px-1.5 py-0.5 text-[10px] text-ink-soft">
|
||
{kindLabel(entity.kind)}
|
||
</span>
|
||
<span className="text-sm text-ink">{entity.name}</span>
|
||
</div>
|
||
<div className="mt-1.5 flex flex-wrap gap-1">
|
||
{(entity.reasons ?? []).map((reason) => (
|
||
<span
|
||
key={reason}
|
||
className="rounded bg-[var(--color-cinnabar-wash)] px-1.5 py-0.5 text-[10px] text-cinnabar"
|
||
>
|
||
{reasonLabel(reason)}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</li>
|
||
);
|
||
}
|