sync me
This commit is contained in:
parent
1eca50c6ce
commit
1b35c937c1
128
app/me/account/page.tsx
Normal file
128
app/me/account/page.tsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
import { Eye, EyeOff, Save } from "lucide-react"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight">账户设置</h2>
|
||||||
|
<p className="text-muted-foreground">管理您的账户基本设置和安全信息</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>账户信息</CardTitle>
|
||||||
|
<CardDescription>管理您的账户基本设置</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>用户名</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">zhangsan2024</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
修改
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>邮箱地址</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">zhangsan@example.com</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
修改
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="current-password">当前密码</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input id="current-password" type={showPassword ? "text" : "password"} placeholder="输入当前密码" />
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="new-password">新密码</Label>
|
||||||
|
<Input id="new-password" type="password" placeholder="输入新密码" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="confirm-password">确认新密码</Label>
|
||||||
|
<Input id="confirm-password" type="password" placeholder="再次输入新密码" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="gap-2">
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
更新密码
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>语言和地区</CardTitle>
|
||||||
|
<CardDescription>设置您的语言偏好和时区</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>语言</Label>
|
||||||
|
<Select defaultValue="zh-cn">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="zh-cn">简体中文</SelectItem>
|
||||||
|
<SelectItem value="zh-tw">繁體中文</SelectItem>
|
||||||
|
<SelectItem value="en">English</SelectItem>
|
||||||
|
<SelectItem value="ja">日本語</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>时区</Label>
|
||||||
|
<Select defaultValue="asia-shanghai">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="asia-shanghai">Asia/Shanghai (UTC+8)</SelectItem>
|
||||||
|
<SelectItem value="asia-tokyo">Asia/Tokyo (UTC+9)</SelectItem>
|
||||||
|
<SelectItem value="america-new-york">America/New_York (UTC-5)</SelectItem>
|
||||||
|
<SelectItem value="europe-london">Europe/London (UTC+0)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button>保存设置</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
101
app/me/notifications/page.tsx
Normal file
101
app/me/notifications/page.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
import { Mail, Bell, Phone, Save } from "lucide-react"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [notifications, setNotifications] = useState({
|
||||||
|
email: true,
|
||||||
|
push: false,
|
||||||
|
sms: true,
|
||||||
|
marketing: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight">通知设置</h2>
|
||||||
|
<p className="text-muted-foreground">选择您希望接收的通知类型和频率</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>通知偏好</CardTitle>
|
||||||
|
<CardDescription>选择您希望接收的通知类型</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="flex items-center gap-2">
|
||||||
|
<Mail className="h-4 w-4" />
|
||||||
|
邮件通知
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">接收重要更新和活动通知</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.email}
|
||||||
|
onCheckedChange={(checked) => setNotifications({ ...notifications, email: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="flex items-center gap-2">
|
||||||
|
<Bell className="h-4 w-4" />
|
||||||
|
推送通知
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">在浏览器中接收实时通知</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.push}
|
||||||
|
onCheckedChange={(checked) => setNotifications({ ...notifications, push: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="flex items-center gap-2">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
短信通知
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">接收安全相关的短信提醒</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.sms}
|
||||||
|
onCheckedChange={(checked) => setNotifications({ ...notifications, sms: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>营销邮件</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">接收产品更新和促销信息</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.marketing}
|
||||||
|
onCheckedChange={(checked) => setNotifications({ ...notifications, marketing: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="gap-2">
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
保存通知设置
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
149
app/me/profile/page.tsx
Normal file
149
app/me/profile/page.tsx
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Calendar } from "@/components/ui/calendar"
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||||
|
import { Camera, CalendarIcon, Save } from "lucide-react"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { format } from "date-fns"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [date, setDate] = useState<Date>()
|
||||||
|
const [profileData, setProfileData] = useState({
|
||||||
|
name: "张三",
|
||||||
|
email: "zhangsan@example.com",
|
||||||
|
phone: "+86 138 0013 8000",
|
||||||
|
bio: "这是我的个人简介,我是一名前端开发工程师,热爱技术和创新。",
|
||||||
|
location: "北京市朝阳区",
|
||||||
|
website: "https://zhangsan.dev",
|
||||||
|
company: "科技有限公司",
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 p-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight">个人资料</h2>
|
||||||
|
<p className="text-muted-foreground">管理您的个人信息和公开资料</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>基本信息</CardTitle>
|
||||||
|
<CardDescription>更新您的个人资料信息</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<Avatar className="h-20 w-20">
|
||||||
|
<AvatarImage src="/placeholder.svg?height=80&width=80" />
|
||||||
|
<AvatarFallback>张三</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Button variant="outline" size="sm" className="gap-2 bg-transparent">
|
||||||
|
<Camera className="h-4 w-4" />
|
||||||
|
更换头像
|
||||||
|
</Button>
|
||||||
|
<p className="text-sm text-muted-foreground">推荐尺寸:400x400px,支持 JPG、PNG 格式</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">姓名</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
value={profileData.name}
|
||||||
|
onChange={(e) => setProfileData({ ...profileData, name: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">邮箱</Label>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={profileData.email}
|
||||||
|
onChange={(e) => setProfileData({ ...profileData, email: e.target.value })}
|
||||||
|
/>
|
||||||
|
<Badge variant="secondary">已验证</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="phone">手机号</Label>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
value={profileData.phone}
|
||||||
|
onChange={(e) => setProfileData({ ...profileData, phone: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="company">公司</Label>
|
||||||
|
<Input
|
||||||
|
id="company"
|
||||||
|
value={profileData.company}
|
||||||
|
onChange={(e) => setProfileData({ ...profileData, company: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="location">所在地</Label>
|
||||||
|
<Input
|
||||||
|
id="location"
|
||||||
|
value={profileData.location}
|
||||||
|
onChange={(e) => setProfileData({ ...profileData, location: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="website">个人网站</Label>
|
||||||
|
<Input
|
||||||
|
id="website"
|
||||||
|
value={profileData.website}
|
||||||
|
onChange={(e) => setProfileData({ ...profileData, website: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="bio">个人简介</Label>
|
||||||
|
<Textarea
|
||||||
|
id="bio"
|
||||||
|
placeholder="介绍一下您自己..."
|
||||||
|
className="min-h-[100px]"
|
||||||
|
value={profileData.bio}
|
||||||
|
onChange={(e) => setProfileData({ ...profileData, bio: e.target.value })}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">{profileData.bio.length}/500 字符</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>生日</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className={cn("w-full justify-start text-left font-normal", !date && "text-muted-foreground")}
|
||||||
|
>
|
||||||
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||||
|
{date ? format(date, "yyyy年MM月dd日") : "选择日期"}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Calendar mode="single" selected={date} onSelect={setDate} initialFocus />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="gap-2">
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
保存更改
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
125
app/me/security/page.tsx
Normal file
125
app/me/security/page.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
import { Save } from "lucide-react"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [security, setSecurity] = useState({
|
||||||
|
twoFactor: true,
|
||||||
|
loginAlerts: true,
|
||||||
|
dataExport: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight">安全设置</h2>
|
||||||
|
<p className="text-muted-foreground">保护您的账户安全和隐私</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>安全设置</CardTitle>
|
||||||
|
<CardDescription>保护您的账户安全</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>双重认证</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">为您的账户添加额外的安全保护</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant={security.twoFactor ? "default" : "secondary"}>
|
||||||
|
{security.twoFactor ? "已启用" : "未启用"}
|
||||||
|
</Badge>
|
||||||
|
<Switch
|
||||||
|
checked={security.twoFactor}
|
||||||
|
onCheckedChange={(checked) => setSecurity({ ...security, twoFactor: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>登录提醒</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">当有新设备登录时发送通知</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={security.loginAlerts}
|
||||||
|
onCheckedChange={(checked) => setSecurity({ ...security, loginAlerts: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>数据导出</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">允许导出个人数据</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={security.dataExport}
|
||||||
|
onCheckedChange={(checked) => setSecurity({ ...security, dataExport: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 pt-4 border-t">
|
||||||
|
<h4 className="font-medium">活跃会话</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between p-3 border rounded-lg">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-medium">当前设备</p>
|
||||||
|
<p className="text-sm text-muted-foreground">Chrome on Windows • 北京市 • 刚刚活跃</p>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline">当前</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-3 border rounded-lg">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-medium">iPhone</p>
|
||||||
|
<p className="text-sm text-muted-foreground">Safari on iOS • 上海市 • 2小时前</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
注销
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="gap-2">
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
保存安全设置
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="border-destructive/50">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-destructive">危险操作</CardTitle>
|
||||||
|
<CardDescription>这些操作不可逆转,请谨慎操作</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>删除账户</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">永久删除您的账户和所有相关数据</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="destructive" size="sm">
|
||||||
|
删除账户
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
48
components/ui/popover.tsx
Normal file
48
components/ui/popover.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Popover({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||||
|
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||||
|
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverContent({
|
||||||
|
className,
|
||||||
|
align = "center",
|
||||||
|
sideOffset = 4,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
data-slot="popover-content"
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverAnchor({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||||
|
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||||
18
components/ui/textarea.tsx
Normal file
18
components/ui/textarea.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
data-slot="textarea"
|
||||||
|
className={cn(
|
||||||
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Textarea }
|
||||||
64
package-lock.json
generated
64
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.13",
|
"@radix-ui/react-navigation-menu": "^1.2.13",
|
||||||
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slider": "^1.3.5",
|
"@radix-ui/react-slider": "^1.3.5",
|
||||||
@ -1434,6 +1435,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover": {
|
||||||
|
"version": "1.1.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz",
|
||||||
|
"integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.2",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.10",
|
||||||
|
"@radix-ui/react-focus-guards": "1.1.2",
|
||||||
|
"@radix-ui/react-focus-scope": "1.1.7",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-popper": "1.2.7",
|
||||||
|
"@radix-ui/react-portal": "1.1.9",
|
||||||
|
"@radix-ui/react-presence": "1.1.4",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"aria-hidden": "^1.2.4",
|
||||||
|
"react-remove-scroll": "^2.6.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
|
||||||
@ -1661,9 +1698,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-slot": {
|
"node_modules/@radix-ui/react-slot": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "http://mirrors.cloud.tencent.com/npm/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.2"
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
},
|
},
|
||||||
@ -6236,6 +6272,28 @@
|
|||||||
"@radix-ui/react-visually-hidden": "1.2.3"
|
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@radix-ui/react-popover": {
|
||||||
|
"version": "1.1.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz",
|
||||||
|
"integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==",
|
||||||
|
"requires": {
|
||||||
|
"@radix-ui/primitive": "1.1.2",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.10",
|
||||||
|
"@radix-ui/react-focus-guards": "1.1.2",
|
||||||
|
"@radix-ui/react-focus-scope": "1.1.7",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-popper": "1.2.7",
|
||||||
|
"@radix-ui/react-portal": "1.1.9",
|
||||||
|
"@radix-ui/react-presence": "1.1.4",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"aria-hidden": "^1.2.4",
|
||||||
|
"react-remove-scroll": "^2.6.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@radix-ui/react-popper": {
|
"@radix-ui/react-popper": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
|
||||||
@ -6351,7 +6409,7 @@
|
|||||||
},
|
},
|
||||||
"@radix-ui/react-slot": {
|
"@radix-ui/react-slot": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "http://mirrors.cloud.tencent.com/npm/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.2"
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.13",
|
"@radix-ui/react-navigation-menu": "^1.2.13",
|
||||||
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slider": "^1.3.5",
|
"@radix-ui/react-slider": "^1.3.5",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user