收口前端平台组件能力

新增 PlatformAsyncStatePanel 统一 profile 异步状态骨架
扩展 PlatformSegmentedTabs 支持滚动 tab 并接入创作入口与发现页
统一 PixelCloseButton 复用 PlatformModalCloseButton 像素关闭能力
抽取平台入口泥点前置提示弹层并收紧阻断语义
补充组件收口文档与共享决策记录
This commit is contained in:
2026-06-11 01:06:31 +08:00
parent edf37d97a7
commit 94122583ac
22 changed files with 897 additions and 445 deletions

View File

@@ -5,6 +5,7 @@ import type {
CreationEntryConfig,
CreationEntryEventBannerConfig,
} from '../../services/creationEntryConfigService';
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
import {
groupVisiblePlatformCreationTypes,
type PlatformCreationTypeCard,
@@ -111,6 +112,23 @@ export function CustomWorldCreationStartCard({
const visibleCreationTypes = isRecentTabActive
? recentCreationTypes
: (activeGroup?.items ?? []);
const categoryTabs = useMemo(
() => [
...(hasRecentCreationTypes
? [
{
id: CREATION_ENTRY_RECENT_TAB_ID,
label: '最近创作',
},
]
: []),
...creationTypeGroups.map((group) => ({
id: group.id,
label: group.label,
})),
],
[creationTypeGroups, hasRecentCreationTypes],
);
const eventBanners = useMemo(
() => resolveCreationEntryEventBanners(entryConfig),
[entryConfig],
@@ -262,52 +280,28 @@ export function CustomWorldCreationStartCard({
</section>
<section className="creation-template-list -mx-1 px-1 sm:-mx-2 sm:px-2">
<div
className="-mx-0.5 flex snap-x items-center gap-2 overflow-x-auto px-0.5 pb-1 scrollbar-hide scroll-px-2 sm:gap-3"
role="tablist"
aria-label="创作入口页签"
>
{hasRecentCreationTypes ? (
<button
type="button"
role="tab"
aria-selected={isRecentTabActive}
onClick={() => setActiveCategoryId(CREATION_ENTRY_RECENT_TAB_ID)}
className={`relative min-h-8 shrink-0 rounded-full px-2.5 text-xs font-black transition sm:min-h-9 sm:px-3.5 sm:text-sm ${
isRecentTabActive
? 'text-[#6f2f21]'
: 'text-[#7a6558] hover:text-[#6f2f21]'
}`}
>
<span></span>
{isRecentTabActive ? (
<span className="absolute bottom-0 left-3 right-3 h-1 rounded-full bg-[#d9793f]" />
) : null}
</button>
) : null}
{creationTypeGroups.map((group) => {
const selected = group.id === activeGroup?.id;
return (
<button
key={group.id}
type="button"
role="tab"
aria-selected={selected}
onClick={() => setActiveCategoryId(group.id)}
className={`relative min-h-8 shrink-0 rounded-full px-2.5 text-xs font-black transition sm:min-h-9 sm:px-3.5 sm:text-sm ${
selected
? 'text-[#6f2f21]'
: 'text-[#7a6558] hover:text-[#6f2f21]'
}`}
>
<span>{group.label}</span>
{selected ? (
<span className="absolute bottom-0 left-3 right-3 h-1 rounded-full bg-[#d9793f]" />
) : null}
</button>
);
})}
</div>
<PlatformSegmentedTabs
items={categoryTabs}
activeId={activeTabId ?? ''}
onChange={setActiveCategoryId}
layout="scroll"
gap="md"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
semantics="tabs"
ariaLabel="创作入口页签"
className="-mx-0.5 snap-x px-0.5 pb-1 scroll-px-2 sm:!gap-3"
itemClassName={(_, active) =>
[
"relative shrink-0 snap-start !min-h-8 !rounded-full !border-0 !bg-transparent !px-2.5 !text-xs !font-black !shadow-none sm:!min-h-9 sm:!px-3.5 sm:!text-sm",
active
? "!text-[#6f2f21] after:absolute after:bottom-0 after:left-3 after:right-3 after:h-1 after:rounded-full after:bg-[#d9793f] after:content-['']"
: '!text-[#7a6558] hover:!bg-transparent hover:!text-[#6f2f21]',
].join(' ')
}
/>
{isRecentTabActive ? (
<div className="creation-template-list__recent-window mt-2 text-[11px] font-bold leading-4 text-[#8b6654] sm:text-xs">

View File

@@ -1,3 +1,5 @@
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
export type CustomWorldWorkFilter = 'all' | 'draft' | 'published';
const FILTER_OPTIONS: Array<{
@@ -22,33 +24,42 @@ export function CustomWorldWorkTabs({
publishedCount,
onChange,
}: CustomWorldWorkTabsProps) {
return (
<div
className="flex min-w-0 items-center gap-4 overflow-x-auto pb-1 scrollbar-hide xl:pb-0"
role="tablist"
aria-label="作品筛选"
>
{FILTER_OPTIONS.map((option) => {
const count =
option.id === 'draft'
? draftCount
: option.id === 'published'
? publishedCount
: draftCount + publishedCount;
const filterTabs = FILTER_OPTIONS.map((option) => {
const count =
option.id === 'draft'
? draftCount
: option.id === 'published'
? publishedCount
: draftCount + publishedCount;
return (
<button
key={option.id}
type="button"
role="tab"
aria-selected={activeFilter === option.id}
onClick={() => onChange(option.id)}
className={`platform-mobile-home-channel shrink-0 ${activeFilter === option.id ? 'platform-mobile-home-channel--active' : ''}`}
>
{option.label} {count}
</button>
);
})}
</div>
return {
id: option.id,
label: `${option.label} ${count}`,
};
});
return (
<PlatformSegmentedTabs
items={filterTabs}
activeId={activeFilter}
onChange={onChange}
layout="scroll"
gap="md"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
semantics="tabs"
ariaLabel="作品筛选"
className="pb-1 !gap-4 xl:pb-0"
itemClassName={(_, active) =>
[
'platform-mobile-home-channel shrink-0 !min-h-8 !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
active ? 'platform-mobile-home-channel--active' : null,
]
.filter(Boolean)
.join(' ')
}
/>
);
}