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.
32 lines
1.9 KiB
TypeScript
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>;
|
|
}
|