88 lines
2.0 KiB
TypeScript
88 lines
2.0 KiB
TypeScript
// frontend\src\components\ui\LoadingSpinner.tsx
|
|
|
|
'use client'
|
|
|
|
import * as React from 'react'
|
|
|
|
type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' | number
|
|
|
|
export type LoadingSpinnerProps = {
|
|
/** Größe als Preset oder px (number) */
|
|
size?: SpinnerSize
|
|
/** Farbe via Tailwind (z.B. "text-indigo-500") */
|
|
className?: string
|
|
/** Optionaler Text neben dem Spinner (sichtbar) */
|
|
label?: React.ReactNode
|
|
/** Screenreader-Text (wenn label nicht gesetzt ist) */
|
|
srLabel?: string
|
|
/** Zentriert Spinner + Label als Inline-Flex */
|
|
center?: boolean
|
|
}
|
|
|
|
function sizeToPx(size: SpinnerSize): number {
|
|
if (typeof size === 'number') return size
|
|
if (size === 'xs') return 12
|
|
if (size === 'sm') return 16
|
|
if (size === 'lg') return 28
|
|
return 20 // md default
|
|
}
|
|
|
|
function cn(...parts: Array<string | false | null | undefined>) {
|
|
return parts.filter(Boolean).join(' ')
|
|
}
|
|
|
|
export default function LoadingSpinner({
|
|
size = 'md',
|
|
className,
|
|
label,
|
|
srLabel = 'Lädt…',
|
|
center = false,
|
|
}: LoadingSpinnerProps) {
|
|
const px = sizeToPx(size)
|
|
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center gap-2',
|
|
center && 'justify-center w-full',
|
|
)}
|
|
role="status"
|
|
aria-live="polite"
|
|
>
|
|
<svg
|
|
width={px}
|
|
height={px}
|
|
viewBox="0 0 24 24"
|
|
className={cn('animate-spin text-gray-500', className)}
|
|
aria-hidden="true"
|
|
>
|
|
{/* Hintergrund-Ring (leicht transparent) */}
|
|
<circle
|
|
cx="12"
|
|
cy="12"
|
|
r="9"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="3"
|
|
opacity="0.25"
|
|
/>
|
|
{/* “Arc” vorne */}
|
|
<path
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="3"
|
|
strokeLinecap="round"
|
|
d="M21 12a9 9 0 0 0-9-9"
|
|
opacity="0.95"
|
|
/>
|
|
</svg>
|
|
|
|
{label ? (
|
|
<span className="text-sm text-gray-700 dark:text-gray-200">{label}</span>
|
|
) : (
|
|
<span className="sr-only">{srLabel}</span>
|
|
)}
|
|
</span>
|
|
)
|
|
}
|