UI 组件
ProductReady 的 UI 组件库 (KUI) 完整参考
UI 组件
ProductReady 使用 KUI - 一个基于 shadcn/ui 构建的综合 UI 组件库,并带有额外增强。所有组件都是:
- ✅ 类型安全 - 完整的 TypeScript 支持
- ✅ 可访问 - 符合 WCAG 2.1 AA
- ✅ 可主题化 - CSS 变量用于自定义
- ✅ 深色模式 - 内置深色模式支持
- ✅ 可摇树优化 - 只导入你需要的
位置:所有组件位于 packages/kui/src/components/ui/
快速导入
import { Button } from 'kui/button';
import { Card } from 'kui/card';
import { Input } from 'kui/input';
export function MyComponent() {
return (
<Card>
<Input placeholder="输入文本" />
<Button>提交</Button>
</Card>
);
}组件分类
表单组件
import { Input } from 'kui/input';
// 基本输入
<Input placeholder="邮箱" type="email" />
// 带标签
<div>
<Label htmlFor="email">邮箱</Label>
<Input id="email" type="email" />
</div>
// 禁用
<Input disabled placeholder="禁用" />import { Textarea } from 'kui/textarea';
<Textarea placeholder="你的消息..." rows={4} />import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'kui/select';
<Select>
<SelectTrigger>
<SelectValue placeholder="选择选项" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">选项 1</SelectItem>
<SelectItem value="option2">选项 2</SelectItem>
</SelectContent>
</Select>import { Checkbox } from 'kui/checkbox';
import { Label } from 'kui/label';
<div className="flex items-center gap-2">
<Checkbox id="terms" />
<Label htmlFor="terms">接受条款</Label>
</div>可用的表单组件:
Input- 带变体的文本输入Textarea- 多行文本输入Select- 下拉选择Checkbox- 带标签的复选框Radio Group- 单选按钮组Switch- 切换开关Slider- 范围滑块Calendar- 日期选择器Input OTP- 一次性密码输入Form- 带验证的表单包装器
布局组件
Card
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'kui/card';
<Card>
<CardHeader>
<CardTitle>卡片标题</CardTitle>
<CardDescription>卡片描述在这里</CardDescription>
</CardHeader>
<CardContent>
<p>卡片内容</p>
</CardContent>
<CardFooter>
<Button>操作</Button>
</CardFooter>
</Card>Separator
import { Separator } from 'kui/separator';
<div>
<p>上方内容</p>
<Separator className="my-4" />
<p>下方内容</p>
</div>
// 垂直分隔符
<Separator orientation="vertical" className="h-4" />Tabs
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'kui/tabs';
<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>Accordion
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'kui/accordion';
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>它是可访问的吗?</AccordionTrigger>
<AccordionContent>
是的。它遵循 WAI-ARIA 设计模式。
</AccordionContent>
</AccordionItem>
</Accordion>按钮和操作
Button
import { Button } from 'kui/button';
// 变体
<Button variant="default">默认</Button>
<Button variant="destructive">破坏性</Button>
<Button variant="outline">轮廓</Button>
<Button variant="secondary">次要</Button>
<Button variant="ghost">幽灵</Button>
<Button variant="link">链接</Button>
// 尺寸
<Button size="default">默认</Button>
<Button size="sm">小</Button>
<Button size="lg">大</Button>
<Button size="icon"><Icon /></Button>
// 状态
<Button disabled>禁用</Button>
<Button loading>加载中...</Button>Button Group
import { ButtonGroup } from 'kui/button-group';
<ButtonGroup>
<Button>左</Button>
<Button>中</Button>
<Button>右</Button>
</ButtonGroup>Toggle
import { Toggle } from 'kui/toggle';
<Toggle aria-label="切换斜体">
<Italic className="h-4 w-4" />
</Toggle>覆盖组件
Dialog
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from 'kui/dialog';
<Dialog>
<DialogTrigger asChild>
<Button>打开对话框</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>对话框标题</DialogTitle>
<DialogDescription>对话框描述</DialogDescription>
</DialogHeader>
<p>对话框内容在这里</p>
</DialogContent>
</Dialog>Drawer
import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger } from 'kui/drawer';
<Drawer>
<DrawerTrigger>打开抽屉</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>抽屉标题</DrawerTitle>
<DrawerDescription>抽屉描述</DrawerDescription>
</DrawerHeader>
</DrawerContent>
</Drawer>Sheet
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from 'kui/sheet';
<Sheet>
<SheetTrigger>打开表单</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>表单标题</SheetTitle>
<SheetDescription>表单描述</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>Popover
import { Popover, PopoverContent, PopoverTrigger } from 'kui/popover';
<Popover>
<PopoverTrigger>打开弹出框</PopoverTrigger>
<PopoverContent>
<p>弹出框内容</p>
</PopoverContent>
</Popover>Tooltip
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'kui/tooltip';
<TooltipProvider>
<Tooltip>
<TooltipTrigger>悬停我</TooltipTrigger>
<TooltipContent>
<p>工具提示文本</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>反馈组件
Alert
import { Alert, AlertDescription, AlertTitle } from 'kui/alert';
import { AlertCircle } from 'lucide-react';
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertTitle>注意!</AlertTitle>
<AlertDescription>
这是一条警告消息。
</AlertDescription>
</Alert>
// 变体
<Alert variant="default">默认警告</Alert>
<Alert variant="destructive">错误警告</Alert>Toast (Sonner)
import { toast } from 'sonner';
// 成功
toast.success('任务创建成功!');
// 错误
toast.error('出了些问题');
// 信息
toast.info('有新更新可用');
// 自定义
toast('自定义消息', {
description: '带描述',
action: {
label: '撤消',
onClick: () => console.log('撤消'),
},
});Progress
import { Progress } from 'kui/progress';
<Progress value={60} /> // 60%Spinner
import { Spinner } from 'kui/spinner';
<Spinner size="sm" />
<Spinner size="default" />
<Spinner size="lg" />Skeleton
import { Skeleton } from 'kui/skeleton';
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
</div>数据展示
Table
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'kui/table';
<Table>
<TableHeader>
<TableRow>
<TableHead>名称</TableHead>
<TableHead>邮箱</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>张三</TableCell>
<TableCell>john@example.com</TableCell>
</TableRow>
</TableBody>
</Table>Badge
import { Badge } from 'kui/badge';
<Badge>默认</Badge>
<Badge variant="secondary">次要</Badge>
<Badge variant="destructive">破坏性</Badge>
<Badge variant="outline">轮廓</Badge>Avatar
import { Avatar, AvatarFallback, AvatarImage } from 'kui/avatar';
<Avatar>
<AvatarImage src="/avatar.jpg" alt="用户" />
<AvatarFallback>张三</AvatarFallback>
</Avatar>Stats Card
import { StatsCard } from 'kui/stats-card';
<StatsCard
title="总用户"
value="1,234"
change="+12%"
trend="up"
/>Chart
import { Chart } from 'kui/chart';
<Chart
data={chartData}
type="line"
xKey="date"
yKey="value"
/>导航组件
Breadcrumb
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from 'kui/breadcrumb';
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">首页</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>当前页面</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>Sidebar
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from 'kui/sidebar';
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>导航</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/dashboard">仪表板</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>Menubar
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarTrigger } from 'kui/menubar';
<Menubar>
<MenubarMenu>
<MenubarTrigger>文件</MenubarTrigger>
<MenubarContent>
<MenubarItem>新建文件</MenubarItem>
<MenubarItem>保存</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>Pagination
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from 'kui/pagination';
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>特殊组件
命令面板
import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from 'kui/command';
<Command>
<CommandInput placeholder="输入命令..." />
<CommandList>
<CommandEmpty>未找到结果。</CommandEmpty>
<CommandGroup heading="建议">
<CommandItem>仪表板</CommandItem>
<CommandItem>设置</CommandItem>
</CommandGroup>
</CommandList>
</Command>轮播
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from 'kui/carousel';
<Carousel>
<CarouselContent>
<CarouselItem>幻灯片 1</CarouselItem>
<CarouselItem>幻灯片 2</CarouselItem>
<CarouselItem>幻灯片 3</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>上下文菜单
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from 'kui/context-menu';
<ContextMenu>
<ContextMenuTrigger>右键点击我</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>编辑</ContextMenuItem>
<ContextMenuItem>删除</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>完整组件列表
| 组件 | 描述 | 类别 |
|---|---|---|
accordion | 可折叠内容部分 | 布局 |
alert-dialog | 模态确认对话框 | 覆盖 |
alert | 反馈消息 | 反馈 |
aspect-ratio | 保持纵横比 | 布局 |
avatar | 用户头像图片 | 数据展示 |
badge | 状态指示器 | 数据展示 |
breadcrumb | 导航面包屑 | 导航 |
button-group | 分组按钮 | 操作 |
button | 可点击按钮 | 操作 |
calendar | 日期选择器 | 表单 |
card | 内容容器 | 布局 |
carousel | 图片/内容滑块 | 特殊 |
chart | 数据可视化 | 数据展示 |
checkbox | 复选框输入 | 表单 |
collapsible | 可展开内容 | 布局 |
command | 命令面板 | 特殊 |
context-menu | 右键菜单 | 导航 |
dialog | 模态对话框 | 覆盖 |
drawer | 侧边面板 | 覆盖 |
dropdown-menu | 下拉菜单 | 导航 |
empty | 空状态 | 反馈 |
field | 表单字段包装器 | 表单 |
form | 表单容器 | 表单 |
hover-card | 悬停预览卡片 | 覆盖 |
input-group | 分组输入 | 表单 |
input-otp | OTP 输入 | 表单 |
input | 文本输入 | 表单 |
item | 列表项 | 数据展示 |
kbd | 键盘快捷键 | 数据展示 |
label | 输入标签 | 表单 |
menubar | 菜单栏 | 导航 |
navigation-menu | 导航菜单 | 导航 |
pagination | 页面导航 | 导航 |
popover | 浮动内容 | 覆盖 |
progress | 进度条 | 反馈 |
radio-group | 单选按钮 | 表单 |
resizable | 可调整大小面板 | 布局 |
scroll-area | 可滚动区域 | 布局 |
select | 下拉选择 | 表单 |
separator | 分隔线 | 布局 |
sheet | 侧边表单 | 覆盖 |
sidebar | 应用侧边栏 | 导航 |
skeleton | 加载占位符 | 反馈 |
slider | 范围滑块 | 表单 |
sonner | Toast 通知 | 反馈 |
spinner | 加载旋转器 | 反馈 |
stats-card | 指标卡片 | 数据展示 |
switch | 切换开关 | 表单 |
table-list-view | 表格/列表视图 | 数据展示 |
table | 数据表格 | 数据展示 |
tabs | 选项卡内容 | 布局 |
textarea | 多行输入 | 表单 |
toggle-group | 切换按钮组 | 操作 |
toggle | 切换按钮 | 操作 |
tooltip | 悬停工具提示 | 覆盖 |
自定义
主题颜色
编辑 apps/productready/src/app/globals.css:
@layer base {
:root {
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
/* ... 更多变量 */
}
.dark {
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
/* ... 更多变量 */
}
}组件变体
扩展组件变体:
// 创建自定义按钮变体
<Button className="bg-purple-600 hover:bg-purple-700">
自定义颜色
</Button>最佳实践
✅ 应该做
- 使用语义 HTML - 组件渲染正确的 HTML
- 只导入需要的 - 摇树优化包大小
- 遵循可访问性 - 使用标签、ARIA 属性
- 组合组件 - 从简单部分构建复杂 UI
- 测试深色模式 - 所有组件支持深色模式
❌ 不应该做
- 不要直接修改 KUI - 创建包装器组件
- 不要跳过标签 - 始终标记表单输入
- 不要内联样式 - 使用 className 和 Tailwind
- 不要忘记加载状态 - 为异步操作显示旋转器
常见模式和实际示例
用户资料卡片
组合多个组件创建完整的用户资料卡片:
import { Card, CardContent, CardFooter, CardHeader } from 'kui/card';
import { Avatar, AvatarFallback, AvatarImage } from 'kui/avatar';
import { Button } from 'kui/button';
import { Badge } from 'kui/badge';
export function UserProfileCard({ user }: { user: User }) {
return (
<Card className="w-[350px]">
<CardHeader className="flex flex-row items-center gap-4">
<Avatar className="h-12 w-12">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>{user.name.slice(0, 2).toUpperCase()}</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<h3 className="font-semibold">{user.name}</h3>
<p className="text-sm text-muted-foreground">{user.email}</p>
</div>
</CardHeader>
<CardContent>
<div className="flex gap-2 flex-wrap">
<Badge variant="secondary">专业会员</Badge>
<Badge variant="outline">活跃</Badge>
</div>
<p className="mt-4 text-sm">{user.bio}</p>
</CardContent>
<CardFooter className="flex gap-2">
<Button variant="default" className="flex-1">发消息</Button>
<Button variant="outline" className="flex-1">关注</Button>
</CardFooter>
</Card>
);
}带验证的设置表单
包含多种输入类型的完整表单示例:
'use client';
import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'kui/card';
import { Input } from 'kui/input';
import { Label } from 'kui/label';
import { Switch } from 'kui/switch';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'kui/select';
import { Button } from 'kui/button';
import { Separator } from 'kui/separator';
import { toast } from 'sonner';
export function SettingsForm() {
const [settings, setSettings] = useState({
name: '',
email: '',
theme: 'light',
notifications: true,
});
const handleSave = () => {
toast.success('设置已成功保存!');
};
return (
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>账户设置</CardTitle>
<CardDescription>管理您的账户偏好设置</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="name">显示名称</Label>
<Input
id="name"
placeholder="输入您的名字"
value={settings.name}
onChange={(e) => setSettings({ ...settings, name: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">电子邮件地址</Label>
<Input
id="email"
type="email"
placeholder="your@email.com"
value={settings.email}
onChange={(e) => setSettings({ ...settings, email: e.target.value })}
/>
</div>
<Separator />
<div className="space-y-2">
<Label htmlFor="theme">主题</Label>
<Select
value={settings.theme}
onValueChange={(value) => setSettings({ ...settings, theme: value })}
>
<SelectTrigger id="theme">
<SelectValue placeholder="选择主题" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">浅色</SelectItem>
<SelectItem value="dark">深色</SelectItem>
<SelectItem value="system">系统</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>邮件通知</Label>
<p className="text-sm text-muted-foreground">
接收有关账户活动的电子邮件
</p>
</div>
<Switch
checked={settings.notifications}
onCheckedChange={(checked) => setSettings({ ...settings, notifications: checked })}
/>
</div>
<div className="flex gap-2 justify-end">
<Button variant="outline">取消</Button>
<Button onClick={handleSave}>保存更改</Button>
</div>
</CardContent>
</Card>
);
}带操作的数据表格
带行操作的交互式表格:
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'kui/table';
import { Badge } from 'kui/badge';
import { Button } from 'kui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from 'kui/dropdown-menu';
import { MoreHorizontal, Pencil, Trash } from 'lucide-react';
export function UsersTable({ users }: { users: User[] }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>名称</TableHead>
<TableHead>邮箱</TableHead>
<TableHead>状态</TableHead>
<TableHead>角色</TableHead>
<TableHead className="text-right">操作</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Badge variant={user.active ? 'default' : 'secondary'}>
{user.active ? '活跃' : '不活跃'}
</Badge>
</TableCell>
<TableCell>{user.role}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Pencil className="mr-2 h-4 w-4" />
编辑
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
<Trash className="mr-2 h-4 w-4" />
删除
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}确认对话框
带有良好用户体验的删除确认:
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from 'kui/alert-dialog';
import { Button } from 'kui/button';
import { Trash } from 'lucide-react';
export function DeleteButton({ itemName, onDelete }: { itemName: string; onDelete: () => void }) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm">
<Trash className="mr-2 h-4 w-4" />
删除
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>您确定要这样做吗?</AlertDialogTitle>
<AlertDialogDescription>
此操作无法撤消。这将永久删除 "{itemName}"
并从我们的服务器中删除数据。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction onClick={onDelete} className="bg-destructive text-destructive-foreground">
删除
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}仪表板统计卡片
使用统计卡片显示指标:
import { Card, CardContent, CardHeader, CardTitle } from 'kui/card';
import { ArrowUp, ArrowDown, Users, DollarSign, Activity, TrendingUp } from 'lucide-react';
export function DashboardStats() {
const stats = [
{ title: '总收入', value: '¥352,341', change: '+20.1%', icon: DollarSign, positive: true },
{ title: '活跃用户', value: '2,345', change: '+12%', icon: Users, positive: true },
{ title: '转化率', value: '3.24%', change: '-2.4%', icon: TrendingUp, positive: false },
{ title: 'API 调用', value: '120万', change: '+45%', icon: Activity, positive: true },
];
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{stats.map((stat) => (
<Card key={stat.title}>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{stat.title}
</CardTitle>
<stat.icon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stat.value}</div>
<p className={`text-xs flex items-center gap-1 ${stat.positive ? 'text-green-600' : 'text-red-600'}`}>
{stat.positive ? <ArrowUp className="h-3 w-3" /> : <ArrowDown className="h-3 w-3" />}
较上月 {stat.change}
</p>
</CardContent>
</Card>
))}
</div>
);
}加载状态和骨架屏
显示加载占位符:
import { Card, CardContent, CardHeader } from 'kui/card';
import { Skeleton } from 'kui/skeleton';
export function UserCardSkeleton() {
return (
<Card>
<CardHeader className="flex flex-row items-center gap-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2 flex-1">
<Skeleton className="h-4 w-[200px]" />
<Skeleton className="h-3 w-[150px]" />
</div>
</CardHeader>
<CardContent>
<Skeleton className="h-20 w-full" />
</CardContent>
</Card>
);
}
// 在组件中使用
export function UsersList() {
const { data: users, isLoading } = trpc.users.list.useQuery();
if (isLoading) {
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<UserCardSkeleton key={i} />
))}
</div>
);
}
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}组件组合技巧
构建复杂表单
有效组合表单组件:
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from 'kui/form';
import { Input } from 'kui/input';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
const formSchema = z.object({
username: z.string().min(3).max(20),
email: z.string().email(),
});
export function SignupForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>用户名</FormLabel>
<FormControl>
<Input placeholder="zhangsan" {...field} />
</FormControl>
<FormDescription>这是您的公开显示名称。</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">创建账户</Button>
</form>
</Form>
);
}响应式布局
使用卡片和网格实现响应式设计:
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{items.map((item) => (
<Card key={item.id} className="hover:shadow-lg transition-shadow">
<CardHeader>
<CardTitle>{item.title}</CardTitle>
</CardHeader>
<CardContent>{item.content}</CardContent>
</Card>
))}
</div>可访问性提示
键盘导航
所有 KUI 组件都支持键盘导航:
- Tab - 在可聚焦元素之间移动
- Enter/Space - 激活按钮和开关
- 方向键 - 导航菜单、选择器、单选按钮组
- Escape - 关闭对话框、弹出框、侧边栏
- Home/End - 导航到列表中的第一个/最后一个项目
屏幕阅读器支持
组件包含 ARIA 属性:
// 带 aria-label 的按钮
<Button aria-label="关闭对话框">
<X className="h-4 w-4" />
</Button>
// 正确标记的输入
<Label htmlFor="email">电子邮件</Label>
<Input id="email" type="email" aria-describedby="email-description" />
<p id="email-description" className="text-sm text-muted-foreground">
我们永远不会分享您的电子邮件。
</p>性能优化
懒加载组件
对于大型应用程序,懒加载重型组件:
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('~/components/heavy-chart'), {
loading: () => <Skeleton className="h-[400px] w-full" />,
ssr: false,
});记忆化
防止不必要的重新渲染:
import { memo } from 'react';
export const UserCard = memo(function UserCard({ user }: { user: User }) {
return (
<Card>
{/* 组件内容 */}
</Card>
);
});下一步
- 设计系统 - 自定义主题和颜色
- Tailwind CSS - 实用类参考
- Lucide Icons - ProductReady 使用的图标库
- Radix UI - 无样式组件原语
所有组件都已生产就绪并完全可访问! 🎨
如需实时组件演示和交互示例,请在开发模式下探索 ProductReady 仪表板。