Files
modern-sk/src/components/spinner/index.tsx
T
2026-06-01 01:09:55 +03:00

75 lines
2.3 KiB
TypeScript

import { useId, type ComponentPropsWithoutRef } from 'react';
import { cx } from '../utils';
export const Spinner = ({
size,
className,
...props
}: ComponentPropsWithoutRef<'span'> & { size?: 'sm' | 'lg' }) => {
const uid = useId();
const grooveId = `modern-sk-groove-${uid}`;
const glowId = `modern-sk-glow-${uid}`;
return (
<span
role="status"
aria-label="Loading"
className={cx('modern-sk-spinner', size && `modern-sk-spinner--${size}`, className)}
{...props}
>
<svg viewBox="0 0 36 36" fill="none">
<defs>
{/* Carved channel: flat ring sunk by a top inner shadow — like the switch well. */}
<filter id={grooveId} x="-30%" y="-30%" width="160%" height="160%">
<feComponentTransfer in="SourceAlpha" result="inv">
<feFuncA type="table" tableValues="1 0" />
</feComponentTransfer>
<feGaussianBlur in="inv" stdDeviation="1" result="blur" />
<feOffset in="blur" dy="1" result="off" />
<feFlood floodColor="#000" floodOpacity="0.7" />
<feComposite in2="off" operator="in" />
<feComposite in2="SourceAlpha" operator="in" result="shadow" />
<feMerge>
<feMergeNode in="SourceGraphic" />
<feMergeNode in="shadow" />
</feMerge>
</filter>
{/* Soft round glow — generous region so it never clips to a square. */}
<filter id={glowId} x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="1.6" />
</filter>
</defs>
<circle
cx="18"
cy="18"
r="14"
stroke="var(--spin-track)"
strokeWidth="5"
filter={`url(#${grooveId})`}
/>
<g className="modern-sk-spinner__arc">
<circle
cx="18"
cy="18"
r="14"
stroke="var(--lime)"
strokeWidth="4"
strokeLinecap="round"
strokeDasharray="22 88"
opacity="0.8"
filter={`url(#${glowId})`}
/>
<circle
cx="18"
cy="18"
r="14"
stroke="var(--lime)"
strokeWidth="3"
strokeLinecap="round"
strokeDasharray="22 88"
/>
</g>
</svg>
</span>
);
};