Files
vyndr/web/src/components/Skeleton.tsx
T

97 lines
2.8 KiB
TypeScript

import type { CSSProperties } from 'react';
type Size = number | string;
const pulseStyle = (extra: CSSProperties = {}): CSSProperties => ({
background: 'var(--bg-2)',
animation: 'skeleton-pulse 1.5s ease-in-out infinite',
...extra,
});
export function SkeletonBox({
width,
height,
borderRadius = 8,
style,
}: {
width?: Size;
height?: Size;
borderRadius?: number;
style?: CSSProperties;
}) {
return (
<div
aria-hidden
style={pulseStyle({
width,
height,
borderRadius,
...style,
})}
/>
);
}
export function SkeletonLine({ width = '80%', height = 14 }: { width?: Size; height?: Size }) {
return <SkeletonBox width={width} height={height} borderRadius={4} style={{ marginBottom: 8 }} />;
}
export function SkeletonGradeCard() {
return (
<div className="surface" style={{ padding: 16, display: 'grid', gap: 12 }} aria-label="Loading grade card" role="status">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<SkeletonBox width={60} height={20} borderRadius={999} />
<SkeletonBox width={36} height={36} borderRadius={999} />
</div>
<SkeletonBox width="70%" height={22} borderRadius={6} />
<SkeletonBox width="45%" height={14} borderRadius={4} />
<div style={{ display: 'flex', justifyContent: 'center', padding: '8px 0' }}>
<SkeletonBox width={72} height={72} borderRadius={12} />
</div>
<SkeletonLine width="90%" />
<SkeletonLine width="80%" />
<SkeletonLine width="75%" />
</div>
);
}
export function SkeletonGameCard() {
return (
<div
className="surface"
style={{ padding: 16, display: 'flex', gap: 12, alignItems: 'stretch' }}
aria-label="Loading game card"
role="status"
>
<SkeletonBox width={3} height={56} borderRadius={2} />
<div style={{ flex: 1, display: 'grid', gap: 8 }}>
<SkeletonBox width="55%" height={18} borderRadius={4} />
<SkeletonBox width="35%" height={14} borderRadius={4} />
</div>
<SkeletonBox width={60} height={20} borderRadius={999} />
</div>
);
}
export function SkeletonRow({ count = 3 }: { count?: number }) {
return (
<div style={{ display: 'grid', gap: 8 }} aria-label="Loading" role="status">
{Array.from({ length: count }, (_, i) => (
<SkeletonGameCard key={i} />
))}
</div>
);
}
export function SkeletonGradeRail({ count = 4 }: { count?: number }) {
return (
<div style={{ display: 'flex', gap: 12, overflowX: 'auto', padding: '4px 4px 12px' }} aria-label="Loading grade rail" role="status">
{Array.from({ length: count }, (_, i) => (
<div key={i} style={{ minWidth: 220, flex: '0 0 auto' }}>
<SkeletonGradeCard />
</div>
))}
</div>
);
}