收口前端平台组件库能力

新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件
迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome
补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
2026-06-10 10:24:18 +08:00
parent a4ee6ff698
commit 1ad25e30f8
226 changed files with 23364 additions and 7825 deletions

View File

@@ -363,26 +363,24 @@ import {
} from '../../services/wooden-fish/woodenFishClient';
import type { CustomWorldProfile } from '../../types';
import { useAuthUi } from '../auth/AuthUiContext';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
import { PublishShareModal } from '../common/PublishShareModal';
import type { PublishShareModalPayload } from '../common/publishShareModalModel';
import { UnifiedModal } from '../common/UnifiedModal';
import { UnifiedConfirmDialog } from '../common/UnifiedConfirmDialog';
import { resolveCreativeAgentTargetSelectionStage } from '../creative-agent/creativeAgentViewModel';
import {
buildCreationWorkShelfItems,
type CreationWorkShelfItem,
isPersistedBarkBattleDraftGenerating,
} from '../custom-world-home/creationWorkShelf';
import {
selectAdjacentPlatformRecommendEntry,
} from '../rpg-entry/rpgEntryPublicGalleryViewModel';
import { selectAdjacentPlatformRecommendEntry } from '../rpg-entry/rpgEntryPublicGalleryViewModel';
import {
isBigFishGalleryEntry,
isEdutainmentGalleryEntry,
isJumpHopGalleryEntry,
isPuzzleGalleryEntry,
isPuzzleClearGalleryEntry,
mapPuzzleClearWorkToPlatformGalleryCard,
mapPuzzleWorkToPlatformGalleryCard,
type PlatformPublicGalleryCard,
resolvePlatformPublicWorkCode,
} from '../rpg-entry/rpgEntryWorldPresentation';
@@ -525,7 +523,6 @@ import { PlatformEntryWorldDetailView } from './PlatformEntryWorldDetailView';
import { PlatformErrorDialog } from './PlatformErrorDialog';
import { PlatformFeedbackView } from './PlatformFeedbackView';
import { resolvePlatformGenerationProgressTickDecision } from './platformGenerationProgressTickModel';
import { buildPlatformRecommendedEntries } from './platformRecommendation';
import {
buildMatch3DProfileFromSession,
hasMatch3DRuntimeAsset,
@@ -636,6 +633,7 @@ import {
buildPuzzleResultWorkId,
} from './platformPuzzleIdentityModel';
import { mergePuzzleServiceRuntimeState } from './platformPuzzleRuntimeStateModel';
import { buildPlatformRecommendedEntries } from './platformRecommendation';
import {
type PlatformPuzzleRuntimeAuthMode,
resolvePlatformRecommendRuntimeAuthPlan,
@@ -764,6 +762,11 @@ type DeleteCreationWorkConfirmation = {
run: () => void;
};
type WorkNotFoundRecoveryDialogState = {
message: string;
nextPath: '/';
};
async function resumePuzzleProfileSaveArchiveRaw(worldKey: string) {
return requestRpgRuntimeJson<
ProfileSaveArchiveResumeResponse<PuzzleSaveArchiveState>
@@ -992,22 +995,22 @@ function isMissingPuzzleWorkError(error: unknown) {
);
}
function maybeAlertWorkNotFoundAndReturnHome() {
function resolveWorkNotFoundRecoveryDialogState(): WorkNotFoundRecoveryDialogState | null {
if (typeof window === 'undefined') {
return false;
return null;
}
const recoveryAction = resolveWorkNotFoundRecoveryAction(
window.location.pathname,
);
if (!recoveryAction) {
return false;
return null;
}
// 中文注释:直接打开公开详情或运行态深链失效时,确认提示后必须离开空详情页。
window.alert('作品不存在或已下架,将返回首页。');
pushAppHistoryPath(recoveryAction.nextPath);
return true;
return {
message: '作品不存在或已下架,将返回首页。',
nextPath: recoveryAction.nextPath,
};
}
function hasSeenPuzzleOnboarding() {
@@ -1194,7 +1197,9 @@ const PuzzleClearResultView = lazy(async () => {
});
const PuzzleClearRuntimeShell = lazy(async () => {
const module = await import('../puzzle-clear-runtime/PuzzleClearRuntimeShell');
const module = await import(
'../puzzle-clear-runtime/PuzzleClearRuntimeShell'
);
return {
default: module.PuzzleClearRuntimeShell,
};
@@ -1372,13 +1377,13 @@ function CreationResultRecoveryPanel({
<div className="mt-2 text-sm leading-6 text-[var(--platform-text-base)]">
{message}
</div>
<button
type="button"
<PlatformActionButton
onClick={onAction}
className="platform-button platform-button--primary mt-4 min-h-11 justify-center px-4 py-3 text-sm"
size="md"
className="mt-4 min-h-11"
>
{actionLabel}
</button>
</PlatformActionButton>
</div>
</div>
);
@@ -1690,6 +1695,8 @@ export function PlatformEntryFlowShellImpl({
>(null);
const [pendingDeleteCreationWork, setPendingDeleteCreationWork] =
useState<DeleteCreationWorkConfirmation | null>(null);
const [workNotFoundRecoveryDialog, setWorkNotFoundRecoveryDialog] =
useState<WorkNotFoundRecoveryDialogState | null>(null);
const [
claimingPuzzlePointIncentiveProfileId,
setClaimingPuzzlePointIncentiveProfileId,
@@ -2130,10 +2137,20 @@ export function PlatformEntryFlowShellImpl({
const returnPlatformHomeAfterMissingWork = useCallback(() => {
setPlatformTab('home');
setSelectionStage('platform');
if (!maybeAlertWorkNotFoundAndReturnHome()) {
pushAppHistoryPath('/');
const recoveryDialog = resolveWorkNotFoundRecoveryDialogState();
if (recoveryDialog) {
setWorkNotFoundRecoveryDialog(recoveryDialog);
return;
}
pushAppHistoryPath('/');
}, [setPlatformTab, setSelectionStage]);
const confirmWorkNotFoundRecovery = useCallback(() => {
const nextPath = workNotFoundRecoveryDialog?.nextPath ?? '/';
setWorkNotFoundRecoveryDialog(null);
setPlatformTab('home');
setSelectionStage('platform');
pushAppHistoryPath(nextPath);
}, [setPlatformTab, setSelectionStage, workNotFoundRecoveryDialog]);
useEffect(() => {
if (selectionStage === 'profile-feedback') {
@@ -2899,8 +2916,10 @@ export function PlatformEntryFlowShellImpl({
woodenFishGalleryEntries,
],
);
const { featuredEntries: featuredGalleryEntries, latestEntries: latestGalleryEntries } =
publicGalleryFeeds;
const {
featuredEntries: featuredGalleryEntries,
latestEntries: latestGalleryEntries,
} = publicGalleryFeeds;
const recommendRuntimeEntries = useMemo(
() =>
buildPlatformRecommendedEntries({
@@ -3083,23 +3102,22 @@ export function PlatformEntryFlowShellImpl({
]);
useEffect(() => {
const progressTickDecision =
resolvePlatformGenerationProgressTickDecision({
selectionStage,
miniGameStates: {
puzzle: puzzleGenerationState,
match3d: match3dGenerationState,
'big-fish': bigFishGenerationState,
'square-hole': squareHoleGenerationState,
'jump-hop': jumpHopGenerationState,
'wooden-fish': woodenFishGenerationState,
'baby-object-match': babyObjectMatchGenerationState,
},
visualNovel: {
startedAtMs: visualNovelGenerationStartedAtMs,
phase: visualNovelGenerationPhase,
},
});
const progressTickDecision = resolvePlatformGenerationProgressTickDecision({
selectionStage,
miniGameStates: {
puzzle: puzzleGenerationState,
match3d: match3dGenerationState,
'big-fish': bigFishGenerationState,
'square-hole': squareHoleGenerationState,
'jump-hop': jumpHopGenerationState,
'wooden-fish': woodenFishGenerationState,
'baby-object-match': babyObjectMatchGenerationState,
},
visualNovel: {
startedAtMs: visualNovelGenerationStartedAtMs,
phase: visualNovelGenerationPhase,
},
});
if (!progressTickDecision.shouldTick) {
return undefined;
@@ -3602,7 +3620,11 @@ export function PlatformEntryFlowShellImpl({
markPendingDraftFailed('match3d', session.sessionId);
markDraftFailed(
'match3d',
[session.draft?.profileId, session.publishedProfileId, session.sessionId],
[
session.draft?.profileId,
session.publishedProfileId,
session.sessionId,
],
errorMessage,
);
try {
@@ -3884,7 +3906,11 @@ export function PlatformEntryFlowShellImpl({
markPendingDraftFailed('square-hole', session.sessionId);
markDraftFailed(
'square-hole',
[session.draft?.profileId, session.publishedProfileId, session.sessionId],
[
session.draft?.profileId,
session.publishedProfileId,
session.sessionId,
],
errorMessage,
);
void refreshSquareHoleShelf().catch(() => undefined);
@@ -3964,15 +3990,18 @@ export function PlatformEntryFlowShellImpl({
if (!isPuzzleCompileActionReady(response.session)) {
const nextPayload =
formPayload ?? buildPuzzleFormPayloadFromSession(response.session);
const fallbackGenerationState = createPuzzleDraftGenerationStateFromPayload(
nextPayload,
response.session,
);
const nextGenerationState = mergePuzzleSessionProgressIntoGenerationState(
puzzleGenerationState ?? fallbackGenerationState,
response.session,
);
activePuzzleGenerationSessionIdRef.current = response.session.sessionId;
const fallbackGenerationState =
createPuzzleDraftGenerationStateFromPayload(
nextPayload,
response.session,
);
const nextGenerationState =
mergePuzzleSessionProgressIntoGenerationState(
puzzleGenerationState ?? fallbackGenerationState,
response.session,
);
activePuzzleGenerationSessionIdRef.current =
response.session.sessionId;
setSelectionStage('puzzle-generating');
markDraftGenerating('puzzle', [
response.session.sessionId,
@@ -7648,8 +7677,7 @@ export function PlatformEntryFlowShellImpl({
...current.filter(
(item) =>
item.workId !== response.work!.summary.workId &&
item.sourceSessionId !==
response.work!.summary.sourceSessionId,
item.sourceSessionId !== response.work!.summary.sourceSessionId,
),
]);
markPendingDraftReady(
@@ -7771,7 +7799,8 @@ export function PlatformEntryFlowShellImpl({
workTitle: puzzleClearSession.draft?.workTitle,
workDescription: puzzleClearSession.draft?.workDescription,
themePrompt: puzzleClearSession.draft?.themePrompt,
boardBackgroundPrompt: puzzleClearSession.draft?.boardBackgroundPrompt,
boardBackgroundPrompt:
puzzleClearSession.draft?.boardBackgroundPrompt,
generateBoardBackground:
puzzleClearSession.draft?.generateBoardBackground,
boardBackgroundAsset: puzzleClearSession.draft?.boardBackgroundAsset,
@@ -7796,11 +7825,9 @@ export function PlatformEntryFlowShellImpl({
);
setPuzzleClearError(errorMessage);
setPuzzleClearGenerationState(
resolveFinishedMiniGameDraftGenerationState(
generationState,
'failed',
{ error: errorMessage },
),
resolveFinishedMiniGameDraftGenerationState(generationState, 'failed', {
error: errorMessage,
}),
);
} finally {
setIsPuzzleClearBusy(false);
@@ -7827,7 +7854,9 @@ export function PlatformEntryFlowShellImpl({
setPuzzleClearWork(response.item);
setPuzzleClearWorks((current) => [
response.item.summary,
...current.filter((item) => item.workId !== response.item.summary.workId),
...current.filter(
(item) => item.workId !== response.item.summary.workId,
),
]);
void refreshPuzzleClearShelf();
void refreshPuzzleClearGallery();
@@ -7840,7 +7869,9 @@ export function PlatformEntryFlowShellImpl({
setPublicWorkDetailError(null);
selectionStageRef.current = 'work-detail';
setSelectionStage('work-detail');
pushAppHistoryPath(buildPublicWorkStagePath('work-detail', publicWorkCode));
pushAppHistoryPath(
buildPublicWorkStagePath('work-detail', publicWorkCode),
);
openPublishShareModal({
title: response.item.summary.workTitle || '拼消消',
publicWorkCode,
@@ -7942,7 +7973,9 @@ export function PlatformEntryFlowShellImpl({
return;
}
setPuzzleClearError(null);
setPuzzleClearRun(retryPuzzleClearLocalLevel(puzzleClearRun, puzzleClearWork));
setPuzzleClearRun(
retryPuzzleClearLocalLevel(puzzleClearRun, puzzleClearWork),
);
return;
}
@@ -10816,6 +10849,7 @@ export function PlatformEntryFlowShellImpl({
[
openPublicWorkDetail,
platformBootstrap.platformTab,
returnPlatformHomeAfterMissingWork,
resolvePuzzleErrorMessage,
setIsPuzzleBusy,
setPuzzleError,
@@ -10971,7 +11005,8 @@ export function PlatformEntryFlowShellImpl({
notices: draftGenerationNotices,
generation: {
activeSessionId: jumpHopSession?.sessionId,
hasActiveGenerationFailure: jumpHopGenerationState?.phase === 'failed',
hasActiveGenerationFailure:
jumpHopGenerationState?.phase === 'failed',
},
});
markDraftNoticeSeen(openIntent.noticeKeys);
@@ -11055,7 +11090,9 @@ export function PlatformEntryFlowShellImpl({
try {
const detail = await puzzleClearClient.getRuntimeWorkDetail(profileId);
setPuzzleClearWork(detail.item);
openPublicWorkDetail(mapPuzzleClearWorkToPlatformGalleryCard(detail.item));
openPublicWorkDetail(
mapPuzzleClearWorkToPlatformGalleryCard(detail.item),
);
} catch (error) {
setPublicWorkDetailError(
resolveRpgCreationErrorMessage(error, '读取拼消消详情失败。'),
@@ -11322,11 +11359,7 @@ export function PlatformEntryFlowShellImpl({
setPuzzleRuntimeAuthMode('default');
setPuzzleError(null);
setPublicWorkDetailError(null);
setPlatformTab('home');
setSelectionStage('platform');
if (!maybeAlertWorkNotFoundAndReturnHome()) {
pushAppHistoryPath('/');
}
returnPlatformHomeAfterMissingWork();
return;
}
@@ -11357,8 +11390,7 @@ export function PlatformEntryFlowShellImpl({
notices: draftGenerationNotices,
generation: {
activeSessionId: puzzleSession?.sessionId,
hasActiveGenerationFailure:
activeGenerationState?.phase === 'failed',
hasActiveGenerationFailure: activeGenerationState?.phase === 'failed',
hasActiveGenerationRunning: isMiniGameDraftGenerating(
activeGenerationState ?? null,
),
@@ -11405,9 +11437,8 @@ export function PlatformEntryFlowShellImpl({
const failedError = backgroundTask?.error ?? openIntent.errorMessage;
if (!failedSession) {
try {
const { session: latestSession } = await getPuzzleAgentSession(
sourceSessionId,
);
const { session: latestSession } =
await getPuzzleAgentSession(sourceSessionId);
failedSession = latestSession;
failedPayload = buildPuzzleFormPayloadFromSession(latestSession);
} catch {
@@ -11490,9 +11521,8 @@ export function PlatformEntryFlowShellImpl({
if (openIntent.type === 'restore-generating') {
try {
const { session: latestSession } = await getPuzzleAgentSession(
sourceSessionId,
);
const { session: latestSession } =
await getPuzzleAgentSession(sourceSessionId);
const payload = buildPuzzleFormPayloadFromSession(latestSession);
const startedAtMs = resolveMiniGameDraftGenerationStartedAtMs(
latestSession.updatedAt,
@@ -11541,9 +11571,7 @@ export function PlatformEntryFlowShellImpl({
markDraftNoticeSeen(noticeKeys);
const restoredSession = await puzzleFlow.restoreDraft(
sourceSessionId,
);
const restoredSession = await puzzleFlow.restoreDraft(sourceSessionId);
if (!restoredSession) {
await refreshPuzzleShelf().catch(() => undefined);
return;
@@ -11591,8 +11619,7 @@ export function PlatformEntryFlowShellImpl({
forceDraft: options.forceDraft,
generation: {
activeSessionId: match3dSession?.sessionId,
hasActiveGenerationFailure:
activeGenerationState?.phase === 'failed',
hasActiveGenerationFailure: activeGenerationState?.phase === 'failed',
hasActiveGenerationRunning: isMiniGameDraftGenerating(
activeGenerationState ?? null,
),
@@ -11787,9 +11814,7 @@ export function PlatformEntryFlowShellImpl({
markDraftNoticeSeen(noticeKeys);
const restoredSession = await match3dFlow.restoreDraft(
sourceSessionId,
);
const restoredSession = await match3dFlow.restoreDraft(sourceSessionId);
if (!restoredSession) {
await refreshMatch3DShelf().catch(() => undefined);
return;
@@ -13238,8 +13263,7 @@ export function PlatformEntryFlowShellImpl({
activeRecommendEntryKey && !isDesktopLayout
? (recommendRuntimeEntries.find(
(entry) =>
getPlatformPublicGalleryEntryKey(entry) ===
activeRecommendEntryKey,
getPlatformPublicGalleryEntryKey(entry) === activeRecommendEntryKey,
) ?? null)
: null;
const isActiveRecommendRuntimeReady =
@@ -13255,7 +13279,8 @@ export function PlatformEntryFlowShellImpl({
hasVisualNovelRun: Boolean(visualNovelRun),
hasWoodenFishRun: Boolean(woodenFishRun),
puzzleRunEntryProfileId: puzzleRun?.entryProfileId ?? null,
puzzleRunCurrentLevelProfileId: puzzleRun?.currentLevel?.profileId ?? null,
puzzleRunCurrentLevelProfileId:
puzzleRun?.currentLevel?.profileId ?? null,
});
useEffect(() => {
@@ -13620,10 +13645,7 @@ export function PlatformEntryFlowShellImpl({
const detailEntry = mapPuzzleClearWorkToPlatformGalleryCard(entry);
return (
canExposePublicWork(detailEntry) &&
isSamePuzzleClearPublicWorkCode(
normalizedKeyword,
entry.profileId,
)
isSamePuzzleClearPublicWorkCode(normalizedKeyword, entry.profileId)
);
});
@@ -14044,7 +14066,9 @@ export function PlatformEntryFlowShellImpl({
jumpHopItems: isJumpHopCreationVisible ? jumpHopShelfItems : [],
woodenFishItems: woodenFishShelfItems,
match3dItems: match3dShelfItems,
squareHoleItems: isSquareHoleCreationVisible ? squareHoleShelfItems : [],
squareHoleItems: isSquareHoleCreationVisible
? squareHoleShelfItems
: [],
puzzleItems: puzzleShelfItems,
babyObjectMatchItems: isBabyObjectMatchVisible
? babyObjectMatchDrafts
@@ -14276,7 +14300,7 @@ export function PlatformEntryFlowShellImpl({
puzzleShelfError ??
puzzleError ??
(isVisualNovelCreationOpen ? visualNovelError : null) ??
babyObjectMatchError ??
babyObjectMatchError ??
puzzleClearError ??
barkBattleError)
}
@@ -14652,8 +14676,20 @@ export function PlatformEntryFlowShellImpl({
onDelete={
detailNavigation.isSelectedWorldOwned
? () => {
runProtectedAction(() => {
void detailNavigation.handleDeleteSelectedWorld();
const deleteModel =
resolvePlatformCreationWorkDeleteConfirmationModel({
kind: 'rpg-library',
entry: selectedDetailEntry,
});
requestDeleteCreationWork({
id: deleteModel.id,
title: deleteModel.title,
detail: deleteModel.detail,
run: () => {
runProtectedAction(() => {
void detailNavigation.handleDeleteSelectedWorld();
});
},
});
}
: null
@@ -15684,7 +15720,9 @@ export function PlatformEntryFlowShellImpl({
profile={jumpHopWork}
isBusy={isJumpHopBusy}
error={jumpHopError}
runtimeRequestOptions={jumpHopRuntimeRequestOptions ?? undefined}
runtimeRequestOptions={
jumpHopRuntimeRequestOptions ?? undefined
}
onBack={() => {
setSelectionStage(jumpHopRuntimeReturnStage);
}}
@@ -16144,7 +16182,9 @@ export function PlatformEntryFlowShellImpl({
<UnifiedCreationPage
spec={getUnifiedSpec('visual-novel')}
onBack={leaveVisualNovelFlow}
isBackDisabled={isVisualNovelBusy || isVisualNovelStreamingReply}
isBackDisabled={
isVisualNovelBusy || isVisualNovelStreamingReply
}
>
<VisualNovelAgentWorkspace
session={visualNovelSession}
@@ -16842,29 +16882,19 @@ export function PlatformEntryFlowShellImpl({
}}
/>
) : null}
<UnifiedModal
<UnifiedConfirmDialog
open={Boolean(draftGenerationPointNotice)}
title={draftGenerationPointNotice?.title ?? '泥点提示'}
description={draftGenerationPointNoticeDescription}
onClose={() => setDraftGenerationPointNotice(null)}
confirmLabel="知道了"
closeOnBackdrop
size="sm"
overlayClassName={`platform-theme ${platformThemeClass} !items-center`}
panelClassName="platform-remap-surface rounded-[1.75rem]"
footer={
<button
type="button"
onClick={() => setDraftGenerationPointNotice(null)}
className="platform-button platform-button--primary min-h-0 rounded-full px-4 py-2 text-sm"
>
</button>
}
>
<div className="text-sm leading-6 text-[var(--platform-text-base)]">
{draftGenerationPointNotice?.message}
</div>
</UnifiedModal>
{draftGenerationPointNotice?.message}
</UnifiedConfirmDialog>
<PublishShareModal
open={Boolean(publishSharePayload)}
payload={publishSharePayload}
@@ -16882,7 +16912,19 @@ export function PlatformEntryFlowShellImpl({
overlayClassName={`platform-theme ${platformThemeClass} !items-center`}
panelClassName="platform-remap-surface rounded-[1.5rem]"
/>
<UnifiedModal
<UnifiedConfirmDialog
open={Boolean(workNotFoundRecoveryDialog)}
title="作品不可用"
onClose={confirmWorkNotFoundRecovery}
confirmLabel="知道了"
closeOnBackdrop
size="sm"
overlayClassName={`platform-theme ${platformThemeClass} !items-center`}
panelClassName="platform-remap-surface rounded-[1.75rem]"
>
{workNotFoundRecoveryDialog?.message}
</UnifiedConfirmDialog>
<UnifiedConfirmDialog
open={Boolean(pendingDeleteCreationWork)}
title="删除作品"
description={
@@ -16891,36 +16933,19 @@ export function PlatformEntryFlowShellImpl({
: undefined
}
onClose={closeDeleteCreationWorkConfirmation}
closeDisabled={Boolean(deletingCreationWorkId)}
busy={Boolean(deletingCreationWorkId)}
closeOnBackdrop={!deletingCreationWorkId}
size="sm"
overlayClassName={`platform-theme ${platformThemeClass} !items-center`}
panelClassName="platform-remap-surface rounded-[1.75rem]"
footer={
<>
<button
type="button"
onClick={closeDeleteCreationWorkConfirmation}
disabled={Boolean(deletingCreationWorkId)}
className="platform-button platform-button--ghost min-h-0 rounded-full px-4 py-2 text-sm"
>
</button>
<button
type="button"
onClick={confirmDeleteCreationWork}
disabled={Boolean(deletingCreationWorkId)}
className="platform-button platform-button--danger min-h-0 rounded-full px-4 py-2 text-sm disabled:cursor-not-allowed disabled:opacity-55"
>
{deletingCreationWorkId ? '删除中' : '确认删除'}
</button>
</>
}
showCancel
confirmLabel="确认删除"
busyConfirmLabel="删除中"
confirmTone="danger"
onConfirm={confirmDeleteCreationWork}
>
<div className="text-sm leading-6 text-[var(--platform-text-base)]">
{pendingDeleteCreationWork?.detail}
</div>
</UnifiedModal>
{pendingDeleteCreationWork?.detail}
</UnifiedConfirmDialog>
<AnimatePresence>
{(searchedPublicUser || publicSearchError) && (
<motion.div
@@ -16939,17 +16964,14 @@ export function PlatformEntryFlowShellImpl({
{publicSearchError ? '未找到结果' : '命中用户'}
</div>
</div>
<button
type="button"
<PlatformModalCloseButton
onClick={() => {
setSearchedPublicUser(null);
setPublicSearchError(null);
}}
className="platform-icon-button"
aria-label="关闭搜索结果"
>
×
</button>
label="关闭搜索结果"
variant="platformIcon"
/>
</div>
{publicSearchError ? (
<div className="mt-4 text-sm leading-6 text-[var(--platform-text-soft)]">