Files
WebDrop/web/src/features/home/ChannelList.tsx
T
justme d0aefb3d6e feat(web): split the home page into quick share and spaces zones
Two side-by-side zones (stacked on narrow screens) with an icon and a
one-line positioning each: quick share is temporary, read-only and needs
no account; spaces are long-lived, multi-user and need a login. Shorter
copy, my shares listed under the share input, and an empty state for
spaces.
2026-10-14 22:42:06 +08:00

71 lines
3.2 KiB
TypeScript

import { useState } from 'react';
import type { ChannelInfo, Share } from '@/core-bridge/types';
import { formatAgo, formatRemaining } from '@/lib/format';
import { useChannels } from '@/store/channels';
import { useShares } from '@/store/shares';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
import styles from './ChannelList.module.css';
interface Props {
kind: 'channel' | 'share';
title: string;
onOpen: (c: ChannelInfo | Share) => void;
/** 标题只给屏幕阅读器(所在分区已有标题) */
quietTitle?: boolean;
}
export function ChannelList({ kind, title, onOpen, quietTitle = false }: Props) {
const channels = useChannels((s) => s.list);
const forgetChannel = useChannels((s) => s.forget);
const shares = useShares((s) => s.list);
const forgetShare = useShares((s) => s.forget);
const list = kind === 'share' ? shares : channels;
const [confirmId, setConfirmId] = useState<string>();
if (list.length === 0) return null;
return (
<section aria-label={title}>
<h2 className={`${styles.h} ${quietTitle ? styles.quiet : ''}`}>{title}</h2>
<ul className={styles.list}>
{list.map((c) => (
<li key={c.id} className={styles.row}>
<button type="button" className={styles.open} onClick={() => onOpen(c)}>
<span className={styles.id}>{'state' in c ? c.id.slice(0, 8) : c.name || c.id.slice(0, 8)}…</span>
<span className={styles.meta}>
{c.dataSource === 'cache' ? <span>离线缓存</span> : null}
{'state' in c ? (
<>
<span className={styles.enc}><Icon name="lock" size={12} /> 加密</span>
<span className={c.state === 'sealed' ? styles.enc : styles.raw}>{c.state === 'sealed' ? '已封存' : '草稿'}</span>
<span>{c.mine ? '我创建的' : '收到的'}</span>
<span>{formatRemaining(c.expiresAt)}</span>
</>
) : (
<>
{c.name ? <span className={styles.channelId}>{c.id.slice(0, 8)}…</span> : null}
<span className={c.mode === 'enc' ? styles.enc : styles.raw}>
{c.mode === 'enc' ? <><Icon name="lock" size={12} /> 加密</> : '明文'}
</span>
<span>{c.role === 'owner' ? '所有者' : '成员'}</span>
<span>最多 {c.maxMembers} 人</span>
<span>{formatAgo(c.createdAt)}创建</span>
</>
)}
</span>
</button>
{confirmId === c.id ? (
<span className={styles.confirm}>
<Button variant="ghost" onClick={() => { (kind === 'share' ? forgetShare : forgetChannel)(c.id); setConfirmId(undefined); }}>确认移除</Button>
<Button variant="ghost" onClick={() => setConfirmId(undefined)}>取消</Button>
</span>
) : (
<Button variant="ghost" icon="trash" label="从当前资源清单移除(不会删除服务端内容)" onClick={() => setConfirmId(c.id)} />
)}
</li>
))}
</ul>
</section>
);
}