继续收口首页可导航扁平行
新增 PlatformNavigableListItem 统一 desktop flat row 的交互骨架 接入首页搜索结果与桌面最近作品最近浏览入口 补充组件测试首页回归测试并更新收口计划和共享决策记录
This commit is contained in:
48
src/components/common/PlatformNavigableListItem.test.tsx
Normal file
48
src/components/common/PlatformNavigableListItem.test.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { PlatformNavigableListItem } from './PlatformNavigableListItem';
|
||||
|
||||
describe('PlatformNavigableListItem', () => {
|
||||
test('renders shared navigable row structure with leading and trailing slots', () => {
|
||||
render(
|
||||
<PlatformNavigableListItem
|
||||
align="start"
|
||||
leading={<span data-testid="leading">L</span>}
|
||||
trailing={<span data-testid="trailing">R</span>}
|
||||
>
|
||||
<span>行内容</span>
|
||||
</PlatformNavigableListItem>,
|
||||
);
|
||||
|
||||
const button = screen.getByRole('button', { name: /L\s+行内容\s+R/u });
|
||||
|
||||
expect(button.className).toContain('platform-navigable-list-item');
|
||||
expect(button.className).toContain('items-start');
|
||||
expect(button.className).toContain('gap-4');
|
||||
expect(screen.getByTestId('leading').parentElement?.className).toContain(
|
||||
'platform-navigable-list-item__leading',
|
||||
);
|
||||
expect(screen.getByTestId('trailing').parentElement?.className).toContain(
|
||||
'platform-navigable-list-item__trailing',
|
||||
);
|
||||
});
|
||||
|
||||
test('defaults to type button and forwards click handlers', () => {
|
||||
const onClick = vi.fn();
|
||||
|
||||
render(
|
||||
<PlatformNavigableListItem onClick={onClick}>
|
||||
<span>打开详情</span>
|
||||
</PlatformNavigableListItem>,
|
||||
);
|
||||
|
||||
const button = screen.getByRole('button', { name: '打开详情' });
|
||||
|
||||
expect(button.getAttribute('type')).toBe('button');
|
||||
fireEvent.click(button);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
88
src/components/common/PlatformNavigableListItem.tsx
Normal file
88
src/components/common/PlatformNavigableListItem.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
||||
|
||||
type PlatformNavigableListItemAlign = 'center' | 'start';
|
||||
|
||||
type PlatformNavigableListItemProps = Omit<
|
||||
ComponentPropsWithoutRef<'button'>,
|
||||
'children' | 'type'
|
||||
> & {
|
||||
children: ReactNode;
|
||||
leading?: ReactNode;
|
||||
trailing?: ReactNode;
|
||||
align?: PlatformNavigableListItemAlign;
|
||||
leadingClassName?: string;
|
||||
trailingClassName?: string;
|
||||
bodyClassName?: string;
|
||||
};
|
||||
|
||||
const ALIGN_CLASS: Record<PlatformNavigableListItemAlign, string> = {
|
||||
center: 'items-center gap-3',
|
||||
start: 'items-start gap-4',
|
||||
};
|
||||
|
||||
/**
|
||||
* 轻量可导航列表行骨架。
|
||||
* 只统一 button 语义与 left-content/right-affordance 结构,不持有业务文案、badge 或封面规则。
|
||||
*/
|
||||
export function PlatformNavigableListItem({
|
||||
children,
|
||||
leading = null,
|
||||
trailing = null,
|
||||
align = 'center',
|
||||
className,
|
||||
leadingClassName,
|
||||
trailingClassName,
|
||||
bodyClassName,
|
||||
...props
|
||||
}: PlatformNavigableListItemProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
'platform-navigable-list-item flex w-full min-w-0 text-left',
|
||||
ALIGN_CLASS[align],
|
||||
className ?? null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
{...props}
|
||||
>
|
||||
{leading ? (
|
||||
<div
|
||||
className={[
|
||||
'platform-navigable-list-item__leading shrink-0',
|
||||
leadingClassName ?? null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{leading}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={[
|
||||
'platform-navigable-list-item__body min-w-0 flex-1',
|
||||
bodyClassName ?? null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{trailing ? (
|
||||
<div
|
||||
className={[
|
||||
'platform-navigable-list-item__trailing shrink-0',
|
||||
trailingClassName ?? null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{trailing}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
import type {
|
||||
ConfirmWechatProfileRechargeOrderResponse,
|
||||
CreateProfileRechargeOrderResponse,
|
||||
PlatformBrowseHistoryEntry,
|
||||
ProfilePlayStatsResponse,
|
||||
ProfileReferralInviteCenterResponse,
|
||||
ProfileTaskCenterResponse,
|
||||
@@ -902,6 +903,7 @@ function renderLoggedOutHomeView(
|
||||
| 'onOpenRecommendGalleryDetail'
|
||||
| 'onOpenChildMotionDemo'
|
||||
| 'onSearchPublicCode'
|
||||
| 'historyEntries'
|
||||
| 'recommendRuntimeContent'
|
||||
| 'activeRecommendEntryKey'
|
||||
| 'isStartingRecommendEntry'
|
||||
@@ -945,7 +947,7 @@ function renderLoggedOutHomeView(
|
||||
featuredEntries={overrides.featuredEntries ?? []}
|
||||
latestEntries={overrides.latestEntries ?? []}
|
||||
myEntries={[]}
|
||||
historyEntries={[]}
|
||||
historyEntries={overrides.historyEntries ?? []}
|
||||
profileDashboard={null}
|
||||
isLoadingPlatform={false}
|
||||
isLoadingDashboard={false}
|
||||
@@ -3746,7 +3748,14 @@ test('discover search fuzzy matches public work id, name, author and description
|
||||
|
||||
await user.clear(searchInput);
|
||||
await user.type(searchInput, '火桥{enter}');
|
||||
expect(await within(discoverPanel).findByText('火桥谜图')).toBeTruthy();
|
||||
const lavaBridgeResultButton = await within(discoverPanel).findByRole(
|
||||
'button',
|
||||
{ name: /火桥谜图/u },
|
||||
);
|
||||
expect(lavaBridgeResultButton.className).toContain(
|
||||
'platform-navigable-list-item',
|
||||
);
|
||||
expect(within(discoverPanel).getByText('火桥谜图')).toBeTruthy();
|
||||
expect(within(discoverPanel).queryByText('月井机关')).toBeNull();
|
||||
|
||||
await user.clear(searchInput);
|
||||
@@ -5336,6 +5345,41 @@ test('desktop logged in home syncs mobile home modules without square or latest
|
||||
expect(screen.queryByText('1777110165.990127Z')).toBeNull();
|
||||
});
|
||||
|
||||
test('desktop home quick access rows reuse shared navigable list item', async () => {
|
||||
mockDesktopLayout();
|
||||
const user = userEvent.setup();
|
||||
const onOpenRecommendGalleryDetail = vi.fn();
|
||||
const historyEntry = {
|
||||
ownerUserId: 'history-user-1',
|
||||
profileId: 'history-profile-1',
|
||||
worldName: '桌面最近浏览作品',
|
||||
subtitle: '历史副标题',
|
||||
summaryText: '这是最近浏览入口。',
|
||||
coverImageSrc: null,
|
||||
themeMode: 'mythic',
|
||||
authorDisplayName: '历史作者',
|
||||
visitedAt: new Date().toISOString(),
|
||||
} satisfies PlatformBrowseHistoryEntry;
|
||||
|
||||
renderLoggedOutHomeView(
|
||||
vi.fn(),
|
||||
{
|
||||
historyEntries: [historyEntry],
|
||||
onOpenRecommendGalleryDetail,
|
||||
},
|
||||
'home',
|
||||
true,
|
||||
);
|
||||
|
||||
const historyButton = screen.getByRole('button', {
|
||||
name: /桌面最近浏览作品/u,
|
||||
});
|
||||
|
||||
expect(historyButton.className).toContain('platform-navigable-list-item');
|
||||
await user.click(historyButton);
|
||||
expect(onOpenRecommendGalleryDetail).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('mobile home moves category shelf into game category channel', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = renderStatefulLoggedOutHomeView({
|
||||
|
||||
@@ -87,6 +87,7 @@ import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import { PlatformIconBadge } from '../common/PlatformIconBadge';
|
||||
import { PlatformIconButton } from '../common/PlatformIconButton';
|
||||
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformNavigableListItem } from '../common/PlatformNavigableListItem';
|
||||
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
||||
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
|
||||
import { PlatformStatusDialog } from '../common/PlatformStatusDialog';
|
||||
@@ -1918,7 +1919,7 @@ function DesktopTrendingItem({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="platform-desktop-trending-item flex items-center gap-4 px-4 py-4 text-left"
|
||||
className="platform-desktop-trending-item flex items-start gap-4 px-4 py-4 text-left"
|
||||
>
|
||||
<div className="relative h-[5.5rem] w-[4.3rem] shrink-0 overflow-hidden rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-[rgba(255,255,255,0.66)]">
|
||||
{coverImage ? (
|
||||
@@ -2282,11 +2283,19 @@ function PlatformWorkSearchResults({
|
||||
entry.summaryText || entry.subtitle || '等待补充世界摘要。';
|
||||
|
||||
return (
|
||||
<button
|
||||
<PlatformNavigableListItem
|
||||
key={`${buildPublicGalleryCardKey(entry)}:search-result`}
|
||||
type="button"
|
||||
onClick={() => onOpen(entry)}
|
||||
className="platform-desktop-trending-item flex w-full items-center justify-between gap-3 px-4 py-3 text-left"
|
||||
className="platform-desktop-trending-item px-4 py-3"
|
||||
trailing={
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="shrink-0 px-3 tracking-[0.18em]"
|
||||
>
|
||||
{describePlatformPublicWorkKind(entry)}
|
||||
</PlatformPillBadge>
|
||||
}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="line-clamp-1 text-sm font-semibold text-[var(--platform-text-strong)]">
|
||||
@@ -2300,14 +2309,7 @@ function PlatformWorkSearchResults({
|
||||
{summaryText}
|
||||
</div>
|
||||
</div>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="shrink-0 px-3 tracking-[0.18em]"
|
||||
>
|
||||
{describePlatformPublicWorkKind(entry)}
|
||||
</PlatformPillBadge>
|
||||
</button>
|
||||
</PlatformNavigableListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -4723,11 +4725,21 @@ export function RpgEntryHomeView({
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
<PlatformNavigableListItem
|
||||
key={`${entry.ownerUserId}:${entry.profileId}:desktop-mine`}
|
||||
type="button"
|
||||
onClick={() => onOpenLibraryDetail(entry)}
|
||||
className="platform-desktop-trending-item flex w-full items-center justify-between gap-3 px-4 py-4 text-left"
|
||||
className="platform-desktop-trending-item px-4 py-4"
|
||||
trailing={
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
>
|
||||
{entry.visibility === 'published'
|
||||
? '已发布'
|
||||
: '草稿'}
|
||||
</PlatformPillBadge>
|
||||
}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="line-clamp-1 text-base font-semibold text-[var(--platform-text-strong)]">
|
||||
@@ -4739,16 +4751,7 @@ export function RpgEntryHomeView({
|
||||
: '草稿待完善'}
|
||||
</div>
|
||||
</div>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
>
|
||||
{entry.visibility === 'published'
|
||||
? '已发布'
|
||||
: '草稿'}
|
||||
</PlatformPillBadge>
|
||||
</button>
|
||||
</PlatformNavigableListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -4760,9 +4763,8 @@ export function RpgEntryHomeView({
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
<PlatformNavigableListItem
|
||||
key={`${entry.ownerUserId}:${entry.profileId}:desktop-history`}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
openRecommendGalleryDetail({
|
||||
ownerUserId: entry.ownerUserId,
|
||||
@@ -4783,7 +4785,16 @@ export function RpgEntryHomeView({
|
||||
likeCount: 0,
|
||||
})
|
||||
}
|
||||
className="platform-desktop-trending-item flex w-full items-center justify-between gap-3 px-4 py-4 text-left"
|
||||
className="platform-desktop-trending-item px-4 py-4"
|
||||
trailing={
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
>
|
||||
浏览
|
||||
</PlatformPillBadge>
|
||||
}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="line-clamp-1 text-base font-semibold text-[var(--platform-text-strong)]">
|
||||
@@ -4793,14 +4804,7 @@ export function RpgEntryHomeView({
|
||||
作者:{entry.authorDisplayName}
|
||||
</div>
|
||||
</div>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
>
|
||||
浏览
|
||||
</PlatformPillBadge>
|
||||
</button>
|
||||
</PlatformNavigableListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user