97 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|