110 lines
2.6 KiB
TypeScript
110 lines
2.6 KiB
TypeScript
|
|
"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>
|
||
|
|
);
|
||
|
|
}
|