Files
auto-trade/components/ui/loading-spinner.tsx

110 lines
2.6 KiB
TypeScript
Raw Normal View History

"use client";
import { cn } from "@/lib/utils";
/**
* [ ]
*
* .
* - (sm, md, lg)
* -
* -
*
* @example
* // 기본 사용
* <LoadingSpinner />
*
* @example
* // 크기 및 텍스트 지정
* <LoadingSpinner size="lg" text="로딩 중..." />
*
* @example
* // 버튼 내부에서 사용
* <Button disabled={isLoading}>
* {isLoading ? <LoadingSpinner size="sm" /> : "제출"}
* </Button>
*/
interface LoadingSpinnerProps {
/** 스피너 크기 */
size?: "sm" | "md" | "lg";
/** 스피너와 함께 표시할 텍스트 */
text?: string;
/** 추가 CSS 클래스 */
className?: string;
/** 스피너 색상 (Tailwind 클래스) */
color?: string;
}
export function LoadingSpinner({
size = "md",
text,
className,
color = "border-gray-900 dark:border-white",
}: LoadingSpinnerProps) {
// 크기별 스타일 매핑
const sizeClasses = {
sm: "h-4 w-4 border-2",
md: "h-8 w-8 border-3",
lg: "h-12 w-12 border-4",
};
return (
<div className={cn("flex items-center justify-center gap-2", className)}>
{/* ========== 회전 스피너 ========== */}
<div
className={cn(
"animate-spin rounded-full border-solid border-t-transparent",
sizeClasses[size],
color,
)}
role="status"
aria-label="로딩 중"
/>
{/* ========== 로딩 텍스트 (선택적) ========== */}
{text && (
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
{text}
</span>
)}
</div>
);
}
/**
* [ ]
*
* .
*
* @example
* <Button disabled={isLoading}>
* {isLoading && <InlineSpinner />}
*
* </Button>
*/
export function InlineSpinner({ className }: { className?: string }) {
return (
<svg
className={cn("h-4 w-4 animate-spin", className)}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
aria-label="로딩 중"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
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"
/>
</svg>
);
}