ui UI 组件
共享 UI 组件库,基于 shadcn/ui 和 Radix UI 构建
本页面介绍组件 API 使用方法。设计原则和规范请参考 开发指南。
@mono/ui
共享 UI 组件库,基于 shadcn/ui 和 Radix UI 构建,提供完整的设计系统和业务组件。
特性
- 80+ 个可复用的 UI 组件
- 基于 shadcn/ui 的设计系统
- Radix UI 原语,确保可访问性
- 内置亮色/暗色主题切换
- 完整的 TypeScript 类型定义
- 与 react-hook-form 深度集成
- 移动端优先的响应式设计
安装
pnpm add @mono/ui快速开始
基础组件
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button } from "@mono/ui";
export function Example() {
return (
<Card>
<CardHeader>
<CardTitle>卡片标题</CardTitle>
<CardDescription>这是卡片的描述文本</CardDescription>
</CardHeader>
<CardContent>
<p>卡片内容区域</p>
</CardContent>
<CardFooter>
<Button>操作按钮</Button>
</CardFooter>
</Card>
);
}import { Input, Label } from "@mono/ui";
export function Example() {
return (
<div className="space-y-2">
<Label htmlFor="email">邮箱</Label>
<Input
id="email"
type="email"
placeholder="your@email.com"
/>
</div>
);
}表单集成
使用 react-hook-form 和 zod 进行表单验证:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
Form,
FormField,
FormItem,
FormLabel,
FormControl,
FormMessage,
Input,
Button,
} from "@mono/ui";
const formSchema = z.object({
username: z.string().min(2, "用户名至少 2 个字符"),
email: z.string().email("无效的邮箱地址"),
});
export function LoginForm() {
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
},
});
const onSubmit = (data: z.infer<typeof formSchema>) => {
console.log(data);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>用户名</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>邮箱</FormLabel>
<FormControl>
<Input {...field} type="email" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">提交</Button>
</form>
</Form>
);
}组件库
基础组件
按钮和输入
- Button - 按钮组件,支持多种变体(default, outline, ghost, destructive)
- Input - 文本输入框
- Textarea - 多行文本输入
- PasswordInput - 密码输入框(带显示/隐藏切换)
- PhoneInput - 手机号输入框
- InputOTP - OTP 验证码输入
选择器
- Select - 下拉选择器
- Checkbox - 复选框
- RadioGroup - 单选按钮组
- Switch - 开关切换
- Slider - 滑块
- CountryCodeSelect - 国家代码选择器
布局
- Card - 卡片容器
- Separator - 分隔线
- ScrollArea - 滚动区域
- AspectRatio - 宽高比容器
- Resizable - 可调整大小的面板
反馈
- Alert - 警告提示
- Badge - 徽章标签
- Progress - 进度条
- Skeleton - 骨架屏
- Spinner - 加载动画
- Sonner - Toast 通知
导航组件
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@mono/ui";
export function Example() {
return (
<Tabs defaultValue="tab1">
<TabsList>
<TabsTrigger value="tab1">标签 1</TabsTrigger>
<TabsTrigger value="tab2">标签 2</TabsTrigger>
</TabsList>
<TabsContent value="tab1">标签 1 的内容</TabsContent>
<TabsContent value="tab2">标签 2 的内容</TabsContent>
</Tabs>
);
}弹窗组件
Dialog(对话框)
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
Button,
} from "@mono/ui";
export function Example() {
return (
<Dialog>
<DialogTrigger asChild>
<Button>打开对话框</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>确认操作</DialogTitle>
<DialogDescription>
此操作无法撤销,请确认是否继续。
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">取消</Button>
<Button>确认</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Popover(弹出框)
import {
Popover,
PopoverTrigger,
PopoverContent,
Button,
} from "@mono/ui";
export function Example() {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">打开弹出框</Button>
</PopoverTrigger>
<PopoverContent>
<div className="space-y-2">
<h4 className="font-medium">弹出框标题</h4>
<p className="text-sm text-muted-foreground">
这是弹出框的内容
</p>
</div>
</PopoverContent>
</Popover>
);
}数据展示
Table(表格)
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@mono/ui";
export function Example() {
const data = [
{ id: 1, name: "张三", email: "zhang@example.com" },
{ id: 2, name: "李四", email: "li@example.com" },
];
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>姓名</TableHead>
<TableHead>邮箱</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.name}</TableCell>
<TableCell>{row.email}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}Avatar(头像)
import { Avatar, AvatarImage, AvatarFallback } from "@mono/ui";
export function Example() {
return (
<Avatar>
<AvatarImage src="/avatar.jpg" alt="用户头像" />
<AvatarFallback>张三</AvatarFallback>
</Avatar>
);
}业务组件
UserAvatar(用户头像)
import { UserAvatar } from "@mono/ui";
export function Example() {
const user = {
name: "张三",
image: "/avatar.jpg",
};
return (
<div className="flex gap-4">
<UserAvatar user={user} size="sm" />
<UserAvatar user={user} size="md" />
<UserAvatar user={user} size="lg" />
</div>
);
}CommentSystem(评论系统)
import { CommentSystem } from "@mono/ui";
export function Example() {
return (
<CommentSystem
targetId="post-123"
targetType="post"
currentUserId="user-456"
/>
);
}ImageUpload(图片上传)
"use client";
import { useState } from "react";
import { ImageUpload } from "@mono/ui";
export function Example() {
const [imageUrl, setImageUrl] = useState("");
return (
<ImageUpload
value={imageUrl}
onChange={setImageUrl}
maxSize={5} // 5MB
accept="image/*"
/>
);
}主题系统
配置主题
使用 Providers 组件配置全局主题:
import { Providers } from "@mono/ui";
const config = {
ui: {
defaultTheme: "system", // "light" | "dark" | "system"
enabledThemes: ["light", "dark", "system"],
},
};
export function App({ children }) {
return (
<Providers config={config}>
{children}
</Providers>
);
}主题切换
使用 ColorModeToggle 组件:
import { ColorModeToggle } from "@mono/ui";
export function Header() {
return (
<header className="flex items-center justify-between p-4">
<h1>我的应用</h1>
<ColorModeToggle />
</header>
);
}自定义颜色
在 CSS 中定义颜色变量:
:root {
--color-primary: 220 90% 56%;
--color-primary-foreground: 0 0% 100%;
--color-background: 0 0% 100%;
--color-foreground: 222 47% 11%;
}
.dark {
--color-primary: 217 91% 60%;
--color-primary-foreground: 222 47% 11%;
--color-background: 222 47% 11%;
--color-foreground: 213 31% 91%;
}样式定制
使用 Tailwind CSS
所有组件都支持 className 属性:
<Button className="w-full bg-gradient-to-r from-blue-500 to-purple-500">
渐变按钮
</Button>
<Card className="border-2 border-primary shadow-lg">
自定义卡片
</Card>使用 cn 工具函数
合并类名:
import { cn } from "@mono/ui/lib";
<div className={cn(
"base-class",
isActive && "active-class",
className
)} />可访问性
所有组件都遵循 WCAG 2.1 AA 标准:
- ✅ 键盘导航支持
- ✅ 正确的 ARIA 标签
- ✅ 清晰的焦点指示器
- ✅ 屏幕阅读器友好
- ✅ 颜色对比度符合标准
焦点样式
<Button className="focus-visible:ring-2 focus-visible:ring-ring">
可访问的按钮
</Button>表单可访问性
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>邮箱</FormLabel>
<FormControl>
<Input
{...field}
aria-invalid={!!form.formState.errors.email}
aria-describedby="email-error"
/>
</FormControl>
<FormMessage id="email-error" />
</FormItem>
)}
/>响应式设计
移动端优先
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{items.map(item => (
<Card key={item.id}>{item.content}</Card>
))}
</div>响应式按钮
<Button className="w-full md:w-auto">
响应式按钮
</Button>抽屉 vs 对话框
在移动端使用 Drawer,桌面端使用 Dialog:
import { useMediaQuery } from "usehooks-ts";
import { Dialog, Drawer } from "@mono/ui";
export function ResponsiveModal({ children, ...props }) {
const isDesktop = useMediaQuery("(min-width: 768px)");
if (isDesktop) {
return <Dialog {...props}>{children}</Dialog>;
}
return <Drawer {...props}>{children}</Drawer>;
}性能优化
按需导入
// ✅ 推荐
import { Button, Card } from "@mono/ui";
// ❌ 不推荐
import * as UI from "@mono/ui";懒加载大型组件
import dynamic from "next/dynamic";
const CommentSystem = dynamic(() =>
import("@mono/ui").then(mod => ({ default: mod.CommentSystem })),
{ loading: () => <Skeleton className="h-40" /> }
);客户端组件
需要交互的组件使用 "use client":
"use client";
import { useState } from "react";
import { Button } from "@mono/ui";
export function Counter() {
const [count, setCount] = useState(0);
return <Button onClick={() => setCount(count + 1)}>{count}</Button>;
}最佳实践
1. 使用语义化组件
// ✅ 推荐
<Card>
<CardHeader>
<CardTitle>标题</CardTitle>
</CardHeader>
<CardContent>内容</CardContent>
</Card>
// ❌ 不推荐
<div className="rounded-lg border p-4">
<h3 className="font-bold">标题</h3>
<div>内容</div>
</div>2. 表单验证
使用 zod 进行类型安全的验证:
const schema = z.object({
email: z.string().email("无效的邮箱"),
password: z.string().min(8, "密码至少 8 位"),
});3. 错误处理
提供清晰的错误反馈:
<FormMessage />4. 加载状态
使用 Skeleton 或 Spinner:
{isLoading ? (
<Skeleton className="h-20 w-full" />
) : (
<Card>{content}</Card>
)}5. 主题一致性
使用语义化颜色:
// ✅ 推荐
<div className="bg-primary text-primary-foreground">
// ❌ 不推荐
<div className="bg-blue-500 text-white">故障排查
样式不生效
确保 Tailwind 配置包含 UI 包:
// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@mono/ui/src/**/*.{js,ts,jsx,tsx}",
],
};主题切换不工作
确保使用了 Providers:
import { Providers } from "@mono/ui";
<Providers config={config}>
<App />
</Providers>类型错误
安装 peer dependencies:
pnpm add react react-dom next
pnpm add -D @types/react @types/react-dom