# 🎨 按钮使用规范指南 ## 📋 目录 - [按钮样式规范](#按钮样式规范) - [按钮尺寸规范](#按钮尺寸规范) - [图标使用规范](#图标使用规范) - [交互状态规范](#交互状态规范) - [权限控制规范](#权限控制规范) - [最佳实践](#最佳实践) --- ## 按钮样式规范 ### 样式类型 ```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 (
{/* 顶部操作栏 */}
{/* 表单内容 */}
{ e.preventDefault(); handleSave(); }}> {/* ... 表单字段 ... */} {/* 底部按钮 */}
) } ``` --- ## 🔍 检查清单 ### 新增按钮前的检查 - [ ] 确定按钮的主要用途 - [ ] 选择合适的 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/)