Files
WebDrop/web/src/app/ChannelSkeleton.tsx
T
justme 22f272e361 feat(web): show a skeleton while a space loads
Draw the page frame instead of a blank screen, fade it in only after 300ms,
and surface load errors with retry and back-to-home actions.
2026-10-16 05:44:58 +08:00

32 lines
1.9 KiB
TypeScript

import { useEffect } from 'react';
import { Button } from '@/ui/Button';
import styles from './ChannelSkeleton.module.css';
const ROWS = [72, 48, 88, 36, 64];
/** 频道加载中:先画出页面框架(Apple HIG「别留白屏」),内容到了再替换。超过约 300ms 才可见,避免快的时候闪一下 */
export function ChannelSkeleton({ error, onRetry, onHome }: { error?: string; onRetry: () => void; onHome: () => void }) {
// 与手机聊天页一致:账号入口不浮在骨架屏上
useEffect(() => {
document.documentElement.dataset.chatShell = '';
return () => { delete document.documentElement.dataset.chatShell; };
}, []);
return <div className={styles.page} role="status" aria-label={error ? '频道加载失败' : '正在加载频道'} aria-busy={!error}>
<header className={styles.bar}><span className={`${styles.block} ${styles.title}`} /></header>
<aside className={styles.rail}><span className={`${styles.block} ${styles.tile}`} /></aside>
<aside className={styles.nav}>{[56, 40, 48].map((w) => <span key={w} className={styles.block} style={{ width: `${w}%` }} />)}</aside>
<main className={styles.chat}>
{ROWS.map((w, i) => <div key={i} className={styles.message}>
<span className={`${styles.block} ${styles.avatar}`} />
<div className={styles.lines}><span className={`${styles.block} ${styles.name}`} /><span className={styles.block} style={{ width: `${w}%` }} /></div>
</div>)}
<div className={`${styles.block} ${styles.composer}`} />
</main>
<aside className={styles.files}>{[80, 60, 70, 50].map((w) => <span key={w} className={styles.block} style={{ width: `${w}%` }} />)}</aside>
{error ? <div className={styles.error} role="alert">
<p>频道没能加载:{error}</p>
<div><Button variant="primary" onClick={onRetry}>重试</Button><Button variant="ghost" onClick={onHome}>回到首页</Button></div>
</div> : null}
</div>;
}