密码
-
{
@@ -171,7 +173,8 @@ export default function AuthModal({ open, onClose, onAuth, defaultTab = "login"
setError("");
}}
placeholder={tab === "register" ? "至少 6 个字符" : "请输入密码"}
- className="h-11 w-full rounded-xl border-none bg-elevated px-4 pr-10 text-sm text-heading outline-none ring-1 ring-border transition-colors placeholder:text-dim focus:ring-2 focus:ring-accent/50"
+ size="xl"
+ className="pr-10"
/>
)}
diff --git a/src/components/Card.tsx b/src/components/Card.tsx
new file mode 100644
index 0000000..67695f4
--- /dev/null
+++ b/src/components/Card.tsx
@@ -0,0 +1,37 @@
+import { motion } from "framer-motion";
+import type { ReactNode } from "react";
+
+interface CardProps {
+ children: ReactNode;
+ className?: string;
+ animated?: boolean;
+ delay?: number;
+}
+
+const fadeUp = {
+ initial: { y: 10, opacity: 0 },
+ animate: { y: 0, opacity: 1 },
+} as const;
+
+export default function Card({
+ children,
+ className = "",
+ animated = false,
+ delay,
+}: CardProps) {
+ const cls = `rounded-2xl bg-surface p-4 ring-1 ring-border ${className}`;
+
+ if (animated) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return
{children}
;
+}
diff --git a/src/components/Input.tsx b/src/components/Input.tsx
new file mode 100644
index 0000000..585cbb5
--- /dev/null
+++ b/src/components/Input.tsx
@@ -0,0 +1,31 @@
+import { type ComponentPropsWithoutRef, forwardRef } from "react";
+
+const sizeStyles = {
+ sm: "h-8 rounded-lg px-2",
+ md: "h-9 rounded-lg px-3",
+ lg: "h-10 rounded-xl px-3",
+ xl: "h-11 rounded-xl px-4",
+} as const;
+
+const variantStyles = {
+ default: "bg-elevated text-heading focus:ring-accent/50",
+ purple: "bg-surface text-foreground focus:ring-purple-600",
+} as const;
+
+interface InputProps extends Omit
, "size"> {
+ size?: keyof typeof sizeStyles;
+ variant?: keyof typeof variantStyles;
+}
+
+const Input = forwardRef(
+ ({ size = "md", variant = "default", className = "", ...rest }, ref) => (
+
+ ),
+);
+
+Input.displayName = "Input";
+export default Input;
diff --git a/src/components/RoomManageModal.tsx b/src/components/RoomManageModal.tsx
index 81efe0a..4ac5de6 100644
--- a/src/components/RoomManageModal.tsx
+++ b/src/components/RoomManageModal.tsx
@@ -11,7 +11,7 @@ import {
Loader2,
} from "lucide-react";
import { UserProfile } from "@/types";
-import { getAvatar, getAvatarBg } from "@/lib/avatars";
+import UserAvatar from "@/components/UserAvatar";
import Modal from "@/components/Modal";
import { useToast } from "@/hooks/useToast";
@@ -129,10 +129,7 @@ export default function RoomManageModal({
{users.map((uid) => {
- const profile = userProfiles[uid];
- const emoji = profile?.avatar ?? getAvatar(uid).emoji;
- const bg = profile ? getAvatarBg(profile.avatar) : getAvatar(uid).bg;
- const displayName = profile?.username ?? uid.slice(0, 8);
+ const displayName = userProfiles[uid]?.username ?? uid.slice(0, 8);
const isCreator = uid === userId;
const swiped = swipeCounts[uid] ?? 0;
const finished = swiped >= totalCards;
@@ -142,11 +139,7 @@ export default function RoomManageModal({
key={uid}
className="flex items-center gap-2.5 rounded-xl bg-elevated px-3 py-2.5"
>
-
- {emoji}
-
+
{isCreator && (
diff --git a/src/components/SwipeDeck.tsx b/src/components/SwipeDeck.tsx
index 54b6779..28d1a83 100644
--- a/src/components/SwipeDeck.tsx
+++ b/src/components/SwipeDeck.tsx
@@ -8,7 +8,7 @@ import MatchResult from "./MatchResult";
import SwipeGuide from "./SwipeGuide";
import { Restaurant, SwipeDirection, MatchType, RunnerUp, UserProfile, SceneType } from "@/types";
import { Heart, Undo2, Check } from "lucide-react";
-import { getAvatar, getAvatarBg } from "@/lib/avatars";
+import UserAvatar from "@/components/UserAvatar";
function UserProgressBar({
userId,
@@ -27,17 +27,11 @@ function UserProgressBar({
}) {
const others = Object.entries(swipeCounts).filter(([id]) => id !== userId);
- const myProfile = userProfiles[userId];
- const myAvatar = myProfile?.avatar ?? getAvatar(userId).emoji;
- const myAvatarBg = myProfile ? getAvatarBg(myProfile.avatar) : "bg-emerald-500/20";
-
return (
-
- {myAvatar}
-
+
你
{localIndex}/{total}
@@ -45,18 +39,13 @@ function UserProgressBar({
{others.map(([id, count]) => {
const finished = count >= total;
- const profile = userProfiles[id];
- const emoji = profile?.avatar ?? getAvatar(id).emoji;
- const bg = profile ? getAvatarBg(profile.avatar) : getAvatar(id).bg;
- const label = profile?.username ?? "";
+ const label = userProfiles[id]?.username ?? "";
return (
-
- {emoji}
-
+
{label && {label}}
{count}/{total}
@@ -95,17 +84,11 @@ function WaitingProgress({
const others = entries.filter(([id]) => id !== userId);
const finishedCount = others.filter(([, c]) => c >= total).length;
- const myProfile = userProfiles[userId];
- const myEmoji = myProfile?.avatar ?? getAvatar(userId).emoji;
- const myBg = myProfile ? getAvatarBg(myProfile.avatar) : "bg-emerald-100";
-
return (
-
- {myEmoji}
-
+
你 {total}/{total}
@@ -114,17 +97,17 @@ function WaitingProgress({
{others.map(([id, count]) => {
const finished = count >= total;
const pct = Math.min((count / total) * 100, 100);
- const profile = userProfiles[id];
- const emoji = profile?.avatar ?? getAvatar(id).emoji;
- const bg = profile ? getAvatarBg(profile.avatar) : getAvatar(id).bg;
- const label = profile?.username ?? "";
+ const label = userProfiles[id]?.username ?? "";
return (
-
- {emoji}
-
+
{label && {label}}
{count}/{total}
diff --git a/src/components/UserAvatar.tsx b/src/components/UserAvatar.tsx
new file mode 100644
index 0000000..75be9dc
--- /dev/null
+++ b/src/components/UserAvatar.tsx
@@ -0,0 +1,34 @@
+import { resolveAvatar } from "@/lib/avatars";
+
+const sizeStyles = {
+ xs: "h-4 w-4 text-[10px]",
+ sm: "h-5 w-5 text-sm",
+ md: "h-8 w-8 text-base",
+ lg: "h-11 w-11 text-xl",
+ xl: "h-14 w-14 text-2xl",
+} as const;
+
+interface UserAvatarProps {
+ userId: string;
+ profile?: { avatar: string } | null;
+ size?: keyof typeof sizeStyles;
+ bg?: string;
+ className?: string;
+}
+
+export default function UserAvatar({
+ userId,
+ profile,
+ size = "md",
+ bg,
+ className = "",
+}: UserAvatarProps) {
+ const avatar = resolveAvatar(userId, profile);
+ return (
+
+ {avatar.emoji}
+
+ );
+}
diff --git a/src/lib/avatars.ts b/src/lib/avatars.ts
index c139e5e..88f3cef 100644
--- a/src/lib/avatars.ts
+++ b/src/lib/avatars.ts
@@ -25,3 +25,14 @@ export function getAvatarBg(emoji: string): string {
const found = AVATARS.find((a) => a.emoji === emoji);
return found?.bg ?? "bg-zinc-100";
}
+
+export function resolveAvatar(
+ userId: string,
+ profile?: { avatar: string } | null,
+): { emoji: string; bg: string } {
+ if (profile) {
+ return { emoji: profile.avatar, bg: getAvatarBg(profile.avatar) };
+ }
+ const fallback = getAvatar(userId);
+ return { emoji: fallback.emoji, bg: fallback.bg };
+}