0.0.8.0
This commit is contained in:
@@ -0,0 +1,694 @@
|
||||
# 🎨 按钮使用规范指南
|
||||
|
||||
## 📋 目录
|
||||
- [按钮样式规范](#按钮样式规范)
|
||||
- [按钮尺寸规范](#按钮尺寸规范)
|
||||
- [图标使用规范](#图标使用规范)
|
||||
- [交互状态规范](#交互状态规范)
|
||||
- [权限控制规范](#权限控制规范)
|
||||
- [最佳实践](#最佳实践)
|
||||
|
||||
---
|
||||
|
||||
## 按钮样式规范
|
||||
|
||||
### 样式类型
|
||||
|
||||
```typescript
|
||||
type ButtonVariant =
|
||||
| "default" // 默认样式
|
||||
| "destructive" // 危险操作
|
||||
| "outline" // 边框样式
|
||||
| "secondary" // 次要操作
|
||||
| "ghost" // 幽灵按钮
|
||||
| "link" // 链接样式
|
||||
```
|
||||
|
||||
### 使用场景
|
||||
|
||||
#### 1. Primary (default)
|
||||
**用途**: 页面主要操作,每个页面最多1-2个
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button variant="default">保存</Button>
|
||||
<Button variant="default">创建家族树</Button>
|
||||
<Button variant="default">提交</Button>
|
||||
|
||||
// ❌ 错误使用 - 不要在一个区域放置多个 primary 按钮
|
||||
<Button variant="default">保存</Button>
|
||||
<Button variant="default">提交</Button>
|
||||
<Button variant="default">确认</Button>
|
||||
```
|
||||
|
||||
**视觉特征**:
|
||||
- 背景色: `bg-primary`
|
||||
- 文字色: `text-primary-foreground`
|
||||
- 高对比度,最显眼
|
||||
|
||||
#### 2. Destructive
|
||||
**用途**: 危险操作,如删除、清空
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button variant="destructive" onClick={handleDelete}>
|
||||
<Trash className="h-4 w-4 mr-2" />
|
||||
删除成员
|
||||
</Button>
|
||||
|
||||
// ✅ 必须配合确认对话框
|
||||
const handleDelete = async () => {
|
||||
const confirmed = await showConfirm('确定要删除吗?')
|
||||
if (confirmed) {
|
||||
// 执行删除
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**视觉特征**:
|
||||
- 背景色: `bg-destructive` (红色)
|
||||
- 文字色: `text-destructive-foreground`
|
||||
- 警示作用
|
||||
|
||||
#### 3. Outline
|
||||
**用途**: 次要操作,常规功能
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button variant="outline">编辑</Button>
|
||||
<Button variant="outline">导出</Button>
|
||||
<Button variant="outline">取消</Button>
|
||||
```
|
||||
|
||||
**视觉特征**:
|
||||
- 边框: `border border-input`
|
||||
- 背景: 透明
|
||||
- 文字色: `text-foreground`
|
||||
|
||||
#### 4. Ghost
|
||||
**用途**: 辅助操作,不抢眼的功能
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button variant="ghost">返回</Button>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
**视觉特征**:
|
||||
- 无边框,无背景
|
||||
- hover 时显示背景
|
||||
- 最不显眼
|
||||
|
||||
#### 5. Secondary
|
||||
**用途**: 次要强调操作
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button variant="secondary">添加配偶</Button>
|
||||
<Button variant="secondary">筛选</Button>
|
||||
```
|
||||
|
||||
**视觉特征**:
|
||||
- 背景色: `bg-secondary`
|
||||
- 介于 primary 和 outline 之间
|
||||
|
||||
#### 6. Link
|
||||
**用途**: 链接样式的按钮
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button variant="link">了解更多</Button>
|
||||
<Button variant="link">查看详情</Button>
|
||||
```
|
||||
|
||||
**视觉特征**:
|
||||
- 无背景,无边框
|
||||
- 文字带下划线
|
||||
- 类似超链接
|
||||
|
||||
---
|
||||
|
||||
## 按钮尺寸规范
|
||||
|
||||
### 尺寸类型
|
||||
|
||||
```typescript
|
||||
type ButtonSize =
|
||||
| "default" // 默认尺寸
|
||||
| "sm" // 小尺寸
|
||||
| "lg" // 大尺寸
|
||||
| "icon" // 图标按钮
|
||||
```
|
||||
|
||||
### 使用场景
|
||||
|
||||
#### 1. Default
|
||||
**用途**: 常规按钮
|
||||
|
||||
```tsx
|
||||
<Button size="default">保存</Button>
|
||||
```
|
||||
|
||||
**尺寸**: `h-10 px-4 py-2`
|
||||
|
||||
#### 2. Small (sm)
|
||||
**用途**: 紧凑布局、辅助操作
|
||||
|
||||
```tsx
|
||||
<Button size="sm">刷新</Button>
|
||||
<Button size="sm" variant="ghost">查看全部</Button>
|
||||
```
|
||||
|
||||
**尺寸**: `h-9 px-3`
|
||||
|
||||
#### 3. Large (lg)
|
||||
**用途**: 重要操作、首屏按钮
|
||||
|
||||
```tsx
|
||||
<Button size="lg">立即开始</Button>
|
||||
```
|
||||
|
||||
**尺寸**: `h-11 px-8`
|
||||
|
||||
#### 4. Icon
|
||||
**用途**: 仅图标按钮
|
||||
|
||||
```tsx
|
||||
<Button size="icon" variant="outline">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
**尺寸**: `h-10 w-10` (正方形)
|
||||
|
||||
---
|
||||
|
||||
## 图标使用规范
|
||||
|
||||
### 图标位置
|
||||
|
||||
#### 1. 左侧图标
|
||||
**用途**: 强调操作类型
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
新增成员
|
||||
</Button>
|
||||
|
||||
<Button>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
导出数据
|
||||
</Button>
|
||||
```
|
||||
|
||||
#### 2. 右侧图标
|
||||
**用途**: 表示方向或展开
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button>
|
||||
查看全部
|
||||
<ArrowRight className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
|
||||
<Button>
|
||||
更多选项
|
||||
<ChevronDown className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
#### 3. 仅图标
|
||||
**用途**: 空间受限或辅助操作
|
||||
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button size="icon" variant="ghost" title="搜索">
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
// ⚠️ 注意:必须添加 title 属性用于无障碍访问
|
||||
```
|
||||
|
||||
### 图标尺寸
|
||||
|
||||
| 按钮尺寸 | 图标尺寸 | 类名 |
|
||||
|---------|---------|------|
|
||||
| sm | 3 | `h-3 w-3` |
|
||||
| default | 4 | `h-4 w-4` |
|
||||
| lg | 5 | `h-5 w-5` |
|
||||
|
||||
### 图标间距
|
||||
|
||||
```tsx
|
||||
// 左侧图标
|
||||
<Icon className="mr-2" />
|
||||
|
||||
// 右侧图标
|
||||
<Icon className="ml-2" />
|
||||
|
||||
// 仅图标(无间距)
|
||||
<Icon />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 交互状态规范
|
||||
|
||||
### 状态类型
|
||||
|
||||
#### 1. 默认状态
|
||||
```tsx
|
||||
<Button>点击我</Button>
|
||||
```
|
||||
|
||||
#### 2. Hover 状态
|
||||
```tsx
|
||||
// 自动处理,无需额外代码
|
||||
// primary: hover:bg-primary/90
|
||||
// outline: hover:bg-accent
|
||||
// ghost: hover:bg-accent hover:text-accent-foreground
|
||||
```
|
||||
|
||||
#### 3. 禁用状态
|
||||
```tsx
|
||||
// ✅ 正确使用
|
||||
<Button disabled={isLoading || !hasPermission}>
|
||||
保存
|
||||
</Button>
|
||||
|
||||
// ✅ 配合加载状态
|
||||
<Button disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
保存中...
|
||||
</>
|
||||
) : (
|
||||
'保存'
|
||||
)}
|
||||
</Button>
|
||||
```
|
||||
|
||||
**视觉特征**:
|
||||
- 透明度: `opacity-50`
|
||||
- 鼠标: `cursor-not-allowed`
|
||||
- 禁用点击事件
|
||||
|
||||
#### 4. 加载状态
|
||||
```tsx
|
||||
// ✅ 推荐方式
|
||||
import { Loader2 } from "lucide-react"
|
||||
|
||||
<Button disabled={isLoading}>
|
||||
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{isLoading ? '处理中...' : '提交'}
|
||||
</Button>
|
||||
```
|
||||
|
||||
#### 5. 激活状态
|
||||
```tsx
|
||||
// ✅ 用于切换按钮
|
||||
<Button
|
||||
variant={isActive ? "secondary" : "outline"}
|
||||
onClick={toggle}
|
||||
>
|
||||
{isActive ? '已激活' : '未激活'}
|
||||
</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 权限控制规范
|
||||
|
||||
### 权限级别
|
||||
|
||||
```typescript
|
||||
type Role = "OWNER" | "EDITOR" | "VIEWER"
|
||||
```
|
||||
|
||||
### 实现方式
|
||||
|
||||
#### 1. 条件渲染
|
||||
```tsx
|
||||
// ✅ 推荐:完全隐藏按钮
|
||||
{(role === "OWNER" || role === "EDITOR") && (
|
||||
<Button onClick={handleEdit}>编辑</Button>
|
||||
)}
|
||||
```
|
||||
|
||||
#### 2. 禁用状态
|
||||
```tsx
|
||||
// ⚠️ 可选:显示但禁用
|
||||
<Button
|
||||
disabled={role === "VIEWER"}
|
||||
title={role === "VIEWER" ? "您没有编辑权限" : ""}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 权限矩阵
|
||||
|
||||
| 操作 | VIEWER | EDITOR | OWNER |
|
||||
|-----|--------|--------|-------|
|
||||
| 查看 | ✅ | ✅ | ✅ |
|
||||
| 新增 | ❌ | ✅ | ✅ |
|
||||
| 编辑 | ❌ | ✅ | ✅ |
|
||||
| 删除 | ❌ | ✅ | ✅ |
|
||||
| 管理协作者 | ❌ | ❌ | ✅ |
|
||||
| 删除家族树 | ❌ | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 按钮组合
|
||||
|
||||
#### ✅ 正确的按钮组合
|
||||
```tsx
|
||||
// 主次分明
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button variant="default" onClick={onSave}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### ❌ 错误的按钮组合
|
||||
```tsx
|
||||
// 不要使用多个 primary 按钮
|
||||
<div className="flex gap-2">
|
||||
<Button variant="default">保存</Button>
|
||||
<Button variant="default">提交</Button>
|
||||
<Button variant="default">确认</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. 按钮位置
|
||||
|
||||
#### 表单按钮
|
||||
```tsx
|
||||
// ✅ 正确:右对齐,取消在左,确认在右
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline">取消</Button>
|
||||
<Button variant="default">保存</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 对话框按钮
|
||||
```tsx
|
||||
// ✅ 正确:底部右对齐
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button variant="default" onClick={onConfirm}>
|
||||
确认
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
```
|
||||
|
||||
### 3. 响应式设计
|
||||
|
||||
```tsx
|
||||
// ✅ 移动端优化
|
||||
<Button className="w-full md:w-auto">
|
||||
保存
|
||||
</Button>
|
||||
|
||||
// ✅ 图标按钮在移动端隐藏文字
|
||||
<Button>
|
||||
<Plus className="h-4 w-4 md:mr-2" />
|
||||
<span className="hidden md:inline">新增</span>
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 4. 无障碍访问
|
||||
|
||||
```tsx
|
||||
// ✅ 正确:添加 aria 属性
|
||||
<Button
|
||||
aria-label="删除成员"
|
||||
title="删除成员"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
// ✅ 正确:键盘支持
|
||||
<Button
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
handleClick()
|
||||
}
|
||||
}}
|
||||
>
|
||||
点击我
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 5. 加载和错误处理
|
||||
|
||||
```tsx
|
||||
// ✅ 完整的状态处理
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
await saveData()
|
||||
toast.success('保存成功')
|
||||
} catch (err) {
|
||||
setError('保存失败')
|
||||
toast.error('保存失败')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{isLoading ? '保存中...' : '保存'}
|
||||
</Button>
|
||||
{error && <p className="text-destructive text-sm mt-2">{error}</p>}
|
||||
</>
|
||||
)
|
||||
```
|
||||
|
||||
### 6. 确认对话框
|
||||
|
||||
```tsx
|
||||
// ✅ 危险操作必须确认
|
||||
const handleDelete = async () => {
|
||||
const confirmed = await showConfirm(
|
||||
'确定要删除这个成员吗?此操作不可撤销。',
|
||||
'删除确认',
|
||||
'destructive'
|
||||
)
|
||||
|
||||
if (!confirmed) return
|
||||
|
||||
try {
|
||||
await deleteMember(id)
|
||||
toast.success('删除成功')
|
||||
router.push('/members')
|
||||
} catch (error) {
|
||||
toast.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
<Button variant="destructive" onClick={handleDelete}>
|
||||
<Trash className="h-4 w-4 mr-2" />
|
||||
删除
|
||||
</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 样式定制
|
||||
|
||||
### 自定义按钮
|
||||
|
||||
```tsx
|
||||
// ✅ 使用 className 扩展样式
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-2 border-primary hover:bg-primary/10"
|
||||
>
|
||||
自定义样式
|
||||
</Button>
|
||||
|
||||
// ✅ 使用 CSS 变量
|
||||
<Button
|
||||
style={{
|
||||
'--button-bg': 'hsl(var(--primary))',
|
||||
'--button-fg': 'hsl(var(--primary-foreground))'
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
使用变量
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 按钮组
|
||||
|
||||
```tsx
|
||||
// ✅ 使用 flex 布局
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline">选项1</Button>
|
||||
<Button variant="outline">选项2</Button>
|
||||
<Button variant="outline">选项3</Button>
|
||||
</div>
|
||||
|
||||
// ✅ 使用 ButtonGroup(如果有)
|
||||
<ButtonGroup>
|
||||
<Button>选项1</Button>
|
||||
<Button>选项2</Button>
|
||||
<Button>选项3</Button>
|
||||
</ButtonGroup>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 代码示例
|
||||
|
||||
### 完整示例:成员编辑页面
|
||||
|
||||
```tsx
|
||||
export default function MemberEditPage() {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { showConfirm } = useDialog()
|
||||
const router = useRouter()
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
await saveMember(data)
|
||||
toast.success('保存成功')
|
||||
router.push(`/members/${id}`)
|
||||
} catch (error) {
|
||||
toast.error('保存失败')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
const confirmed = await showConfirm(
|
||||
'确定要删除这个成员吗?',
|
||||
'删除确认',
|
||||
'destructive'
|
||||
)
|
||||
|
||||
if (!confirmed) return
|
||||
|
||||
try {
|
||||
await deleteMember(id)
|
||||
toast.success('删除成功')
|
||||
router.push('/members')
|
||||
} catch (error) {
|
||||
toast.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 顶部操作栏 */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
返回
|
||||
</Button>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => window.print()}
|
||||
>
|
||||
<Printer className="h-4 w-4 mr-2" />
|
||||
打印
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<Trash className="h-4 w-4 mr-2" />
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 表单内容 */}
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleSave(); }}>
|
||||
{/* ... 表单字段 ... */}
|
||||
|
||||
{/* 底部按钮 */}
|
||||
<div className="flex justify-end gap-2 mt-6">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => router.back()}
|
||||
disabled={isLoading}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
{isLoading ? '保存中...' : '保存'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 检查清单
|
||||
|
||||
### 新增按钮前的检查
|
||||
|
||||
- [ ] 确定按钮的主要用途
|
||||
- [ ] 选择合适的 variant
|
||||
- [ ] 选择合适的 size
|
||||
- [ ] 添加合适的图标(如需要)
|
||||
- [ ] 考虑权限控制
|
||||
- [ ] 添加加载状态
|
||||
- [ ] 添加错误处理
|
||||
- [ ] 危险操作添加确认
|
||||
- [ ] 添加无障碍属性
|
||||
- [ ] 测试响应式布局
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关资源
|
||||
|
||||
- [shadcn/ui Button 文档](https://ui.shadcn.com/docs/components/button)
|
||||
- [Lucide Icons](https://lucide.dev/)
|
||||
- [Radix UI Primitives](https://www.radix-ui.com/)
|
||||
- [Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/WAI/WCAG21/quickref/)
|
||||
Reference in New Issue
Block a user