feat: model complete async UI surface states
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/** @param {{ label?: string }} props */
|
||||
export function LoadingSurface({ label = "불러오는 중" }) {
|
||||
return (
|
||||
<section aria-busy="true" aria-label={label}>
|
||||
<div className="ui-skeleton" aria-hidden="true" />
|
||||
<span className="sr-only">{label}</span>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {{ title?: string, action?: React.ReactNode }} props */
|
||||
export function EmptySurface({ title = "표시할 항목이 없습니다.", action }) {
|
||||
return (
|
||||
<section>
|
||||
<p>{title}</p>
|
||||
{action}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* userMessageKey: string,
|
||||
* action: "retry" | "reauth" | "navigate" | "reload-once" |
|
||||
* "contact-support" | "none",
|
||||
* onAction?: () => void
|
||||
* }} props
|
||||
*/
|
||||
export function TerminalErrorSurface({ userMessageKey, action, onAction }) {
|
||||
return (
|
||||
<section role="alert" aria-labelledby="terminal-error-message">
|
||||
<p id="terminal-error-message">{userMessageKey}</p>
|
||||
{action !== "none" && (
|
||||
<button type="button" onClick={onAction}>
|
||||
{action}
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* state: ReturnType<typeof import("../../application/view-models/async-state.js").deriveAsyncState>,
|
||||
* children?: React.ReactNode,
|
||||
* onAction?: () => void
|
||||
* }} props
|
||||
*/
|
||||
export function AsyncSurface({ state, children, onAction }) {
|
||||
if (state.base === "initial-loading") return <LoadingSurface />;
|
||||
if (state.base === "empty") return <EmptySurface />;
|
||||
if (state.base === "terminal-error" && state.failure) {
|
||||
return (
|
||||
<TerminalErrorSurface
|
||||
userMessageKey={state.failure.userMessageKey}
|
||||
action={state.failure.action}
|
||||
onAction={onAction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section aria-busy={state.overlay.refreshing || state.overlay.mutationPending}>
|
||||
{state.indicator && (
|
||||
<p role="status" aria-live="polite">
|
||||
{state.indicator}
|
||||
</p>
|
||||
)}
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user