Skip to content

Commit dd7f6c4

Browse files
committed
fix: 一些样式错误
1 parent 38bbaaa commit dd7f6c4

5 files changed

Lines changed: 178 additions & 1 deletion

File tree

app/components/GiscusComments.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,21 @@ export function GiscusComments({ className, docId }: GiscusCommentsProps) {
1313
const { theme } = useTheme();
1414
const normalizedDocId = typeof docId === "string" ? docId.trim() : "";
1515
const useSpecificMapping = normalizedDocId.length > 0;
16+
// mounted 门槛:SSR 阶段 ThemeProvider 的 useState 初值是 defaultTheme("dark"),
17+
// 和客户端 hydrate 后从 localStorage 读到的真实主题可能不同。
18+
// 如果这里直接渲染 Giscus,iframe 会以 SSR 的错主题加载;之后即使 key 变化触发
19+
// remount,@giscus/react 的 iframe 也可能残留旧 theme。延迟到 mount 后再渲染,
20+
// 用的就是客户端已经对齐过 localStorage 的主题。
21+
const [mounted, setMounted] = useState(false);
1622
const [isSystemDark, setIsSystemDark] = useState(() => {
1723
if (typeof window === "undefined") return false;
1824
return window.matchMedia("(prefers-color-scheme: dark)").matches;
1925
});
2026

27+
useEffect(() => {
28+
setMounted(true);
29+
}, []);
30+
2131
useEffect(() => {
2232
if (theme !== "system" || typeof window === "undefined") return;
2333
const media = window.matchMedia("(prefers-color-scheme: dark)");
@@ -34,6 +44,11 @@ export function GiscusComments({ className, docId }: GiscusCommentsProps) {
3444
return theme === "dark" ? "dark" : "light";
3545
}, [isSystemDark, theme]);
3646

47+
if (!mounted) {
48+
// 占位 div 保持布局稳定,避免 mount 前后文档滚动位置跳动
49+
return <div className={className} aria-hidden />;
50+
}
51+
3752
return (
3853
<div className={className}>
3954
<Giscus

app/components/Header.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Link from "next/link";
22
import { getTranslations } from "next-intl/server";
33
import { ThemeToggle } from "./ThemeToggle";
4+
import { LocaleToggle } from "./LocaleToggle";
45
import { Button } from "@/components/ui/button";
56
import { MessageCircle } from "lucide-react";
67
import { Github as GithubIcon } from "./icons/Github";
@@ -29,7 +30,7 @@ export async function Header() {
2930
</div>
3031
</div>
3132

32-
<div className="flex items-center justify-between h-10">
33+
<div className="flex items-center justify-end md:justify-between h-10">
3334
<nav className="hidden md:flex items-center gap-8 font-sans text-xs font-bold uppercase tracking-widest text-[var(--foreground)]">
3435
<Link
3536
href="/docs"
@@ -106,6 +107,7 @@ export async function Header() {
106107
<MessageCircle className="h-4 w-4" />
107108
</a>
108109
</Button>
110+
<LocaleToggle />
109111
<ThemeToggle />
110112
<AuthNav />
111113
</div>

app/components/LocaleToggle.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client";
2+
3+
/**
4+
* Header 里的语言切换按钮(匿名也能用)。
5+
*
6+
* 为什么要做:
7+
* 之前切语言的唯一入口在 /settings 页面,UserMenu 里只有登录用户能看到。
8+
* 访客看到的永远是默认 zh,站点对英语用户非常不友好。
9+
*
10+
* 实现:
11+
* - 写 locale=zh|en 到 document.cookie(path=/,一年有效期,samesite=lax)
12+
* 字段和格式与 SettingsForm 完全一致,登录用户在设置页改的偏好仍然生效
13+
* - 切完 router.refresh() 让 SSR 重新渲染,server component(Hero / docs
14+
* 详情页等)从 cookie 读新 locale 切文案
15+
* - 简单的 ZH / EN 双字母展示,当前语言高亮;button 尺寸与 ThemeToggle 对齐
16+
*/
17+
18+
import { useEffect, useState } from "react";
19+
import { useRouter } from "next/navigation";
20+
import { Button } from "@/components/ui/button";
21+
22+
type Locale = "zh" | "en";
23+
24+
function readLocaleCookie(): Locale {
25+
if (typeof document === "undefined") return "zh";
26+
const m = document.cookie.match(/(?:^|;\s*)locale=([^;]+)/);
27+
const v = m?.[1];
28+
return v === "en" ? "en" : "zh";
29+
}
30+
31+
function writeLocaleCookie(next: Locale) {
32+
// 一年;samesite=lax 够用(这个 cookie 不涉及跨站 POST)
33+
document.cookie = `locale=${next};path=/;max-age=${60 * 60 * 24 * 365};samesite=lax`;
34+
}
35+
36+
export function LocaleToggle() {
37+
const router = useRouter();
38+
// 初始 render 先给默认值避免 hydration 不一致,真实值由 useEffect 读 cookie 后覆盖
39+
const [locale, setLocale] = useState<Locale>("zh");
40+
const [ready, setReady] = useState(false);
41+
42+
useEffect(() => {
43+
setLocale(readLocaleCookie());
44+
setReady(true);
45+
}, []);
46+
47+
const toggle = () => {
48+
const next: Locale = locale === "zh" ? "en" : "zh";
49+
writeLocaleCookie(next);
50+
setLocale(next);
51+
// 刷新 server component 树,重新按 cookie 渲染各页面
52+
router.refresh();
53+
};
54+
55+
return (
56+
<Button
57+
variant="ghost"
58+
size="sm"
59+
onClick={toggle}
60+
aria-label="Toggle language"
61+
title={locale === "zh" ? "切换为 English" : "Switch to 中文"}
62+
className="h-10 px-2 rounded-none font-mono text-xs uppercase tracking-widest transition-colors"
63+
data-umami-event="locale_toggle"
64+
data-umami-event-locale={locale === "zh" ? "en" : "zh"}
65+
>
66+
<span className={ready && locale === "zh" ? "font-bold" : "opacity-50"}>
67+
ZH
68+
</span>
69+
<span className="opacity-30 mx-0.5">/</span>
70+
<span className={ready && locale === "en" ? "font-bold" : "opacity-50"}>
71+
EN
72+
</span>
73+
</Button>
74+
);
75+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"use client";
2+
3+
/**
4+
* Fumadocs sidebar 底部用的 ThemeToggle。
5+
*
6+
* 视觉复刻 fumadocs-ui 内置 ThemeToggle(圆形 border + sun/moon 居中高亮),
7+
* 但 onClick 走自己的 ThemeProvider(layout.tsx 里禁用了 fumadocs 的 next-themes,
8+
* 内置按钮点击无效;这里直接 setTheme 到自建 ThemeProvider 即可)。
9+
*/
10+
11+
import { useEffect, useState } from "react";
12+
import { Moon, Sun } from "lucide-react";
13+
import { useTheme } from "./ThemeProvider";
14+
import { cn } from "@/lib/utils";
15+
16+
export function SidebarThemeToggle() {
17+
const { theme, setTheme } = useTheme();
18+
const [mounted, setMounted] = useState(false);
19+
20+
useEffect(() => {
21+
setMounted(true);
22+
}, []);
23+
24+
// 把 "system" 折算成实际显示主题,决定哪个 icon 高亮
25+
const resolved = (() => {
26+
if (!mounted) return null;
27+
if (theme === "system") {
28+
return typeof window !== "undefined" &&
29+
window.matchMedia("(prefers-color-scheme: dark)").matches
30+
? "dark"
31+
: "light";
32+
}
33+
return theme === "dark" ? "dark" : "light";
34+
})();
35+
36+
return (
37+
<button
38+
type="button"
39+
aria-label="Toggle Theme"
40+
data-theme-toggle=""
41+
className="inline-flex items-center rounded-full border p-1"
42+
onClick={() => {
43+
const next = resolved === "light" ? "dark" : "light";
44+
setTheme(next);
45+
if (typeof window !== "undefined" && window.umami) {
46+
window.umami.track("theme_toggle", { theme: next });
47+
}
48+
}}
49+
>
50+
<Sun
51+
fill="currentColor"
52+
className={cn(
53+
"size-6.5 rounded-full p-1.5",
54+
resolved === "light"
55+
? "bg-fd-accent text-fd-accent-foreground"
56+
: "text-fd-muted-foreground",
57+
)}
58+
/>
59+
<Moon
60+
fill="currentColor"
61+
className={cn(
62+
"size-6.5 rounded-full p-1.5",
63+
resolved === "dark"
64+
? "bg-fd-accent text-fd-accent-foreground"
65+
: "text-fd-muted-foreground",
66+
)}
67+
/>
68+
</button>
69+
);
70+
}

lib/layout.shared.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
22
import { AuthNav } from "@/app/components/AuthNav";
3+
import { LocaleToggle } from "@/app/components/LocaleToggle";
4+
import { SidebarThemeToggle } from "@/app/components/SidebarThemeToggle";
35

46
export async function baseOptions(): Promise<BaseLayoutProps> {
57
return {
68
nav: {
79
title: "内卷地狱",
810
children: (
11+
// 只放登录入口;语言切换挪到 sidebar 底部与主题按钮同排,见下方 themeSwitch
912
<div className="ms-auto flex items-center gap-2 pr-3">
1013
<AuthNav />
1114
</div>
1215
),
1316
},
17+
// Fumadocs sidebar 底部的主题切换槽。
18+
// 内置按钮走 next-themes,但根 layout 里传的是 theme={{ enabled: false }}
19+
// (避免和自建 ThemeProvider 同时写 <html class> 导致闪烁),内置按钮于是点不动。
20+
// 这里替换为自己的 LocaleToggle + ThemeToggle,挂在自建 ThemeProvider 上。
21+
themeSwitch: {
22+
component: (
23+
<div className="ms-auto flex items-center gap-1">
24+
<LocaleToggle />
25+
<SidebarThemeToggle />
26+
</div>
27+
),
28+
},
1429
};
1530
}

0 commit comments

Comments
 (0)