feat(navigation): add favorite entry and Menu tab

This commit is contained in:
Codex
2026-07-22 19:31:06 +08:00
parent 318e4991be
commit 469512df18
23 changed files with 794 additions and 118 deletions
+16 -52
View File
@@ -2,37 +2,31 @@
/**
* ChatHeader 顶部栏
*
* Profile 优先展示用户头像;头像缺失时回退到 lucide-react <UserRound />
* 顶部保持返回、首充优惠、收藏入口三段结构
*/
import type { ReactNode } from "react";
import { Lock, UserRound } from "lucide-react";
import { Lock } from "lucide-react";
import { BackButton, UserMessageAvatar } from "@/app/_components";
import { useActiveCharacterRoutes } from "@/providers/character-provider";
import { buildGlobalPageUrl } from "@/router/global-route-context";
import { ROUTES } from "@/router/routes";
import { BackButton } from "@/app/_components";
import { FavoriteEntryButton } from "@/app/_components/core";
import {
useActiveCharacter,
useActiveCharacterRoutes,
} from "@/providers/character-provider";
import { useAppNavigator } from "@/router/use-app-navigator";
import { useUserSelector } from "@/stores/user/user-context";
import { BrowserHintOverlay } from "./browser-hint-overlay";
export interface ChatHeaderProps {
isGuest: boolean;
offerBanner?: ReactNode;
showBrowserHint?: boolean;
}
export function ChatHeader({
isGuest,
offerBanner,
showBrowserHint = false,
}: ChatHeaderProps) {
const navigator = useAppNavigator();
const character = useActiveCharacter();
const characterRoutes = useActiveCharacterRoutes();
const avatarUrl = useUserSelector((state) => state.context.avatarUrl);
const handleOpenProfile = () => {
navigator.push(buildGlobalPageUrl(ROUTES.profile, characterRoutes.chat));
};
return (
<header className="flex shrink-0 flex-col items-stretch bg-transparent p-0">
@@ -52,17 +46,9 @@ export function ChatHeader({
/>
<span>Sign up to unlock more features</span>
</button>
) : (
offerBanner
)}
) : null}
<div
className={
isGuest
? "flex items-center px-(--chat-inline-padding,16px) py-(--spacing-sm,8px)"
: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-(--spacing-sm,8px) px-(--chat-inline-padding,16px) py-(--spacing-sm,8px)"
}
>
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-(--spacing-sm,8px) px-(--chat-inline-padding,16px) py-(--spacing-sm,8px)">
<BackButton
href={characterRoutes.splash}
variant="dark"
@@ -70,34 +56,12 @@ export function ChatHeader({
analyticsKey="chat.back_to_home"
/>
{!isGuest ? (
<>
<div className="flex min-w-0 justify-center">
{showBrowserHint ? <BrowserHintOverlay /> : null}
</div>
<div className="flex min-w-0 justify-center">{offerBanner}</div>
{avatarUrl ? (
<UserMessageAvatar
avatarUrl={avatarUrl}
size="var(--responsive-icon-button-size, 42px)"
actionLabel="Profile"
analyticsKey="chat.open_profile"
onClick={handleOpenProfile}
/>
) : (
<button
type="button"
data-analytics-key="chat.open_profile"
data-analytics-label="Open profile"
className="flex size-(--responsive-icon-button-size,42px) cursor-pointer items-center justify-center rounded-full border border-[rgba(255,255,255,0.12)] bg-[rgba(13,11,20,0.7)] text-(--color-text-primary,#fff) shadow-[0_10px_24px_rgba(0,0,0,0.2)] backdrop-blur-md transition-[background,transform] duration-150 hover:bg-[rgba(28,24,39,0.82)] active:scale-96 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
onClick={handleOpenProfile}
aria-label="Profile"
>
<UserRound size={24} aria-hidden="true" />
</button>
)}
</>
) : null}
<FavoriteEntryButton
characterSlug={character.slug}
tone="dark"
/>
</div>
</header>
);