# add-ui-primitive > 为 MediMind 添加基础 UI 原子组件(Button, Input, Card等) - Author: Perlou - Repository: Perlou/llm-course - Version: 20260205232402 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/Perlou/llm-course - Web: https://mule.run/skillshub/@@Perlou/llm-course~add-ui-primitive:20260205232402 --- --- name: add-ui-primitive description: 为 MediMind 添加基础 UI 原子组件(Button, Input, Card等) --- # 添加 UI 原子组件技能 此技能用于在 MediMind 的 `@medimind/ui` 包中添加基础原子组件。 ## 原子组件目录 ``` frontend/packages/ui/src/primitives/ ├── index.ts # 导出入口 ├── Button.tsx # 按钮 ├── Input.tsx # 输入框 ├── Card.tsx # 卡片 ├── Badge.tsx # 徽章 ├── Spinner.tsx # 加载动画 ├── Avatar.tsx # 头像 ├── Modal.tsx # 模态框 └── Tooltip.tsx # 工具提示 ``` ## 组件模板 ### Button - 按钮 ```tsx // src/primitives/Button.tsx import React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../utils"; import { Loader2 } from "lucide-react"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 rounded-lg font-medium transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed", { variants: { variant: { primary: "bg-teal-600 text-white hover:bg-teal-700 focus:ring-teal-500", secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500", outline: "border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus:ring-gray-500", ghost: "text-gray-700 hover:bg-gray-100 focus:ring-gray-500", danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500", }, size: { sm: "h-8 px-3 text-sm", md: "h-10 px-4 text-base", lg: "h-12 px-6 text-lg", icon: "h-10 w-10", }, }, defaultVariants: { variant: "primary", size: "md", }, }, ); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { /** 加载状态 */ loading?: boolean; } export function Button({ className, variant, size, loading, disabled, children, ...props }: ButtonProps) { return ( ); } ``` ### Input - 输入框 ```tsx // src/primitives/Input.tsx import React from "react"; import { cn } from "../utils"; export interface InputProps extends React.InputHTMLAttributes { /** 错误状态 */ error?: boolean; /** 错误信息 */ errorMessage?: string; /** 左侧图标 */ leftIcon?: React.ReactNode; /** 右侧图标 */ rightIcon?: React.ReactNode; } export const Input = React.forwardRef( ({ className, error, errorMessage, leftIcon, rightIcon, ...props }, ref) => { return (
{leftIcon && (
{leftIcon}
)} {rightIcon && (
{rightIcon}
)}
{errorMessage && (

{errorMessage}

)}
); }, ); Input.displayName = "Input"; ``` ### Card - 卡片 ```tsx // src/primitives/Card.tsx import React from "react"; import { cn } from "../utils"; export interface CardProps extends React.HTMLAttributes { /** 变体 */ variant?: "default" | "bordered" | "elevated"; /** 是否可点击 */ clickable?: boolean; } export function Card({ className, variant = "default", clickable, children, ...props }: CardProps) { const variantStyles = { default: "bg-white border border-gray-200", bordered: "bg-white border-2 border-gray-300", elevated: "bg-white shadow-lg", }; return (
{children}
); } // 子组件 Card.Header = function CardHeader({ className, children, ...props }: React.HTMLAttributes) { return (
{children}
); }; Card.Body = function CardBody({ className, children, ...props }: React.HTMLAttributes) { return (
{children}
); }; Card.Footer = function CardFooter({ className, children, ...props }: React.HTMLAttributes) { return (
{children}
); }; ``` ### Badge - 徽章 ```tsx // src/primitives/Badge.tsx import React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../utils"; const badgeVariants = cva( "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium", { variants: { variant: { default: "bg-gray-100 text-gray-800", primary: "bg-teal-100 text-teal-800", success: "bg-green-100 text-green-800", warning: "bg-yellow-100 text-yellow-800", danger: "bg-red-100 text-red-800", }, }, defaultVariants: { variant: "default", }, }, ); export interface BadgeProps extends React.HTMLAttributes, VariantProps {} export function Badge({ className, variant, ...props }: BadgeProps) { return ( ); } ``` ### Spinner - 加载动画 ```tsx // src/primitives/Spinner.tsx import React from "react"; import { cn } from "../utils"; export interface SpinnerProps { /** 尺寸 */ size?: "sm" | "md" | "lg"; /** 颜色 */ color?: "primary" | "white" | "gray"; /** 自定义类名 */ className?: string; } export function Spinner({ size = "md", color = "primary", className, }: SpinnerProps) { const sizeStyles = { sm: "w-4 h-4 border-2", md: "w-6 h-6 border-2", lg: "w-8 h-8 border-3", }; const colorStyles = { primary: "border-teal-600 border-t-transparent", white: "border-white border-t-transparent", gray: "border-gray-400 border-t-transparent", }; return (
); } ``` ### Textarea - 文本域 ```tsx // src/primitives/Textarea.tsx import React from "react"; import { cn } from "../utils"; export interface TextareaProps extends React.TextareaHTMLAttributes { /** 错误状态 */ error?: boolean; /** 错误信息 */ errorMessage?: string; } export const Textarea = React.forwardRef( ({ className, error, errorMessage, ...props }, ref) => { return (