c5200c7d45
## 变更内容 - 新增基于 shadcn open-code 模式的共享 UI canonical 组件、样式与导出。 - 新增 `/components` 共享组件展示页,并覆盖平台组件、Token、状态和移动端布局。 - 补齐平台展示页的筛选、排序、上传预览、标签、开关和异步状态交互。 - 修复排序导致预览主题变化、筛选弹窗误关闭和入口状态不更新的问题。 - 同步网站与客户端构建别名、依赖、路由测试和项目文档。 ## 验证 - `npm run test -- src/components/shared-components/SharedComponentsShowcasePage.test.tsx` - `npm run typecheck` - `npm run check:encoding` - `npm run build:raw` - `git diff --check` - pre-push 门禁通过 Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/202 Co-authored-by: kdletters <kdletters@qq.com> Co-committed-by: kdletters <kdletters@qq.com>
810 lines
28 KiB
TypeScript
810 lines
28 KiB
TypeScript
import '@genarrative/shared/styles.css';
|
||
import './sharedComponentsShowcase.css';
|
||
|
||
import {
|
||
Badge,
|
||
Button,
|
||
Checkbox,
|
||
Divider,
|
||
EmptyState,
|
||
IconButton,
|
||
Label,
|
||
Modal,
|
||
PlatformAsyncStatePanel,
|
||
PlatformBackActionButton,
|
||
PlatformEmptyState,
|
||
PlatformFilterToolbar,
|
||
PlatformInfoBlock,
|
||
PlatformNavigableListItem,
|
||
PlatformRuntimeStatusToast,
|
||
PlatformStatGrid,
|
||
PlatformToggleRow,
|
||
ProgressBar,
|
||
SegmentedTabs,
|
||
SelectField,
|
||
Skeleton,
|
||
Spinner,
|
||
Status,
|
||
Subpanel,
|
||
Switch,
|
||
Table,
|
||
TableBody,
|
||
TableCaption,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
TextField,
|
||
} from '@genarrative/shared/components';
|
||
import { Check, CircleAlert, Info, Plus, Sparkles } from 'lucide-react';
|
||
import { type ReactNode, useEffect, useState } from 'react';
|
||
|
||
import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard';
|
||
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
|
||
import { PlatformMediaTileGrid } from '../common/PlatformMediaTileGrid';
|
||
import { PlatformTagEditor } from '../common/PlatformTagEditor';
|
||
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
|
||
import { PlatformUploadTile } from '../common/PlatformUploadTile';
|
||
|
||
const tabs = [
|
||
{ id: 'overview', label: '概览' },
|
||
{ id: 'platform', label: '平台组件' },
|
||
{ id: 'tokens', label: 'Token' },
|
||
{ id: 'states', label: '状态' },
|
||
] as const;
|
||
|
||
const demoAssetImages = [
|
||
['珊瑚岛', '#ef9b72', '#ffd7b8', '草稿', 3],
|
||
['月光林', '#8094cf', '#dce3ff', '已发布', 4],
|
||
['萤火谷', '#78b899', '#d4f3d5', '草稿', 1],
|
||
['赤岩城', '#c87967', '#f8d4c9', '已发布', 2],
|
||
] as const;
|
||
|
||
type DemoAsset = {
|
||
id: string;
|
||
label: string;
|
||
image: string;
|
||
status: '草稿' | '已发布';
|
||
recentOrder: number;
|
||
};
|
||
|
||
function createDemoImage(label: string, from: string, to: string) {
|
||
return `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 220"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop stop-color="${from}"/><stop offset="1" stop-color="${to}"/></linearGradient></defs><rect width="320" height="220" rx="28" fill="url(#g)"/><circle cx="252" cy="54" r="30" fill="white" fill-opacity=".4"/><path d="M0 180 Q80 115 160 175 T320 150 V220 H0Z" fill="white" fill-opacity=".22"/><text x="24" y="196" fill="white" font-family="sans-serif" font-size="24" font-weight="700">${label}</text></svg>`)}`;
|
||
}
|
||
|
||
const demoAssets: DemoAsset[] = demoAssetImages.map(
|
||
([label, from, to, status, recentOrder], index) => ({
|
||
id: `demo-asset-${index}`,
|
||
label,
|
||
image: createDemoImage(label, from, to),
|
||
status,
|
||
recentOrder,
|
||
}),
|
||
);
|
||
|
||
export default function SharedComponentsShowcasePage() {
|
||
const [activeTab, setActiveTab] =
|
||
useState<(typeof tabs)[number]['id']>('overview');
|
||
const [enabled, setEnabled] = useState(true);
|
||
const [checkboxEnabled, setCheckboxEnabled] = useState(true);
|
||
const [progress, setProgress] = useState(64);
|
||
const [modalOpen, setModalOpen] = useState(false);
|
||
const [feedback, setFeedback] = useState<string | null>(null);
|
||
const [filterTab, setFilterTab] = useState('全部');
|
||
const [platformTags, setPlatformTags] = useState(['冒险', '海岛']);
|
||
const [platformToggle, setPlatformToggle] = useState(true);
|
||
const [selectedAssetId, setSelectedAssetId] = useState(
|
||
demoAssets[0]?.id ?? '',
|
||
);
|
||
const [filterOpen, setFilterOpen] = useState(false);
|
||
const [sortMode, setSortMode] = useState<'recent' | 'name'>('recent');
|
||
const [asyncMode, setAsyncMode] = useState<
|
||
'content' | 'loading' | 'empty' | 'error'
|
||
>('content');
|
||
|
||
const showFeedback = (label: string) => {
|
||
setFeedback(`${label}已点击`);
|
||
};
|
||
|
||
const adjustProgress = (delta: number, label: string) => {
|
||
setProgress((value) => Math.min(100, Math.max(0, value + delta)));
|
||
showFeedback(label);
|
||
};
|
||
|
||
const visibleDemoAssets = [...demoAssets]
|
||
.filter((asset) => filterTab === '全部' || asset.status === filterTab)
|
||
.sort((left, right) =>
|
||
sortMode === 'recent'
|
||
? right.recentOrder - left.recentOrder
|
||
: left.label.localeCompare(right.label, 'zh-CN'),
|
||
);
|
||
|
||
useEffect(() => {
|
||
document.title = '共享组件展示|陶泥儿 Genarrative';
|
||
}, []);
|
||
|
||
return (
|
||
<main className="shared-components-showcase genarrative-ui platform-theme platform-theme--light">
|
||
<div className="shared-components-showcase__inner">
|
||
<header className="shared-components-showcase__header">
|
||
<div>
|
||
<Badge tone="accent" icon={<Sparkles size={13} />}>
|
||
共享 UI
|
||
</Badge>
|
||
<h1>共享组件展示</h1>
|
||
<p>无业务依赖的基础交互组件,网站与客户端可直接复用。</p>
|
||
</div>
|
||
<IconButton
|
||
label="创建示例"
|
||
icon={<Plus size={18} />}
|
||
variant="default"
|
||
onClick={() => showFeedback('创建示例')}
|
||
/>
|
||
</header>
|
||
|
||
{feedback ? (
|
||
<Status
|
||
tone="info"
|
||
className="shared-components-showcase__feedback"
|
||
aria-label="交互反馈"
|
||
aria-live="polite"
|
||
>
|
||
{feedback}
|
||
</Status>
|
||
) : null}
|
||
|
||
<SegmentedTabs
|
||
items={tabs}
|
||
activeId={activeTab}
|
||
onChange={(id) => {
|
||
setActiveTab(id);
|
||
showFeedback(
|
||
tabs.find((tab) => tab.id === id)?.label.toString() ?? id,
|
||
);
|
||
}}
|
||
label="展示页分区"
|
||
className="shared-components-showcase__tabs"
|
||
/>
|
||
|
||
{activeTab === 'overview' ? (
|
||
<div className="shared-components-showcase__grid">
|
||
<ShowcaseSection title="Button / IconButton">
|
||
<div className="showcase-row">
|
||
<Button size="sm" onClick={() => showFeedback('主要操作')}>
|
||
主要操作
|
||
</Button>
|
||
<Button
|
||
variant="secondary"
|
||
size="sm"
|
||
onClick={() => showFeedback('次要操作')}
|
||
>
|
||
次要操作
|
||
</Button>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => showFeedback('幽灵按钮')}
|
||
>
|
||
幽灵按钮
|
||
</Button>
|
||
<Button
|
||
variant="danger"
|
||
size="sm"
|
||
onClick={() => showFeedback('危险操作')}
|
||
>
|
||
危险操作
|
||
</Button>
|
||
<Button loading size="sm">
|
||
加载中
|
||
</Button>
|
||
</div>
|
||
<Divider />
|
||
<div className="showcase-row">
|
||
<IconButton
|
||
label="添加"
|
||
icon={<Plus size={18} />}
|
||
onClick={() => showFeedback('添加')}
|
||
/>
|
||
<IconButton
|
||
label="成功"
|
||
icon={<Check size={18} />}
|
||
variant="quiet"
|
||
onClick={() => showFeedback('成功')}
|
||
/>
|
||
<IconButton
|
||
label="删除"
|
||
icon="×"
|
||
variant="danger"
|
||
onClick={() => showFeedback('删除')}
|
||
/>
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="Badge / Status">
|
||
<div className="showcase-row showcase-row--wrap">
|
||
<Badge>中性</Badge>
|
||
<Badge tone="accent">强调</Badge>
|
||
<Badge tone="success" icon={<Check size={12} />}>
|
||
成功
|
||
</Badge>
|
||
<Badge tone="warning">提醒</Badge>
|
||
<Badge tone="danger">错误</Badge>
|
||
<Badge tone="info">信息</Badge>
|
||
</div>
|
||
<div className="showcase-stack">
|
||
<Status tone="info" icon={<Info size={16} />}>
|
||
这是信息状态提示。
|
||
</Status>
|
||
<Status tone="success" icon={<Check size={16} />}>
|
||
保存完成,可以继续。
|
||
</Status>
|
||
<Status tone="warning" icon={<CircleAlert size={16} />}>
|
||
请检查输入内容。
|
||
</Status>
|
||
<Status tone="error">操作失败,请稍后重试。</Status>
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="TextField / SelectField">
|
||
<div className="showcase-stack">
|
||
<TextField
|
||
label="名称"
|
||
placeholder="输入名称"
|
||
hint="支持中英文和数字。"
|
||
/>
|
||
<TextField label="描述" multiline placeholder="输入多行描述" />
|
||
<TextField
|
||
label="错误示例"
|
||
defaultValue="不合法的值"
|
||
error="请输入有效内容。"
|
||
/>
|
||
<SelectField label="主题" defaultValue="warm">
|
||
<option value="warm">暖色</option>
|
||
<option value="dark">暗色</option>
|
||
</SelectField>
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="Label / Checkbox / Skeleton / Table">
|
||
<div className="showcase-stack">
|
||
<div className="showcase-row">
|
||
<Checkbox
|
||
id="showcase-autosave"
|
||
checked={checkboxEnabled}
|
||
onChange={(event) => {
|
||
setCheckboxEnabled(event.currentTarget.checked);
|
||
showFeedback('复选框');
|
||
}}
|
||
/>
|
||
<Label htmlFor="showcase-autosave">
|
||
{checkboxEnabled ? '自动保存已开启' : '自动保存已关闭'}
|
||
</Label>
|
||
</div>
|
||
<div className="showcase-stack" aria-label="骨架屏示例">
|
||
<Skeleton className="h-3 w-2/5" />
|
||
<Skeleton className="h-3 w-full" />
|
||
<Skeleton className="h-3 w-4/5" />
|
||
</div>
|
||
<Table>
|
||
<TableCaption>最近同步记录</TableCaption>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>项目</TableHead>
|
||
<TableHead>状态</TableHead>
|
||
<TableHead>时间</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
<TableRow>
|
||
<TableCell>珊瑚岛</TableCell>
|
||
<TableCell>已发布</TableCell>
|
||
<TableCell>2 分钟前</TableCell>
|
||
</TableRow>
|
||
<TableRow>
|
||
<TableCell>月光林</TableCell>
|
||
<TableCell>草稿</TableCell>
|
||
<TableCell>昨天</TableCell>
|
||
</TableRow>
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="Subpanel / Switch">
|
||
<Subpanel
|
||
title="可复用面板"
|
||
actions={<Badge tone="success">稳定</Badge>}
|
||
tone="soft"
|
||
>
|
||
<p className="showcase-copy">
|
||
面板只负责 surface、标题和 actions 插槽,不读取任何业务状态。
|
||
</p>
|
||
<Switch
|
||
checked={enabled}
|
||
label={enabled ? '已启用' : '已停用'}
|
||
onClick={() => {
|
||
setEnabled((value) => !value);
|
||
showFeedback('开关');
|
||
}}
|
||
/>
|
||
</Subpanel>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="ProgressBar / Spinner / Divider">
|
||
<div className="showcase-stack">
|
||
<ProgressBar value={progress} label="示例进度">
|
||
完成 {progress}%
|
||
</ProgressBar>
|
||
<div className="showcase-row showcase-row--between">
|
||
<Button
|
||
size="sm"
|
||
variant="secondary"
|
||
onClick={() => adjustProgress(-10, '减少')}
|
||
>
|
||
减少
|
||
</Button>
|
||
<Spinner label="加载示例" />
|
||
<Button size="sm" onClick={() => adjustProgress(10, '增加')}>
|
||
增加
|
||
</Button>
|
||
</div>
|
||
<ProgressBar indeterminate label="正在处理" />
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="EmptyState / Modal">
|
||
<EmptyState
|
||
icon={<Sparkles size={20} />}
|
||
title="这里还没有内容"
|
||
description="空态组件只接受展示内容和 action,不负责创建逻辑。"
|
||
action={
|
||
<Button
|
||
size="sm"
|
||
onClick={() => {
|
||
setModalOpen(true);
|
||
showFeedback('打开弹窗');
|
||
}}
|
||
>
|
||
打开弹窗
|
||
</Button>
|
||
}
|
||
/>
|
||
<Modal
|
||
open={modalOpen}
|
||
title="共享弹窗"
|
||
description="弹窗支持 ESC、遮罩点击和 portal。"
|
||
onClose={() => setModalOpen(false)}
|
||
footer={
|
||
<Button
|
||
onClick={() => {
|
||
setModalOpen(false);
|
||
showFeedback('完成');
|
||
}}
|
||
>
|
||
完成
|
||
</Button>
|
||
}
|
||
>
|
||
<p className="showcase-copy">
|
||
正文由宿主传入,组件本身不携带账号、请求或页面路由。
|
||
</p>
|
||
</Modal>
|
||
</ShowcaseSection>
|
||
</div>
|
||
) : null}
|
||
|
||
{activeTab === 'platform' ? (
|
||
<PlatformComponentsReference
|
||
filterTab={filterTab}
|
||
sortLabel={sortMode === 'recent' ? '最近使用' : '名称'}
|
||
assets={visibleDemoAssets}
|
||
platformTags={platformTags}
|
||
platformToggle={platformToggle}
|
||
selectedAssetId={selectedAssetId}
|
||
asyncMode={asyncMode}
|
||
filterOpen={filterOpen}
|
||
onOpenFilter={() => {
|
||
setFilterOpen(true);
|
||
showFeedback('打开筛选');
|
||
}}
|
||
onCloseFilter={() => setFilterOpen(false)}
|
||
onFeedback={showFeedback}
|
||
onFilterTabChange={(id) => {
|
||
setFilterTab(id);
|
||
showFeedback(`筛选:${id}`);
|
||
}}
|
||
onToggleSort={() => {
|
||
const nextMode = sortMode === 'recent' ? 'name' : 'recent';
|
||
setSortMode(nextMode);
|
||
showFeedback(
|
||
`排序:${nextMode === 'recent' ? '最近使用' : '名称'}`,
|
||
);
|
||
}}
|
||
onTagsChange={(tags) => {
|
||
setPlatformTags(tags);
|
||
showFeedback('标签已更新');
|
||
}}
|
||
onGenerateTags={() => showFeedback('生成标签')}
|
||
onToggleChange={(checked) => {
|
||
setPlatformToggle(checked);
|
||
showFeedback(checked ? '开关已开启' : '开关已关闭');
|
||
}}
|
||
onSelectAsset={(id) => {
|
||
setSelectedAssetId(id);
|
||
showFeedback('已选择素材');
|
||
}}
|
||
onAsyncModeChange={(mode) => {
|
||
setAsyncMode(mode);
|
||
showFeedback(`异步状态:${mode}`);
|
||
}}
|
||
/>
|
||
) : null}
|
||
|
||
{activeTab === 'tokens' ? <TokenReference /> : null}
|
||
{activeTab === 'states' ? <StateReference /> : null}
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|
||
|
||
type PlatformComponentsReferenceProps = {
|
||
filterTab: string;
|
||
sortLabel: string;
|
||
assets: DemoAsset[];
|
||
platformTags: string[];
|
||
platformToggle: boolean;
|
||
selectedAssetId: string;
|
||
asyncMode: 'content' | 'loading' | 'empty' | 'error';
|
||
filterOpen: boolean;
|
||
onFeedback: (label: string) => void;
|
||
onOpenFilter: () => void;
|
||
onCloseFilter: () => void;
|
||
onFilterTabChange: (id: string) => void;
|
||
onToggleSort: () => void;
|
||
onTagsChange: (tags: string[]) => void;
|
||
onGenerateTags: () => void;
|
||
onToggleChange: (checked: boolean) => void;
|
||
onSelectAsset: (id: string) => void;
|
||
onAsyncModeChange: (
|
||
mode: PlatformComponentsReferenceProps['asyncMode'],
|
||
) => void;
|
||
};
|
||
|
||
function PlatformComponentsReference({
|
||
filterTab,
|
||
sortLabel,
|
||
assets,
|
||
platformTags,
|
||
platformToggle,
|
||
selectedAssetId,
|
||
asyncMode,
|
||
filterOpen,
|
||
onFeedback,
|
||
onOpenFilter,
|
||
onCloseFilter,
|
||
onFilterTabChange,
|
||
onToggleSort,
|
||
onTagsChange,
|
||
onGenerateTags,
|
||
onToggleChange,
|
||
onSelectAsset,
|
||
onAsyncModeChange,
|
||
}: PlatformComponentsReferenceProps) {
|
||
// 预览绑定当前选中的素材,而不是过滤/排序后的第一项,避免排序带动上传预览的视觉主题变化。
|
||
const previewAsset =
|
||
demoAssets.find((asset) => asset.id === selectedAssetId) ?? demoAssets[0];
|
||
const filterApplied = filterTab !== '全部';
|
||
const filterEntryLabel = filterApplied ? filterTab : '筛选';
|
||
const filterEntryCount = filterApplied ? assets.length : 3;
|
||
|
||
return (
|
||
<div className="shared-components-showcase__platform-grid">
|
||
<ShowcaseSection title="导航 / 筛选工具栏">
|
||
<div className="showcase-platform-desktop-toolbar">
|
||
<PlatformFilterToolbar
|
||
filterLabel={filterEntryLabel}
|
||
filterCount={filterEntryCount}
|
||
tabItems={[
|
||
{ id: '全部', label: '全部' },
|
||
{ id: '草稿', label: '草稿' },
|
||
{ id: '已发布', label: '已发布' },
|
||
]}
|
||
activeTabId={filterTab}
|
||
sortLabel={sortLabel}
|
||
layout="desktop"
|
||
onOpenFilter={onOpenFilter}
|
||
onTabChange={onFilterTabChange}
|
||
onToggleSort={onToggleSort}
|
||
/>
|
||
</div>
|
||
<div className="showcase-platform-mobile-toolbar">
|
||
<PlatformFilterToolbar
|
||
filterLabel={filterEntryLabel}
|
||
filterCount={filterEntryCount}
|
||
tabItems={[
|
||
{ id: '全部', label: '全部' },
|
||
{ id: '草稿', label: '草稿' },
|
||
{ id: '已发布', label: '已发布' },
|
||
]}
|
||
activeTabId={filterTab}
|
||
sortLabel={sortLabel}
|
||
layout="mobile"
|
||
onOpenFilter={onOpenFilter}
|
||
onTabChange={onFilterTabChange}
|
||
onToggleSort={onToggleSort}
|
||
/>
|
||
</div>
|
||
<p className="showcase-copy showcase-filter-summary">
|
||
当前筛选:{filterTab} · {sortLabel} · {assets.length} 个结果
|
||
</p>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="标签 / 整行开关">
|
||
<PlatformTagEditor
|
||
title="内容标签"
|
||
tags={platformTags}
|
||
maxTags={5}
|
||
parseInput={(value) => value.split(/[\s,,]+/)}
|
||
onChange={onTagsChange}
|
||
onGenerate={onGenerateTags}
|
||
generateLabel="生成标签"
|
||
inputLabel="新增标签"
|
||
inputPlaceholder="输入后回车"
|
||
tone="warm"
|
||
padding="md"
|
||
/>
|
||
<div className="mt-3">
|
||
<PlatformToggleRow
|
||
label="允许智能推荐"
|
||
checked={platformToggle}
|
||
onChange={onToggleChange}
|
||
onLabel="开启"
|
||
offLabel="关闭"
|
||
icon={<Sparkles size={15} />}
|
||
/>
|
||
</div>
|
||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||
<PlatformBackActionButton onClick={() => onFeedback('返回动作')} />
|
||
<PlatformBackActionButton
|
||
label="返回编辑"
|
||
variant="regular"
|
||
surface="editorDark"
|
||
onClick={() => onFeedback('返回编辑')}
|
||
/>
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="媒体 / 上传入口">
|
||
<div className="showcase-platform-media-row">
|
||
<PlatformMediaFrame
|
||
alt="无图片时的媒体框"
|
||
fallbackLabel="媒体 fallback"
|
||
aspect="landscape"
|
||
surface="soft"
|
||
fallbackContent={<span>16:9 媒体框</span>}
|
||
/>
|
||
<PlatformUploadTile
|
||
label="上传素材"
|
||
hint="PNG / JPG"
|
||
size="panel"
|
||
onClick={() => onFeedback('上传素材')}
|
||
/>
|
||
<div className="showcase-platform-upload-preview-shell">
|
||
<PlatformUploadPreviewCard
|
||
imageSrc={previewAsset?.image ?? ''}
|
||
imageAlt={`${previewAsset?.label ?? '素材'}预览`}
|
||
removeLabel="移除预览"
|
||
layout="square"
|
||
previewLabel={`预览${previewAsset?.label ?? '素材'}`}
|
||
onPreview={() =>
|
||
onFeedback(`预览${previewAsset?.label ?? '素材'}`)
|
||
}
|
||
onRemove={() => onFeedback('移除预览')}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<PlatformMediaTileGrid
|
||
items={assets.map((asset) => ({
|
||
id: asset.id,
|
||
src: asset.image,
|
||
alt: asset.label,
|
||
fallbackLabel: asset.label,
|
||
}))}
|
||
columns="five"
|
||
aspect="auto"
|
||
surface="soft"
|
||
tileSurface="white"
|
||
className="mt-3"
|
||
/>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="素材选择 / 选中态">
|
||
<PlatformAssetPickerGrid
|
||
items={assets}
|
||
loadingLabel="正在读取素材"
|
||
emptyLabel="还没有素材"
|
||
getKey={(asset) => asset.id}
|
||
getImageSrc={(asset) => asset.image}
|
||
getImageAlt={(asset) => asset.label}
|
||
getTitle={(asset) => asset.label}
|
||
getSubtitle={() => '本地示例素材'}
|
||
getAriaLabel={(asset) => `选择${asset.label}`}
|
||
isSelected={(asset) => asset.id === selectedAssetId}
|
||
onSelect={(asset) => onSelectAsset(asset.id)}
|
||
selectLabel="选择"
|
||
gridClassName="grid grid-cols-2 gap-2 sm:grid-cols-4"
|
||
/>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="反馈 / 异步状态">
|
||
<div className="showcase-row showcase-row--wrap">
|
||
{(['content', 'loading', 'empty', 'error'] as const).map((mode) => (
|
||
<Button
|
||
key={mode}
|
||
size="sm"
|
||
variant={asyncMode === mode ? 'primary' : 'secondary'}
|
||
onClick={() => onAsyncModeChange(mode)}
|
||
>
|
||
{mode === 'content'
|
||
? '内容'
|
||
: mode === 'loading'
|
||
? '加载中'
|
||
: mode === 'empty'
|
||
? '空态'
|
||
: '错误'}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
<div className="mt-3">
|
||
<PlatformAsyncStatePanel
|
||
isLoading={asyncMode === 'loading'}
|
||
isEmpty={asyncMode === 'empty'}
|
||
errorState={
|
||
asyncMode === 'error' ? (
|
||
<PlatformRuntimeStatusToast tone="error" surface="light">
|
||
读取失败,请重试
|
||
</PlatformRuntimeStatusToast>
|
||
) : null
|
||
}
|
||
loadingState={
|
||
<PlatformRuntimeStatusToast tone="info" surface="light">
|
||
正在读取素材…
|
||
</PlatformRuntimeStatusToast>
|
||
}
|
||
emptyState={
|
||
<PlatformEmptyState surface="dashed" size="inline">
|
||
暂无可展示内容
|
||
</PlatformEmptyState>
|
||
}
|
||
>
|
||
<div className="showcase-platform-toast-stack">
|
||
<PlatformRuntimeStatusToast tone="success" surface="light">
|
||
已保存
|
||
</PlatformRuntimeStatusToast>
|
||
<PlatformRuntimeStatusToast tone="warning" surface="solid">
|
||
即将过期
|
||
</PlatformRuntimeStatusToast>
|
||
<PlatformRuntimeStatusToast tone="info" surface="dark">
|
||
同步中
|
||
</PlatformRuntimeStatusToast>
|
||
</div>
|
||
</PlatformAsyncStatePanel>
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<ShowcaseSection title="信息 / 指标 / 列表行">
|
||
<PlatformStatGrid
|
||
items={[
|
||
{ label: '作品数', value: '24' },
|
||
{ label: '已发布', value: '18' },
|
||
{ label: '收藏', value: '1.2k' },
|
||
]}
|
||
columns="three"
|
||
surface="plain"
|
||
/>
|
||
<div className="showcase-platform-info-list">
|
||
<PlatformInfoBlock label="最近同步" variant="compactRow">
|
||
2 分钟前
|
||
</PlatformInfoBlock>
|
||
<PlatformNavigableListItem
|
||
leading={<Badge tone="success">在线</Badge>}
|
||
trailing={<span aria-hidden="true">›</span>}
|
||
onClick={() => onFeedback('打开项目工作台')}
|
||
>
|
||
<strong>项目工作台</strong>
|
||
<span className="showcase-platform-list-subtitle">
|
||
点击进入详情
|
||
</span>
|
||
</PlatformNavigableListItem>
|
||
</div>
|
||
</ShowcaseSection>
|
||
|
||
<Modal
|
||
open={filterOpen}
|
||
title="筛选示例"
|
||
description="选择后会同步到入口,点击完成关闭面板。"
|
||
onClose={onCloseFilter}
|
||
footer={
|
||
<Button variant="secondary" onClick={onCloseFilter}>
|
||
完成
|
||
</Button>
|
||
}
|
||
>
|
||
<div className="showcase-filter-options">
|
||
{['全部', '草稿', '已发布'].map((option) => (
|
||
<Button
|
||
key={option}
|
||
variant={filterTab === option ? 'primary' : 'secondary'}
|
||
onClick={() => onFilterTabChange(option)}
|
||
>
|
||
{option}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function ShowcaseSection({
|
||
title,
|
||
children,
|
||
}: {
|
||
title: string;
|
||
children: ReactNode;
|
||
}) {
|
||
return (
|
||
<Subpanel title={title} className="shared-components-showcase__section">
|
||
{children}
|
||
</Subpanel>
|
||
);
|
||
}
|
||
|
||
function TokenReference() {
|
||
return (
|
||
<div className="shared-components-showcase__token-grid">
|
||
{[
|
||
['Accent', 'var(--platform-accent)'],
|
||
['Panel', 'var(--platform-panel-fill)'],
|
||
['Border', 'var(--platform-subpanel-border)'],
|
||
['Strong text', 'var(--platform-text-strong)'],
|
||
['Muted text', 'var(--platform-text-soft)'],
|
||
['Focus', 'var(--platform-input-focus-ring)'],
|
||
].map(([label, value]) => (
|
||
<Subpanel key={label} title={label} padding="sm">
|
||
<div className="showcase-token-value" style={{ background: value }} />
|
||
<code>{value}</code>
|
||
</Subpanel>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function StateReference() {
|
||
return (
|
||
<Subpanel title="交互状态">
|
||
<div className="showcase-state-list">
|
||
<span>
|
||
<span className="showcase-dot showcase-dot--default" />
|
||
默认
|
||
</span>
|
||
<span>
|
||
<span className="showcase-dot showcase-dot--hover" />
|
||
悬停
|
||
</span>
|
||
<span>
|
||
<span className="showcase-dot showcase-dot--focus" />
|
||
聚焦
|
||
</span>
|
||
<span>
|
||
<span className="showcase-dot showcase-dot--disabled" />
|
||
禁用
|
||
</span>
|
||
</div>
|
||
<p className="showcase-copy">
|
||
所有可交互组件都提供原生语义、键盘焦点样式和 disabled 状态。
|
||
</p>
|
||
</Subpanel>
|
||
);
|
||
}
|