包含: - furniture-3d-system:Next.js 16 全栈应用(资产中心、产品管理、3D装配动画) - autocad-plugin:AutoCAD 家具导出插件源码 - 资产中心改造:编辑弹窗化、删除功能、Toast反馈、自定义Select组件 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
15 KiB
15 KiB
Apple 风格 UI 组件使用指南
⚠️ 重要提示: 本文档提供代码示例,但设计标准以
docs/设计规范/苹果风格视觉标准.md为准。在使用代码示例时,请确保符合最新的设计 Token(如颜色、字体等)。
本指南提供了 Apple 风格 UI 设计系统中所有组件的详细使用说明和最佳实践。
目录
设计令牌
字体系统
import { LargeTitle, Title1, Body, Caption1 } from '@/components/ui/Typography'
function MyComponent() {
return (
<div>
<LargeTitle>大标题 (34pt)</LargeTitle>
<Title1>标题 1 (28pt)</Title1>
<Body>正文内容 (17pt)</Body>
<Caption1>说明文字 (12pt)</Caption1>
</div>
)
}
最佳实践:
- 使用语义化的字体组件而不是直接的 CSS 类
- 遵循苹果的字体层级,不要跳级使用
- 中英文混排时使用
fontType="chinese"属性
颜色系统
// 使用 Tailwind 类名
<div className="bg-apple-blue-light text-white">
蓝色背景
</div>
// 使用设计令牌
import { appleColorTokens } from '@/lib/design-tokens'
const blueColor = appleColorTokens.system.blue.light
最佳实践:
- 优先使用语义化颜色(如
apple-bg-primary) - 深色模式会自动切换颜色
- 使用系统颜色保持一致性
基础组件
Button 按钮
import Button from '@/components/ui/Button'
function ButtonExample() {
return (
<div className="space-x-4">
{/* 四种变体 */}
<Button variant="filled">Filled</Button>
<Button variant="tinted">Tinted</Button>
<Button variant="gray">Gray</Button>
<Button variant="plain">Plain</Button>
{/* 三种尺寸 */}
<Button size="small">Small (32px)</Button>
<Button size="regular">Regular (44px)</Button>
<Button size="large">Large (50px)</Button>
{/* 状态 */}
<Button loading>Loading</Button>
<Button disabled>Disabled</Button>
</div>
)
}
最佳实践:
- 主要操作使用
filled变体 - 次要操作使用
tinted或gray变体 - 确保按钮最小尺寸为 44px(苹果触摸目标标准)
- 使用
loading状态而不是禁用按钮
Input 输入框
import Input from '@/components/ui/Input'
function InputExample() {
return (
<div className="space-y-4">
{/* 基础输入框 */}
<Input
label="用户名"
placeholder="请输入用户名"
helperText="用户名必须唯一"
required
/>
{/* 状态变体 */}
<Input
label="邮箱"
type="email"
state="success"
helperText="邮箱格式正确"
/>
<Input
label="密码"
type="password"
errorText="密码长度至少 8 位"
/>
{/* 带图标 */}
<Input
label="搜索"
leftIcon="🔍"
rightIcon="✕"
onRightIconClick={() => console.log('清除')}
/>
</div>
)
}
最佳实践:
- 始终提供
label属性以支持可访问性 - 使用
helperText提供额外说明 - 错误状态使用
errorText而不是helperText - 合理使用图标增强用户体验
Card 卡片
import Card, { CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/Card'
function CardExample() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* 简单卡片 */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-2">卡片标题</h3>
<p className="text-gray-600">卡片内容</p>
</Card>
{/* 组合卡片 */}
<Card variant="elevated">
<CardHeader>
<CardTitle>高级卡片</CardTitle>
</CardHeader>
<CardContent>
<p>使用组合组件构建的卡片</p>
</CardContent>
<CardFooter>
<Button size="small">操作</Button>
</CardFooter>
</Card>
{/* 交互式卡片 */}
<Card
interactive
onClick={() => console.log('卡片点击')}
className="cursor-pointer"
>
<div className="p-6">
<h3>可点击卡片</h3>
<p>支持 hover 和 active 动画</p>
</div>
</Card>
</div>
)
}
最佳实践:
- 使用
variant="elevated"创建层次感 - 交互式卡片添加
interactive属性 - 保持卡片内容简洁,避免过度嵌套
导航组件
NavigationBar 导航栏
import NavigationBar, { BackButton, MenuButton } from '@/components/ui/NavigationBar'
function NavigationExample() {
return (
<div className="space-y-4">
{/* 大标题模式 */}
<NavigationBar
mode="large"
title="大标题导航"
subtitle="副标题说明"
leftContent={<BackButton onBack={() => history.back()} />}
rightContent={<MenuButton onMenu={() => console.log('菜单')} />}
/>
{/* 标准模式 */}
<NavigationBar
mode="standard"
title="标准导航"
leftContent={<BackButton />}
rightContent={
<div className="flex gap-2">
<Button size="small" variant="plain">编辑</Button>
<Button size="small" variant="tinted">保存</Button>
</div>
}
/>
</div>
)
}
TabBar 标签栏
import TabBar, { useTabBar } from '@/components/ui/TabBar'
function TabBarExample() {
const tabBar = useTabBar('home')
const tabs = [
{ id: 'home', label: '首页', icon: '🏠' },
{ id: 'search', label: '搜索', icon: '🔍' },
{ id: 'favorites', label: '收藏', icon: '❤️', badge: 3 },
{ id: 'profile', label: '我的', icon: '👤' },
]
return (
<TabBar
items={tabs}
activeId={tabBar.activeTab}
onTabChange={tabBar.switchTab}
/>
)
}
最佳实践:
- 标签栏最多显示 5 个标签
- 使用
useTabBarHook 管理状态 - 为重要标签添加徽章提示
数据展示组件
Badge 徽章
import Badge, { Tag, NumberBadge, StatusDot } from '@/components/ui/Badge'
function BadgeExample() {
return (
<div className="space-y-4">
{/* 基础徽章 */}
<div className="flex gap-2">
<Badge>Default</Badge>
<Badge type="success">Success</Badge>
<Badge type="error">Error</Badge>
<Badge type="warning">Warning</Badge>
<Badge type="info">Info</Badge>
</div>
{/* 数字徽章 */}
<div className="flex gap-2">
<NumberBadge count={5} />
<NumberBadge count={99} max={99} />
<NumberBadge count={999} max={99} />
</div>
{/* 标签 */}
<div className="flex gap-2">
<Tag closable onClose={() => console.log('关闭')}>
可关闭标签
</Tag>
<Tag icon="🔥" type="success">
带图标标签
</Tag>
</div>
{/* 状态点 */}
<div className="flex gap-2">
<StatusDot status="online" />
<StatusDot status="busy" />
<StatusDot status="away" />
<StatusDot status="offline" />
</div>
</div>
)
}
ProgressBar 进度条
import ProgressBar, { StepProgress } from '@/components/ui/ProgressBar'
function ProgressExample() {
const [progress, setProgress] = useState(45)
return (
<div className="space-y-6">
{/* 线性进度条 */}
<ProgressBar
value={progress}
showLabel
showPercentage
label="下载进度"
animated
/>
{/* 环形进度条 */}
<ProgressBar
value={progress}
variant="circular"
size="large"
type="success"
showPercentage
/>
{/* 步骤进度 */}
<StepProgress
current={2}
total={4}
showLabels
labels={['开始', '配置', '确认', '完成']}
/>
</div>
)
}
反馈组件
Modal 模态框
import Modal, { ConfirmModal } from '@/components/ui/Modal'
function ModalExample() {
const [showModal, setShowModal] = useState(false)
const [showConfirm, setShowConfirm] = useState(false)
return (
<div>
<Button onClick={() => setShowModal(true)}>
打开模态框
</Button>
<Modal
open={showModal}
onClose={() => setShowModal(false)}
title="模态框标题"
description="模态框描述"
size="medium"
>
<div className="space-y-4">
<p>模态框内容</p>
<div className="flex gap-3">
<Button variant="filled">确认</Button>
<Button variant="gray" onClick={() => setShowModal(false)}>
取消
</Button>
</div>
</div>
</Modal>
<ConfirmModal
open={showConfirm}
onClose={() => setShowConfirm(false)}
title="确认删除"
description="此操作无法撤销"
onConfirm={() => console.log('确认删除')}
/>
</div>
)
}
Toast 提示
import { useToast, ToastContainer } from '@/components/ui/Toast'
function ToastExample() {
const toast = useToast()
return (
<div>
<div className="space-x-2">
<Button onClick={() => toast.success('操作成功')}>
成功提示
</Button>
<Button onClick={() => toast.error('操作失败')}>
错误提示
</Button>
<Button onClick={() => toast.info('新消息', {
title: '系统通知',
action: {
label: '查看',
onClick: () => console.log('查看消息')
}
})}>
信息提示
</Button>
</div>
<ToastContainer
toasts={toast.toasts}
onClose={toast.removeToast}
/>
</div>
)
}
动画组件
AnimatedEntry 进入动画
import AnimatedEntry, { SlideInUp, StaggeredAnimation } from '@/components/ui/AnimatedEntry'
function AnimationExample() {
return (
<div className="space-y-8">
{/* 基础进入动画 */}
<AnimatedEntry direction="up" duration="normal">
<Card className="p-6">
<h3>从下方滑入的卡片</h3>
</Card>
</AnimatedEntry>
{/* 预配置动画 */}
<SlideInUp delay={200}>
<div>延迟 200ms 的滑入动画</div>
</SlideInUp>
{/* 交错动画 */}
<StaggeredAnimation staggerDelay={100}>
{[1, 2, 3, 4].map(i => (
<Card key={i} className="p-4">
<p>卡片 {i}</p>
</Card>
))}
</StaggeredAnimation>
</div>
)
}
最佳实践:
- 使用
trigger="visible"实现滚动触发动画 - 交错动画的延迟时间建议 50-150ms
- 响应用户的减少动画偏好设置
可访问性工具
用户偏好设置
import { PreferencesProvider, AccessibilityPanel, useUserPreferences } from '@/lib/preferences'
function App() {
return (
<PreferencesProvider>
<div className="app">
<AccessibilityPanel />
<MyContent />
</div>
</PreferencesProvider>
)
}
function MyContent() {
const { preferences } = useUserPreferences()
return (
<div>
<p>当前字体大小: {preferences.fontSize}%</p>
<p>减少动画: {preferences.prefersReducedMotion ? '是' : '否'}</p>
</div>
)
}
包容性设计
import { createInclusiveStatusIndicator } from '@/lib/accessibility'
function StatusExample() {
const successIndicator = createInclusiveStatusIndicator('success', {
showIcon: true,
showShape: true,
size: 'medium'
})
return (
<div
className="status-indicator success shape-circle"
style={successIndicator.style}
aria-label={successIndicator.ariaLabel}
>
{successIndicator.icon}
</div>
)
}
性能优化
虚拟滚动
import VirtualList from '@/components/ui/VirtualList'
function VirtualListExample() {
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `项目 ${i + 1}`,
description: `描述 ${i + 1}`
}))
return (
<VirtualList
items={items}
itemHeight={60}
height={400}
renderItem={(item, index) => (
<div className="flex items-center p-4 border-b">
<div className="flex-1">
<h4>{item.name}</h4>
<p className="text-sm text-gray-500">{item.description}</p>
</div>
</div>
)}
/>
)
}
性能监控
import { usePerformanceMonitor, useMemoryMonitor } from '@/lib/performance'
function PerformanceExample() {
usePerformanceMonitor('MyComponent')
const memoryInfo = useMemoryMonitor()
return (
<div>
{memoryInfo && (
<p>内存使用: {Math.round(memoryInfo.usedJSHeapSize / 1024 / 1024)}MB</p>
)}
</div>
)
}
迁移指南
从传统 CSS 迁移
// 旧方式
<div className="bg-blue-500 text-white p-4 rounded-lg">
内容
</div>
// 新方式
<Card variant="filled" className="bg-apple-blue-light text-white">
<div className="p-apple-4">
内容
</div>
</Card>
从其他 UI 库迁移
// Material-UI Button
<Button variant="contained" color="primary">
按钮
</Button>
// Apple UI Button
<Button variant="filled">
按钮
</Button>
常见问题
Q: 如何自定义主题颜色?
A: 修改 tailwind.config.ts 中的颜色令牌,或在 CSS 中覆盖 CSS 变量。
Q: 动画不生效怎么办?
A: 检查用户是否启用了减少动画偏好,或者组件是否设置了 disabled 属性。
Q: 如何确保可访问性?
A: 使用语义化 HTML,提供 ARIA 属性,确保键盘导航,测试对比度。
Q: 性能优化建议?
A: 使用 React.memo,避免不必要的重渲染,大列表使用虚拟滚动。
Slider 滑动条(统一元组件)
Slider 元组件位于 src/components/ui/Slider.tsx,用于统一替代原生 input[type="range"]。
import Slider from '@/components/ui/Slider'
function DemoSlider() {
return (
<Slider
min={0}
max={1}
step={0.01}
value={0.45}
onValueChange={(value) => console.log(value)}
aria-label="爆炸程度"
className="w-40"
/>
)
}
最佳实践:
- 必须使用
onValueChange接收数值,避免在业务层反复做字符串转数字。 - 必须补齐
aria-label,保证键盘与读屏可访问性。 - 禁止新增独立的 range 样式分叉,统一使用
apple-slider视觉规范。
总结
Apple 风格 UI 设计系统提供了完整的组件库和工具集,遵循苹果 Human Interface Guidelines 2025 标准。通过合理使用这些组件和工具,可以构建出美观、易用、高性能的用户界面。
记住始终考虑可访问性、性能和用户体验,让每个用户都能享受到优质的交互体验。