完成登录界面样式

This commit is contained in:
2025-04-25 23:23:46 +08:00
parent 1ef8b3ab0f
commit 13220bb169
10 changed files with 550 additions and 1 deletions

View File

@@ -0,0 +1,270 @@
'use client';
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { KeyRound, Phone, Mail } from "lucide-react"
import { useState } from "react"
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "./ui/input-otp";
function LoginHeader() {
return (
<>
<div className="flex flex-col items-center text-center">
<h1 className="text-2xl font-bold"></h1>
<p className="text-muted-foreground text-balance">
</p>
</div>
</>
)
}
function RegisterHeader() {
return (
<>
<div className="flex flex-col items-center text-center">
<h1 className="text-2xl font-bold"></h1>
<p className="text-muted-foreground text-balance">
</p>
</div>
</>
)
}
function PasswordMode() {
return (
<>
<LoginHeader />
<div className="grid gap-3">
<Label htmlFor="email">//</Label>
<Input
id="account"
type="text"
placeholder="电子邮箱/手机号/账号"
required
/>
</div>
<div className="grid gap-3">
<div className="flex items-center h-4">
<Label htmlFor="password"></Label>
<a
href="#"
className="ml-auto text-sm underline-offset-2 hover:underline"
>
</a>
</div>
<Input id="password" type="password" required />
</div>
<Button type="submit" className="w-full">
</Button>
</>
)
}
function PhoneMode() {
return (
<>
<LoginHeader />
<div className="grid gap-3">
<Label htmlFor="phone"></Label>
<Input id="phone" type="text" placeholder="手机号" required />
</div>
<div className="grid gap-3">
<div className="flex items-center h-4">
<Label htmlFor="code"></Label>
</div>
<div className="flex gap-5">
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
required
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<Button variant="secondary"></Button>
</div>
</div>
<Button type="submit" className="w-full">
</Button>
</>
)
}
function EmailMode() {
return (
<>
<LoginHeader />
<div className="grid gap-3">
<Label htmlFor="email"></Label>
<Input id="email" type="text" placeholder="电子邮箱" required />
</div>
<div className="grid gap-3">
<div className="flex items-center h-4">
<Label htmlFor="code"></Label>
</div>
<div className="flex gap-5">
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
required
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<Button variant="secondary"></Button>
</div>
</div>
<Button type="submit" className="w-full">
</Button>
</>
)
}
function RegisterMode() {
return (
<>
<RegisterHeader />
<div className="grid gap-3">
<Label htmlFor="email"></Label>
<Input id="account" type="text" placeholder="账户名" required />
</div>
<div className="grid gap-3">
<Label htmlFor="email"></Label>
<Input id="email" type="text" placeholder="电子邮箱" required />
</div>
<div className="grid gap-3">
<div className="flex items-center h-4">
<Label htmlFor="code"></Label>
</div>
<div className="flex gap-5">
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
required
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<Button variant="secondary"></Button>
</div>
</div>
<Button type="submit" className="w-full">
</Button>
</>
)
}
export function LoginForm({
className,
...props
}: React.ComponentProps<"div">) {
const [loginMode, setLoginMode] = useState<'password' | 'phone' | 'email' | 'register'>('password');
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card className="overflow-hidden p-0">
<CardContent className="grid p-0 md:grid-cols-2">
<form className="p-6 md:p-8">
<div className="flex flex-col gap-6">
{loginMode === 'password' ? <PasswordMode /> : null}
{loginMode === 'phone' ? <PhoneMode /> : null}
{loginMode === 'email' ? <EmailMode /> : null}
{loginMode === 'register' ? <RegisterMode /> : null}
{
loginMode !== 'register' && (
<>
<div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
<span className="bg-card text-muted-foreground relative z-10 px-2">
使
</span>
</div>
<div className="grid grid-cols-3 gap-4">
<Button variant="outline" type="button" className="w-full" onClick={() => setLoginMode('password')}>
<KeyRound />
</Button>
<Button variant="outline" type="button" className="w-full" onClick={() => setLoginMode('phone')}>
<Phone />
</Button>
<Button variant="outline" type="button" className="w-full" onClick={() => setLoginMode('email')}>
<Mail />
</Button>
</div>
</>
)
}
{
loginMode === 'register' ? (
<>
<div className="text-center text-sm">
{" "}
<a className="underline underline-offset-4 cursor-pointer" onClick={() => setLoginMode('password')}>
</a>
</div>
</>
) : (
<>
<div className="text-center text-sm">
{" "}
<a className="underline underline-offset-4 cursor-pointer" onClick={() => setLoginMode('register')}>
</a>
</div>
</>
)
}
</div>
</form>
<div className="bg-muted relative hidden md:block">
<img
src="/placeholder.svg"
alt="Image"
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
/>
</div>
</CardContent>
</Card>
<div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
{" "}
<a href="#" className="underline underline-offset-4">
</a>{" "}
{" "}
<a href="#" className="underline underline-offset-4">
</a>
</div>
</div >
)
}