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>
159 lines
3.9 KiB
TypeScript
159 lines
3.9 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
|
|
export type PlatformStatGridColumns = 'two' | 'three' | 'four' | 'twoToFour';
|
|
export type PlatformStatGridDensity = 'compact' | 'default';
|
|
export type PlatformStatGridOrder = 'valueFirst' | 'labelFirst';
|
|
export type PlatformStatGridSurface = 'soft' | 'plain';
|
|
export type PlatformStatGridTextAlign = 'left' | 'center';
|
|
|
|
export type PlatformStatGridItem = {
|
|
label?: ReactNode;
|
|
value: ReactNode;
|
|
key?: string;
|
|
};
|
|
|
|
export type PlatformStatGridProps = {
|
|
items: readonly PlatformStatGridItem[];
|
|
columns?: PlatformStatGridColumns;
|
|
density?: PlatformStatGridDensity;
|
|
order?: PlatformStatGridOrder;
|
|
surface?: PlatformStatGridSurface;
|
|
textAlign?: PlatformStatGridTextAlign;
|
|
className?: string;
|
|
itemClassName?:
|
|
| string
|
|
| ((item: PlatformStatGridItem, index: number) => string | null);
|
|
};
|
|
|
|
const PLATFORM_STAT_GRID_COLUMNS_CLASS: Record<
|
|
PlatformStatGridColumns,
|
|
string
|
|
> = {
|
|
two: 'grid-cols-2',
|
|
three: 'grid-cols-3',
|
|
four: 'grid-cols-4',
|
|
twoToFour: 'grid-cols-2 sm:grid-cols-4',
|
|
};
|
|
|
|
const PLATFORM_STAT_GRID_DENSITY_CLASS: Record<
|
|
PlatformStatGridDensity,
|
|
{ item: string; value: string; label: string }
|
|
> = {
|
|
compact: {
|
|
item: 'rounded-[1rem] px-2 py-2',
|
|
value: 'text-sm font-black',
|
|
label: 'mt-1 text-[0.68rem] font-bold tracking-[0.14em]',
|
|
},
|
|
default: {
|
|
item: 'rounded-[1rem] px-3 py-3',
|
|
value: 'text-lg font-black',
|
|
label: 'mt-1 text-[11px] font-bold tracking-[0.14em]',
|
|
},
|
|
};
|
|
|
|
const PLATFORM_STAT_GRID_SURFACE_CLASS: Record<
|
|
PlatformStatGridSurface,
|
|
string
|
|
> = {
|
|
soft: 'bg-white/76',
|
|
plain: 'border border-[var(--platform-subpanel-border)] bg-white/68',
|
|
};
|
|
|
|
const PLATFORM_STAT_GRID_TEXT_ALIGN_CLASS: Record<
|
|
PlatformStatGridTextAlign,
|
|
string
|
|
> = {
|
|
left: 'text-left',
|
|
center: 'text-center',
|
|
};
|
|
|
|
/**
|
|
* 平台统计小卡网格。
|
|
* 统一承接结果页里的“数值 / 标签”或轻量状态 chip 布局。
|
|
*/
|
|
export function PlatformStatGrid({
|
|
items,
|
|
columns = 'three',
|
|
density = 'default',
|
|
order = 'valueFirst',
|
|
surface = 'soft',
|
|
textAlign = 'center',
|
|
className,
|
|
itemClassName,
|
|
}: PlatformStatGridProps) {
|
|
const densityClass = PLATFORM_STAT_GRID_DENSITY_CLASS[density];
|
|
|
|
return (
|
|
<div
|
|
className={[
|
|
'grid gap-2',
|
|
PLATFORM_STAT_GRID_COLUMNS_CLASS[columns],
|
|
PLATFORM_STAT_GRID_TEXT_ALIGN_CLASS[textAlign],
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')}
|
|
>
|
|
{items.map((item, index) => {
|
|
const extraClassName =
|
|
typeof itemClassName === 'function'
|
|
? itemClassName(item, index)
|
|
: itemClassName;
|
|
const key =
|
|
item.key ??
|
|
(typeof item.label === 'string'
|
|
? item.label
|
|
: typeof item.value === 'string'
|
|
? item.value
|
|
: index);
|
|
|
|
const valueNode = (
|
|
<div
|
|
className={[
|
|
densityClass.value,
|
|
'text-[var(--platform-text-strong)]',
|
|
].join(' ')}
|
|
>
|
|
{item.value}
|
|
</div>
|
|
);
|
|
const labelNode = item.label ? (
|
|
<div
|
|
className={[
|
|
densityClass.label,
|
|
'text-[var(--platform-text-soft)]',
|
|
].join(' ')}
|
|
>
|
|
{item.label}
|
|
</div>
|
|
) : null;
|
|
|
|
return (
|
|
<div
|
|
key={key}
|
|
className={[
|
|
densityClass.item,
|
|
PLATFORM_STAT_GRID_SURFACE_CLASS[surface],
|
|
extraClassName,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')}
|
|
>
|
|
{order === 'labelFirst' ? (
|
|
<>
|
|
{labelNode}
|
|
{valueNode}
|
|
</>
|
|
) : (
|
|
<>
|
|
{valueNode}
|
|
{labelNode}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|