@olundot/ui · Feedback
Skeleton
콘텐츠가 도착하기 전 실제 레이아웃과 비슷한 자리 표시를 제공하는 로딩 프리뷰입니다.
- 구성
- Skeleton, SkeletonText, SkeletonRow를 상황별로 조합합니다.
- 목적
- 스피너보다 레이아웃 안정성이 중요한 카드, 텍스트, 표 로딩에 사용합니다.
- 접근성
- 대체되는 영역에 role=status, aria-busy, aria-label을 함께 설계합니다.
- 모션
- pulse 애니메이션은 reduced motion에서 제거됩니다.
블록
카드나 그래프처럼 면적이 있는 콘텐츠 자리 표시코드 보기tsx
import { Skeleton } from "@olundot/ui";
export function SkeletonBlock() {
return (
<Skeleton
aria-label="요약 카드 로드 중"
style={{ width: "100%", height: 128 }}
/>
);
}텍스트 줄
문단 길이를 예측 가능한 줄 단위로 표시코드 보기tsx
import { SkeletonText } from "@olundot/ui";
export function SkeletonTextLines() {
return <SkeletonText lines={4} widths={["92%", "78%", "86%", "54%"]} />;
}행과 표
표와 목록의 열 비율을 유지하며 로딩 상태 표시코드 보기tsx
import { SkeletonRow } from "@olundot/ui";
export function SkeletonRowTable() {
return (
<SkeletonRow surface="canvas"
aria-label="응시자 목록 로드 중"
count={5}
columns={[
{ width: "2fr" },
{ width: "1fr", align: "center" },
{ width: "1fr", align: "right" },
]}
/>
);
}접근 가능한 로딩 대체
실제 콘텐츠 영역을 aria-busy 상태와 함께 대체코드 보기tsx
import { useState } from "react";
import { Button, SkeletonText } from "@olundot/ui";
export function SkeletonAccessibleReplacement() {
const [loading, setLoading] = useState(true);
return (
<div style={{ display: "grid", gap: "12px" }}>
<Button size="sm" variant="outline" onClick={() => setLoading((next) => !next)}>
{loading ? "콘텐츠 표시" : "다시 로딩"}
</Button>
{loading ? (
<section role="status" aria-busy="true" aria-label="학생 요약 로드 중">
<SkeletonText lines={3} widths={["88%", "72%", "42%"]} />
</section>
) : (
<section aria-label="학생 요약">
<strong>김서연</strong>
<p style={{ margin: "4px 0 0", color: "var(--text-secondary)" }}>
최근 7일 평균 정답률 82%, 복습 필요 문항 4개
</p>
</section>
)}
</div>
);
}