From e382f2d4c4ba667a892407c928de82c1fc22c50f Mon Sep 17 00:00:00 2001 From: AI Directory Platform Date: Thu, 10 Sep 2026 09:29:17 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20header=20=E6=8E=A7=E4=BB=B6=E5=8C=BA?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E6=8A=96=E5=8A=A8=E6=B8=85=E9=9B=B6=EF=BC=88?= =?UTF-8?q?CLS=20=E4=BF=AE=E5=A4=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ModeToggle:旧实现在 isClient 前完全不渲染(SSR 无按钮 → hydrate 后 凭空多出 40px)。改为恒渲染固定 40px 按钮:挂载前显示 Sun 占位图标, 挂载后切换当前主题图标(动画保留) - UserMenu:isPending 时渲染 24px 小圆(与终态 135px 差距大)。改为 与未登录终态同尺寸骨架:Sign in 透明文字(带 muted 底色块)+ Sign Up 半透明胶囊,右缘与终态精确对齐,占位色跟随各站主题 - SSR HTML 验证:首帧即含 Toggle theme 占位按钮 / Select language / UserMenu aria-hidden 骨架(curl 实证) - 逐帧截图验收:commit 时刻与 hydrate 后 header 右上角布局一致; Language/Toggle x 坐标全程稳定 --- packages/core/src/components/mode-toggle.tsx | 96 +++++++++---------- .../navigation/components/user-menu.tsx | 10 +- sites/findai/tsconfig.json | 5 +- sites/launchai/tsconfig.json | 5 +- sites/the-ai-ledger/tsconfig.json | 5 +- 5 files changed, 62 insertions(+), 59 deletions(-) diff --git a/packages/core/src/components/mode-toggle.tsx b/packages/core/src/components/mode-toggle.tsx index b260afb..767f1c5 100644 --- a/packages/core/src/components/mode-toggle.tsx +++ b/packages/core/src/components/mode-toggle.tsx @@ -2,10 +2,16 @@ import * as React from "react"; import { useTheme } from "next-themes"; -import { MoonIcon } from "lucide-react"; +import { MoonIcon, SunMedium } from "lucide-react"; import { IconSunLow } from "@tabler/icons-react"; import { motion } from "framer-motion"; +/** + * # 组件 ModeToggle + * 说明:深浅模式切换按钮。按钮**恒渲染**(固定 40px)——挂载前显示 Sun 占位图标 + * (next-themes 挂载前主题未知,图标以占位代替),挂载后切换为当前主题图标。 + * 消除旧实现(isClient 前不渲染)导致的 header 宽度抖动(CLS)。 + */ export function ModeToggle() { const { theme, setTheme } = useTheme(); @@ -15,56 +21,48 @@ export function ModeToggle() { setIsClient(true); }, []); - return ( - isClient && ( + // 挂载前:固定占位图标(与真实按钮同尺寸,无布局位移) + if (!isClient) { + return ( - ) + ); + } + + return ( + ); } diff --git a/packages/core/src/features/navigation/components/user-menu.tsx b/packages/core/src/features/navigation/components/user-menu.tsx index a966bad..3ee36c3 100644 --- a/packages/core/src/features/navigation/components/user-menu.tsx +++ b/packages/core/src/features/navigation/components/user-menu.tsx @@ -35,8 +35,16 @@ export function UserMenu() { }, [session.data?.user?.id]); if (session.isPending) { + // 占位与"未登录"终态同尺寸(Sign in 文字 + Sign Up 按钮),消除宽度抖动 return ( -
+
+ + {t('common.actions.signIn')} + + + {t('common.actions.signUp')} + +
); } diff --git a/sites/findai/tsconfig.json b/sites/findai/tsconfig.json index 95fd293..c20f005 100644 --- a/sites/findai/tsconfig.json +++ b/sites/findai/tsconfig.json @@ -15,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -55,8 +55,7 @@ "next-env.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts" + ".next/types/**/*.ts" ], "exclude": [ "node_modules" diff --git a/sites/launchai/tsconfig.json b/sites/launchai/tsconfig.json index 95fd293..c20f005 100644 --- a/sites/launchai/tsconfig.json +++ b/sites/launchai/tsconfig.json @@ -15,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -55,8 +55,7 @@ "next-env.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts" + ".next/types/**/*.ts" ], "exclude": [ "node_modules" diff --git a/sites/the-ai-ledger/tsconfig.json b/sites/the-ai-ledger/tsconfig.json index 95fd293..c20f005 100644 --- a/sites/the-ai-ledger/tsconfig.json +++ b/sites/the-ai-ledger/tsconfig.json @@ -15,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -55,8 +55,7 @@ "next-env.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts" + ".next/types/**/*.ts" ], "exclude": [ "node_modules"