feat: provide reusable UI and state galleries
This commit is contained in:
@@ -1,24 +1,98 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { deriveAsyncState } from "../../application/view-models/async-state.js";
|
||||
import { createFailure } from "../../contracts/errors.js";
|
||||
import {
|
||||
AsyncSurface,
|
||||
EmptySurface,
|
||||
LoadingSurface,
|
||||
TerminalErrorSurface,
|
||||
} from "../components/async-surface.jsx";
|
||||
import { PageHeader } from "../components/page-header.jsx";
|
||||
import {
|
||||
AuthRequiredSurface,
|
||||
ForbiddenSurface,
|
||||
NotFoundSurface,
|
||||
} from "../components/state-surfaces.jsx";
|
||||
import { Button } from "../components/ui/button.jsx";
|
||||
import { Card } from "../components/ui/card.jsx";
|
||||
|
||||
export default function StateGalleryPage() {
|
||||
const [lastAction, setLastAction] = useState(
|
||||
"상태 화면의 작업을 선택하면 결과가 여기에 표시됩니다.",
|
||||
);
|
||||
const refreshingState = deriveAsyncState({
|
||||
data: ["기존 데이터"],
|
||||
isFetching: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="ui-page">
|
||||
<PageHeader
|
||||
eyebrow="예제"
|
||||
title="화면 상태"
|
||||
description="로딩, 빈 화면, 오류, 인증 필요와 권한 없음 상태의 기본 표현을 확인합니다."
|
||||
description="로딩, 빈 화면, 오류, 인증 필요와 권한 없음 상태가 다음 행동까지 일관되게 안내합니다."
|
||||
/>
|
||||
<div className="readiness-grid">
|
||||
<section className="ui-skeleton" aria-label="로딩 상태 예제" />
|
||||
<section className="ui-empty">
|
||||
<h2>표시할 항목이 없습니다.</h2>
|
||||
<p>다음 행동이 있다면 이 위치에 명확한 안내를 제공합니다.</p>
|
||||
</section>
|
||||
<section className="ui-terminal-error" role="alert">
|
||||
<h2>요청을 완료하지 못했습니다.</h2>
|
||||
<p>안전한 재시도 또는 지원 참조 정보를 제공합니다.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="gallery-section" aria-labelledby="async-states-title">
|
||||
<header className="gallery-section__header">
|
||||
<h2 id="async-states-title">비동기 데이터 상태</h2>
|
||||
<p>초기 로딩과 백그라운드 갱신을 구분해 기존 콘텐츠를 보존합니다.</p>
|
||||
</header>
|
||||
<div className="component-grid component-grid--two">
|
||||
<Card title="초기 로딩">
|
||||
<LoadingSurface label="예제 데이터를 불러오는 중" />
|
||||
</Card>
|
||||
<Card title="백그라운드 갱신">
|
||||
<AsyncSurface state={refreshingState}>
|
||||
<div className="state-preview-content">기존 콘텐츠는 계속 표시됩니다.</div>
|
||||
</AsyncSurface>
|
||||
</Card>
|
||||
<Card title="빈 화면">
|
||||
<EmptySurface
|
||||
title="아직 표시할 항목이 없습니다."
|
||||
description="첫 항목을 추가하거나 필터를 초기화할 수 있습니다."
|
||||
action={
|
||||
<Button onClick={() => setLastAction("빈 화면 작업을 실행했습니다.")}>
|
||||
첫 작업 시작
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<Card title="복구 가능한 오류">
|
||||
<TerminalErrorSurface
|
||||
userMessageKey={
|
||||
createFailure("NETWORK_UNREACHABLE", "EXAMPLE", 0)
|
||||
.userMessageKey
|
||||
}
|
||||
action="retry"
|
||||
onAction={() => setLastAction("오류 요청을 다시 시도했습니다.")}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="gallery-section" aria-labelledby="access-states-title">
|
||||
<header className="gallery-section__header">
|
||||
<h2 id="access-states-title">접근과 탐색 상태</h2>
|
||||
<p>인증 여부와 서버 권한 결과를 서로 다른 상태로 전달합니다.</p>
|
||||
</header>
|
||||
<div className="component-grid component-grid--three">
|
||||
<AuthRequiredSurface
|
||||
onSignIn={() => setLastAction("로그인 연동 작업을 시작했습니다.")}
|
||||
/>
|
||||
<ForbiddenSurface
|
||||
onNavigate={() => setLastAction("접근 가능한 화면으로 이동합니다.")}
|
||||
/>
|
||||
<NotFoundSurface
|
||||
onNavigate={() => setLastAction("시작 화면으로 이동합니다.")}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<output className="gallery-notice" aria-live="polite">
|
||||
{lastAction}
|
||||
</output>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user