refactor(ui): extract auth and avatar components

This commit is contained in:
2026-06-18 17:14:02 +08:00
parent 96d0a54105
commit 7354afe93d
12 changed files with 91 additions and 69 deletions
@@ -0,0 +1,29 @@
.backButton {
position: absolute;
top: var(--spacing-xl);
left: var(--spacing-xl);
z-index: 2;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--auth-back-button-size);
height: var(--auth-back-button-size);
padding: 0 2px 0 0;
color: var(--color-auth-text-primary);
cursor: pointer;
background: var(--color-auth-surface);
border: none;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition:
background 0.15s ease,
transform 0.05s ease;
}
.backButton:hover {
background: rgba(0, 0, 0, 0.04);
}
.backButton:active {
transform: scale(0.96);
}
@@ -0,0 +1,22 @@
"use client";
import { ChevronLeft } from "lucide-react";
import styles from "./auth-back-button.module.css";
export interface AuthBackButtonProps {
onClick: () => void;
}
export function AuthBackButton({ onClick }: AuthBackButtonProps) {
return (
<button
type="button"
className={styles.backButton}
onClick={onClick}
aria-label="Back"
>
<ChevronLeft size={20} strokeWidth={2.5} aria-hidden="true" />
</button>
);
}
@@ -4,7 +4,7 @@
position: absolute;
inset: 0;
z-index: 0;
background: var(--color-page-background);
background: #fbf1f2;
overflow: hidden;
}
@@ -2,7 +2,6 @@
*
* 视觉规格(与 Dart 对齐):
* - panelShell 包裹整个面板(position: relative
* - 悬浮返回按钮:40×40 白色圆形 + 淡黑阴影,固定 top/left 20px
*/
.panelShell {
position: relative;
@@ -12,31 +11,3 @@
width: 100%;
min-height: 0;
}
.backButton {
position: absolute;
top: var(--spacing-xl);
left: var(--spacing-xl);
z-index: 2;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--auth-back-button-size);
height: var(--auth-back-button-size);
border: none;
border-radius: 50%;
background: var(--color-auth-surface);
color: var(--color-auth-text-primary);
padding: 0 2px 0 0;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: background 0.15s ease, transform 0.05s ease;
}
.backButton:hover {
background: rgba(0, 0, 0, 0.04);
}
.backButton:active {
transform: scale(0.96);
}
+2 -9
View File
@@ -3,10 +3,10 @@
* 认证面板:顶层 switchFacebook / Email+ 悬浮返回按钮
*/
import { useRouter } from "next/navigation";
import { ChevronLeft } from "lucide-react";
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import { AuthBackButton } from "./auth-back-button";
import { AuthEmailPanel } from "./auth-email-panel";
import { AuthFacebookPanel } from "./auth-facebook-panel";
import styles from "./auth-panel.module.css";
@@ -29,14 +29,7 @@ export function AuthPanel() {
return (
<div className={styles.panelShell}>
<button
type="button"
className={styles.backButton}
onClick={handleBack}
aria-label="Back"
>
<ChevronLeft size={20} strokeWidth={2.5} aria-hidden="true" />
</button>
<AuthBackButton onClick={handleBack} />
{state.authPanelMode === "facebook" ? (
<AuthFacebookPanel />
@@ -10,6 +10,7 @@
flex-direction: column;
width: 100%;
min-height: 100dvh;
background: #fbf1f2;
overflow: hidden;
}
+1
View File
@@ -3,6 +3,7 @@
*/
export * from "./auth-background";
export * from "./auth-back-button";
export * from "./auth-divider";
export * from "./auth-email-panel";
export * from "./auth-error-message";
+1
View File
@@ -22,3 +22,4 @@ export * from "./message-content";
export * from "./pwa-install-dialog";
export * from "./pwa-install-overlay";
export * from "./text-bubble";
export * from "./user-message-avatar";
+2 -28
View File
@@ -1,16 +1,7 @@
"use client";
/**
* MessageAvatar 消息头像
*
* 原始 Dart: lib/ui/chat/widgets/message_avatar.dart85 行)
*
* 显示规则:
* - AI 消息:Elio 头像(`/images/chat/pic-chat-elio.png`
* - 用户消息(有 avatarUrl):用户头像
* - 用户消息(无 avatarUrl):游客头像(`/images/chat/pic-chat-guest.png`
*/
import Image from "next/image";
import { UserMessageAvatar } from "./user-message-avatar";
import styles from "./message-avatar.module.css";
export interface MessageAvatarProps {
@@ -33,22 +24,5 @@ export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) {
);
}
if (userAvatarUrl && userAvatarUrl.length > 0) {
return (
<div className={styles.avatar} aria-label="User avatar">
<Image src={userAvatarUrl} alt="" width={43} height={43} />
</div>
);
}
return (
<div className={styles.avatar} aria-label="Guest avatar">
<Image
src="/images/chat/pic-chat-guest.png"
alt="Guest"
width={43}
height={43}
/>
</div>
);
return <UserMessageAvatar avatarUrl={userAvatarUrl} />;
}
@@ -0,0 +1,30 @@
"use client";
import Image from "next/image";
import styles from "./message-avatar.module.css";
export interface UserMessageAvatarProps {
avatarUrl?: string | null;
}
export function UserMessageAvatar({ avatarUrl }: UserMessageAvatarProps) {
if (avatarUrl && avatarUrl.length > 0) {
return (
<div className={styles.avatar} aria-label="User avatar">
<Image src={avatarUrl} alt="" width={43} height={43} />
</div>
);
}
return (
<div className={styles.avatar} aria-label="Guest avatar">
<Image
src="/images/chat/pic-chat-guest.png"
alt="Guest"
width={43}
height={43}
/>
</div>
);
}
@@ -3,7 +3,7 @@
flex-direction: column;
min-height: 100dvh;
background: var(--color-page-background);
padding: 0 var(--spacing-lg) var(--spacing-xxl);
padding: 30px 20px 10px;
}
.header {
+1 -1
View File
@@ -27,7 +27,7 @@ export const initialState: PaymentState = {
selectedPlanId: "",
payChannel: "stripe",
autoRenew: true,
agreed: false,
agreed: true,
currentOrderId: null,
payParams: null,
orderStatus: null,