| 1 | /** |
| 2 | * Spin - 加载中组件 |
| 3 | * 用于显示加载状态 |
| 4 | */ |
| 5 | |
| 6 | 'use client' |
| 7 | |
| 8 | import { cn } from '@/utils/className' |
| 9 | |
| 10 | interface SpinProps { |
| 11 | /** 是否显示加载状态 */ |
| 12 | spinning?: boolean |
| 13 | /** 子元素 */ |
| 14 | children?: React.ReactNode |
| 15 | /** 自定义类名 */ |
| 16 | className?: string |
| 17 | /** 提示文字 */ |
| 18 | tip?: string |
| 19 | } |
| 20 | |
| 21 | export function Spin({ spinning = false, children, className, tip }: SpinProps) { |
| 22 | if (!spinning && !children) { |
| 23 | return null |
| 24 | } |
| 25 | |
| 26 | const spinner = ( |
| 27 | <div className="flex flex-col items-center justify-center gap-2"> |
| 28 | <svg |
| 29 | className="animate-spin h-5 w-5 text-foreground" |
| 30 | xmlns="http://www.w3.org/2000/svg" |
| 31 | fill="none" |
| 32 | viewBox="0 0 24 24" |
| 33 | > |
| 34 | <circle |
| 35 | className="opacity-25" |
| 36 | cx="12" |
| 37 | cy="12" |
| 38 | r="10" |
| 39 | stroke="currentColor" |
| 40 | strokeWidth="4" |
| 41 | /> |
| 42 | <path |
| 43 | className="opacity-75" |
| 44 | fill="currentColor" |
| 45 | d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" |
| 46 | /> |
| 47 | </svg> |
| 48 | {tip && <span className="text-sm text-muted-foreground">{tip}</span>} |
| 49 | </div> |
| 50 | ) |
| 51 | |
| 52 | if (!children) { |
| 53 | return <div className={cn('flex items-center justify-center p-4', className)}>{spinner}</div> |
| 54 | } |
| 55 | |
| 56 | return ( |
| 57 | <div className={cn('relative', className)}> |
| 58 | {spinning && ( |
| 59 | <div className="absolute inset-0 z-10 flex items-center justify-center bg-background/50 backdrop-blur-sm rounded"> |
| 60 | {spinner} |
| 61 | </div> |
| 62 | )} |
| 63 | {children} |
| 64 | </div> |
| 65 | ) |
| 66 | } |
| 67 |