01MVP 标识01MVP
包文档UI 与工具ui UI 组件

ui UI 组件

共享 UI 组件库,基于 shadcn/ui 和 Radix UI 构建

本页面介绍组件 API 使用方法。设计原则和规范请参考 开发指南

@mono/ui

共享 UI 组件库,基于 shadcn/uiRadix UI 构建,提供完整的设计系统和业务组件。

特性

  • 80+ 个可复用的 UI 组件
  • 基于 shadcn/ui 的设计系统
  • Radix UI 原语,确保可访问性
  • 内置亮色/暗色主题切换
  • 完整的 TypeScript 类型定义
  • 与 react-hook-form 深度集成
  • 移动端优先的响应式设计

安装

pnpm add @mono/ui

快速开始

基础组件

import { Button } from "@mono/ui";

export function Example() {
  return (
    <div className="flex gap-2">
      <Button>默认按钮</Button>
      <Button variant="outline">轮廓按钮</Button>
      <Button variant="destructive">危险按钮</Button>
      <Button variant="ghost">幽灵按钮</Button>
    </div>
  );
}
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

相关资源