- 作品库卡片重设计:书脊封面字块 + 衬线书名 + 一句话故事(缺失给占位) + 元信息行(题材徽章/StatusDot 待审/mono 时间),Card tone=card 浮起消塌陷 - EmptyState 中性化 + inline/card/bare 变体 + eyebrow(向后兼容) - loading 骨架化(Skeleton 拼作品库结构,motion-safe) - 设置页拆分 ProvidersSettings 489→198 + 5 个子组件,凭据点用 StatusDot - 次级页(向导/模板/codex/大纲)排版字阶与圆角 token 收敛
90 lines
2.4 KiB
TypeScript
90 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import { KeyRound, PlugZap, Route, type LucideIcon } from "lucide-react";
|
|
import type { ReactNode } from "react";
|
|
|
|
import { cardClass } from "@/lib/ui/variants";
|
|
|
|
export type SettingsSection = "routing" | "oauth" | "keys";
|
|
|
|
// 分组的单一标签源:桌面 aside 与移动 SegmentedControl 共用,消除「档位路由 / 路由」漂移。
|
|
export const SETTINGS_SECTIONS: Array<{
|
|
value: SettingsSection;
|
|
label: string;
|
|
icon: LucideIcon;
|
|
}> = [
|
|
{ value: "routing", label: "档位路由", icon: Route },
|
|
{ value: "oauth", label: "OAuth", icon: PlugZap },
|
|
{ value: "keys", label: "API Key", icon: KeyRound },
|
|
];
|
|
|
|
export const SECTION_OPTIONS = SETTINGS_SECTIONS.map(({ value, label }) => ({
|
|
value,
|
|
label,
|
|
}));
|
|
|
|
interface SettingsNavProps {
|
|
activeSection: SettingsSection;
|
|
onSelect: (value: SettingsSection) => void;
|
|
detailFor: (value: SettingsSection) => string;
|
|
}
|
|
|
|
// 桌面侧栏:设置分组导航(移动端由 SegmentedControl 承担)。
|
|
export function SettingsNav({
|
|
activeSection,
|
|
onSelect,
|
|
detailFor,
|
|
}: SettingsNavProps) {
|
|
return (
|
|
<aside className="hidden lg:block">
|
|
<nav aria-label="设置分组" className={cardClass("sticky top-20 p-2")}>
|
|
{SETTINGS_SECTIONS.map((section) => (
|
|
<SettingsNavButton
|
|
key={section.value}
|
|
active={activeSection === section.value}
|
|
icon={<section.icon className="h-4 w-4" aria-hidden="true" />}
|
|
label={section.label}
|
|
detail={detailFor(section.value)}
|
|
onClick={() => onSelect(section.value)}
|
|
/>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
interface SettingsNavButtonProps {
|
|
active: boolean;
|
|
icon: ReactNode;
|
|
label: string;
|
|
detail: string;
|
|
onClick: () => void;
|
|
}
|
|
|
|
function SettingsNavButton({
|
|
active,
|
|
icon,
|
|
label,
|
|
detail,
|
|
onClick,
|
|
}: SettingsNavButtonProps) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
aria-pressed={active}
|
|
onClick={onClick}
|
|
className={`mb-1 flex w-full items-start gap-2 rounded px-3 py-2 text-left transition-colors ${
|
|
active
|
|
? "bg-[var(--color-cinnabar-wash)] text-cinnabar"
|
|
: "text-ink hover:bg-bg hover:text-cinnabar"
|
|
}`}
|
|
>
|
|
<span className="mt-0.5 shrink-0">{icon}</span>
|
|
<span className="min-w-0">
|
|
<span className="block text-sm font-medium">{label}</span>
|
|
<span className="block truncate text-xs text-ink-soft">{detail}</span>
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|