# 🎨 按钮使用规范指南
## 📋 目录
- [按钮样式规范](#按钮样式规范)
- [按钮尺寸规范](#按钮尺寸规范)
- [图标使用规范](#图标使用规范)
- [交互状态规范](#交互状态规范)
- [权限控制规范](#权限控制规范)
- [最佳实践](#最佳实践)
---
## 按钮样式规范
### 样式类型
```typescript
type ButtonVariant =
| "default" // 默认样式
| "destructive" // 危险操作
| "outline" // 边框样式
| "secondary" // 次要操作
| "ghost" // 幽灵按钮
| "link" // 链接样式
```
### 使用场景
#### 1. Primary (default)
**用途**: 页面主要操作,每个页面最多1-2个
```tsx
// ✅ 正确使用
// ❌ 错误使用 - 不要在一个区域放置多个 primary 按钮
```
**视觉特征**:
- 背景色: `bg-primary`
- 文字色: `text-primary-foreground`
- 高对比度,最显眼
#### 2. Destructive
**用途**: 危险操作,如删除、清空
```tsx
// ✅ 正确使用
// ✅ 必须配合确认对话框
const handleDelete = async () => {
const confirmed = await showConfirm('确定要删除吗?')
if (confirmed) {
// 执行删除
}
}
```
**视觉特征**:
- 背景色: `bg-destructive` (红色)
- 文字色: `text-destructive-foreground`
- 警示作用
#### 3. Outline
**用途**: 次要操作,常规功能
```tsx
// ✅ 正确使用
```
**视觉特征**:
- 边框: `border border-input`
- 背景: 透明
- 文字色: `text-foreground`
#### 4. Ghost
**用途**: 辅助操作,不抢眼的功能
```tsx
// ✅ 正确使用
```
**视觉特征**:
- 无边框,无背景
- hover 时显示背景
- 最不显眼
#### 5. Secondary
**用途**: 次要强调操作
```tsx
// ✅ 正确使用
```
**视觉特征**:
- 背景色: `bg-secondary`
- 介于 primary 和 outline 之间
#### 6. Link
**用途**: 链接样式的按钮
```tsx
// ✅ 正确使用
```
**视觉特征**:
- 无背景,无边框
- 文字带下划线
- 类似超链接
---
## 按钮尺寸规范
### 尺寸类型
```typescript
type ButtonSize =
| "default" // 默认尺寸
| "sm" // 小尺寸
| "lg" // 大尺寸
| "icon" // 图标按钮
```
### 使用场景
#### 1. Default
**用途**: 常规按钮
```tsx
```
**尺寸**: `h-10 px-4 py-2`
#### 2. Small (sm)
**用途**: 紧凑布局、辅助操作
```tsx
```
**尺寸**: `h-9 px-3`
#### 3. Large (lg)
**用途**: 重要操作、首屏按钮
```tsx
```
**尺寸**: `h-11 px-8`
#### 4. Icon
**用途**: 仅图标按钮
```tsx
```
**尺寸**: `h-10 w-10` (正方形)
---
## 图标使用规范
### 图标位置
#### 1. 左侧图标
**用途**: 强调操作类型
```tsx
// ✅ 正确使用
```
#### 2. 右侧图标
**用途**: 表示方向或展开
```tsx
// ✅ 正确使用
```
#### 3. 仅图标
**用途**: 空间受限或辅助操作
```tsx
// ✅ 正确使用
// ⚠️ 注意:必须添加 title 属性用于无障碍访问
```
### 图标尺寸
| 按钮尺寸 | 图标尺寸 | 类名 |
|---------|---------|------|
| sm | 3 | `h-3 w-3` |
| default | 4 | `h-4 w-4` |
| lg | 5 | `h-5 w-5` |
### 图标间距
```tsx
// 左侧图标
// 右侧图标
// 仅图标(无间距)
```
---
## 交互状态规范
### 状态类型
#### 1. 默认状态
```tsx
```
#### 2. Hover 状态
```tsx
// 自动处理,无需额外代码
// primary: hover:bg-primary/90
// outline: hover:bg-accent
// ghost: hover:bg-accent hover:text-accent-foreground
```
#### 3. 禁用状态
```tsx
// ✅ 正确使用
// ✅ 配合加载状态
```
**视觉特征**:
- 透明度: `opacity-50`
- 鼠标: `cursor-not-allowed`
- 禁用点击事件
#### 4. 加载状态
```tsx
// ✅ 推荐方式
import { Loader2 } from "lucide-react"
```
#### 5. 激活状态
```tsx
// ✅ 用于切换按钮
```
---
## 权限控制规范
### 权限级别
```typescript
type Role = "OWNER" | "EDITOR" | "VIEWER"
```
### 实现方式
#### 1. 条件渲染
```tsx
// ✅ 推荐:完全隐藏按钮
{(role === "OWNER" || role === "EDITOR") && (
)}
```
#### 2. 禁用状态
```tsx
// ⚠️ 可选:显示但禁用
```
### 权限矩阵
| 操作 | VIEWER | EDITOR | OWNER |
|-----|--------|--------|-------|
| 查看 | ✅ | ✅ | ✅ |
| 新增 | ❌ | ✅ | ✅ |
| 编辑 | ❌ | ✅ | ✅ |
| 删除 | ❌ | ✅ | ✅ |
| 管理协作者 | ❌ | ❌ | ✅ |
| 删除家族树 | ❌ | ❌ | ✅ |
---
## 最佳实践
### 1. 按钮组合
#### ✅ 正确的按钮组合
```tsx
// 主次分明
```
#### ❌ 错误的按钮组合
```tsx
// 不要使用多个 primary 按钮
```
### 2. 按钮位置
#### 表单按钮
```tsx
// ✅ 正确:右对齐,取消在左,确认在右
```
#### 对话框按钮
```tsx
// ✅ 正确:底部右对齐
```
### 3. 响应式设计
```tsx
// ✅ 移动端优化
// ✅ 图标按钮在移动端隐藏文字
```
### 4. 无障碍访问
```tsx
// ✅ 正确:添加 aria 属性
// ✅ 正确:键盘支持
```
### 5. 加载和错误处理
```tsx
// ✅ 完整的状态处理
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
const handleSubmit = async () => {
setIsLoading(true)
setError(null)
try {
await saveData()
toast.success('保存成功')
} catch (err) {
setError('保存失败')
toast.error('保存失败')
} finally {
setIsLoading(false)
}
}
return (
<>
{error && {error}
}
>
)
```
### 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('删除失败')
}
}
```
---
## 🎨 样式定制
### 自定义按钮
```tsx
// ✅ 使用 className 扩展样式
// ✅ 使用 CSS 变量
```
### 按钮组
```tsx
// ✅ 使用 flex 布局
// ✅ 使用 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 (
{/* 顶部操作栏 */}
{/* 表单内容 */}
)
}
```
---
## 🔍 检查清单
### 新增按钮前的检查
- [ ] 确定按钮的主要用途
- [ ] 选择合适的 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/)