继续沉淀工具信息弹窗与个人中心内容骨架

新增PlatformUtilityInfoModal统一工具信息弹窗白底骨架
收口profile副弹层的摘要头列表骨架与内容行
同步更新PlatformUiKit收口计划与共享决策记录
This commit is contained in:
2026-06-11 06:07:30 +08:00
parent ef6a2b7200
commit d08842b576
15 changed files with 396 additions and 103 deletions

View File

@@ -0,0 +1,63 @@
import type { ReactNode } from 'react';
import { PlatformSubpanel } from './PlatformSubpanel';
type PlatformProfileContentRowProps = {
as?: 'div' | 'button';
children: ReactNode;
className?: string;
surface?: 'platform' | 'flat' | 'soft';
radius?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
padding?: 'none' | 'row' | 'xs' | 'sm' | 'md' | 'lg' | 'tight';
interactive?: boolean;
disabled?: boolean;
type?: 'button' | 'submit' | 'reset';
onClick?: () => void;
};
/**
* 个人中心白底 modal 内容行骨架。
* 只承接常见的 row 外壳与可点击语义,具体字段布局仍留在业务 modal 内部。
*/
export function PlatformProfileContentRow({
as = 'div',
children,
className,
surface = 'flat',
radius = 'xs',
padding = 'md',
interactive = as === 'button',
disabled,
type = 'button',
onClick,
}: PlatformProfileContentRowProps) {
if (as === 'button') {
return (
<PlatformSubpanel
as="button"
type={type}
onClick={onClick}
disabled={disabled}
interactive={interactive}
surface={surface}
radius={radius}
padding={padding}
className={className}
>
{children}
</PlatformSubpanel>
);
}
return (
<PlatformSubpanel
as="div"
surface={surface}
radius={radius}
padding={padding}
className={className}
>
{children}
</PlatformSubpanel>
);
}

View File

@@ -0,0 +1,51 @@
/* @vitest-environment jsdom */
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, test, vi } from 'vitest';
import { PlatformProfileContentRow } from './PlatformProfileContentRow';
import { PlatformProfileSkeletonList } from './PlatformProfileSkeletonList';
import { PlatformProfileSummaryHeader } from './PlatformProfileSummaryHeader';
test('platform profile summary header renders kicker, title and badge slot', () => {
render(
<PlatformProfileSummaryHeader
kicker="PLAYED"
title="玩过"
badge={<span>1.5</span>}
/>,
);
expect(screen.getByText('PLAYED')).toBeTruthy();
expect(screen.getByText('玩过')).toBeTruthy();
expect(screen.getByText('1.5小时')).toBeTruthy();
});
test('platform profile skeleton list renders requested skeleton rows', () => {
const { container } = render(
<PlatformProfileSkeletonList
count={3}
containerClassName="space-y-3"
itemClassName="h-16"
/>,
);
expect(container.querySelectorAll('.animate-pulse')).toHaveLength(3);
expect(container.querySelector('.space-y-3')).toBeTruthy();
expect(container.querySelector('.h-16')).toBeTruthy();
});
test('platform profile content row preserves interactive button semantics', async () => {
const user = userEvent.setup();
const onClick = vi.fn();
render(
<PlatformProfileContentRow as="button" onClick={onClick}>
</PlatformProfileContentRow>,
);
await user.click(screen.getByRole('button', { name: '点击行' }));
expect(onClick).toHaveBeenCalledTimes(1);
});

View File

@@ -0,0 +1,31 @@
type PlatformProfileSkeletonListProps = {
count: number;
containerClassName?: string;
itemClassName?: string;
};
/**
* 个人中心白底 modal 列表读取骨架。
* 只负责重复 skeleton 行与容器节奏,行高和栅格继续由调用方微调。
*/
export function PlatformProfileSkeletonList({
count,
containerClassName,
itemClassName,
}: PlatformProfileSkeletonListProps) {
return (
<div className={containerClassName}>
{Array.from({ length: count }).map((_, index) => (
<div
key={index}
className={[
'animate-pulse rounded-xl bg-zinc-100',
itemClassName,
]
.filter(Boolean)
.join(' ')}
/>
))}
</div>
);
}

View File

@@ -0,0 +1,39 @@
import type { ReactNode } from 'react';
type PlatformProfileSummaryHeaderProps = {
kicker: ReactNode;
title: ReactNode;
badge?: ReactNode;
className?: string;
titleClassName?: string;
badgeClassName?: string;
};
/**
* 个人中心白底副弹层标题摘要骨架。
* 只收口 kicker、标题和摘要 badge 的层次,不承接业务数值拼装。
*/
export function PlatformProfileSummaryHeader({
kicker,
title,
badge,
className,
titleClassName,
badgeClassName,
}: PlatformProfileSummaryHeaderProps) {
return (
<div className={['pr-10', className].filter(Boolean).join(' ')}>
<div className="text-[10px] font-black tracking-[0.22em] text-[#ff4056]">
{kicker}
</div>
<div className={['mt-1 text-2xl font-black', titleClassName].filter(Boolean).join(' ')}>
{title}
</div>
{badge ? (
<div className={['mt-3', badgeClassName].filter(Boolean).join(' ')}>
{badge}
</div>
) : null}
</div>
);
}

View File

@@ -2,7 +2,7 @@ import { useEffect, useMemo } from 'react';
import { CopyFeedbackButton } from './CopyFeedbackButton';
import { PlatformInfoBlock } from './PlatformInfoBlock';
import { UnifiedModal } from './UnifiedModal';
import { PlatformUtilityInfoModal } from './PlatformUtilityInfoModal';
import { useCopyFeedback } from './useCopyFeedback';
export type PlatformReportDialogField = {
@@ -31,8 +31,8 @@ export function PlatformReportDialog({
onClose,
copyIdleLabel,
fields,
overlayClassName = 'platform-theme platform-theme--light !items-center',
panelClassName = 'platform-remap-surface rounded-[1.5rem]',
overlayClassName,
panelClassName = 'rounded-[1.5rem]',
}: PlatformReportDialogProps) {
const { copyState, copyText, resetCopyState } = useCopyFeedback();
const reportText = useMemo(() => buildPlatformReportText(fields), [fields]);
@@ -50,14 +50,13 @@ export function PlatformReportDialog({
};
return (
<UnifiedModal
<PlatformUtilityInfoModal
open={open}
title={title}
onClose={onClose}
size="sm"
overlayClassName={overlayClassName}
panelClassName={panelClassName}
bodyClassName="space-y-3 px-4 py-4 sm:px-5 sm:py-5"
bodyClassName="space-y-3"
footerClassName="justify-end px-4 py-4 sm:px-5"
footer={
<CopyFeedbackButton
@@ -82,6 +81,6 @@ export function PlatformReportDialog({
</PlatformInfoBlock>
))
: null}
</UnifiedModal>
</PlatformUtilityInfoModal>
);
}

View File

@@ -0,0 +1,51 @@
/* @vitest-environment jsdom */
import { render, screen, within } from '@testing-library/react';
import { expect, test } from 'vitest';
import { PlatformUtilityInfoModal } from './PlatformUtilityInfoModal';
test('renders platform utility info modal shell with default platform styling', () => {
render(
<PlatformUtilityInfoModal
open
title="工具信息"
onClose={() => {}}
footer={<button type="button"></button>}
panelClassName="rounded-[1.5rem]"
>
<div></div>
</PlatformUtilityInfoModal>,
);
const dialog = screen.getByRole('dialog', { name: '工具信息' });
expect(dialog.parentElement?.className).toContain('platform-theme--light');
expect(dialog.parentElement?.className).toContain('!items-center');
expect(dialog.className).toContain('platform-remap-surface');
expect(dialog.className).toContain('rounded-[1.5rem]');
expect(within(dialog).getByText('这里是正文')).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '知道了' })).toBeTruthy();
});
test('supports custom theme and spacing overrides', () => {
render(
<PlatformUtilityInfoModal
open
title="工具信息"
onClose={() => {}}
platformTheme="dark"
bodyClassName="space-y-3"
footerClassName="justify-center pt-0"
footer={<button type="button"></button>}
>
<div></div>
</PlatformUtilityInfoModal>,
);
const dialog = screen.getByRole('dialog', { name: '工具信息' });
expect(dialog.parentElement?.className).toContain('platform-theme--dark');
expect(dialog.querySelector('.space-y-3')).toBeTruthy();
expect(dialog.querySelector('.justify-center')).toBeTruthy();
expect(dialog.querySelector('.pt-0')).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '继续' })).toBeTruthy();
});

View File

@@ -0,0 +1,63 @@
import type { ReactNode } from 'react';
import { UnifiedModal } from './UnifiedModal';
type PlatformUtilityInfoModalProps = {
open: boolean;
title: string;
onClose: () => void;
children: ReactNode;
footer?: ReactNode;
platformTheme?: 'light' | 'dark';
overlayClassName?: string;
panelClassName?: string;
bodyClassName?: string;
footerClassName?: string;
};
function joinClassNames(...classNames: Array<string | null | undefined | false>) {
return classNames.filter(Boolean).join(' ');
}
/**
* 工具信息类弹窗统一走平台主题 overlay + 白底 panel 壳层。
* 这里只收口通用窗口骨架,字段内容、分享渠道和复制按钮仍由业务组件自己渲染。
*/
export function PlatformUtilityInfoModal({
open,
title,
onClose,
children,
footer,
platformTheme = 'light',
overlayClassName,
panelClassName,
bodyClassName,
footerClassName,
}: PlatformUtilityInfoModalProps) {
return (
<UnifiedModal
open={open}
title={title}
onClose={onClose}
size="sm"
overlayClassName={joinClassNames(
`platform-theme platform-theme--${platformTheme}`,
'!items-center',
overlayClassName,
)}
panelClassName={joinClassNames('platform-remap-surface', panelClassName)}
bodyClassName={joinClassNames(
'space-y-4 px-4 py-4 sm:px-5 sm:py-5',
bodyClassName,
)}
footer={footer}
footerClassName={joinClassNames(
'px-4 py-4 sm:px-5',
footerClassName,
)}
>
{children}
</UnifiedModal>
);
}

View File

@@ -4,11 +4,11 @@ import { useAuthUi } from '../auth/AuthUiContext';
import { CopyFeedbackButton } from './CopyFeedbackButton';
import { PlatformInfoBlock } from './PlatformInfoBlock';
import { PlatformSubpanel } from './PlatformSubpanel';
import { PlatformUtilityInfoModal } from './PlatformUtilityInfoModal';
import {
buildPublishShareText,
type PublishShareModalPayload,
} from './publishShareModalModel';
import { UnifiedModal } from './UnifiedModal';
import { useCopyFeedback } from './useCopyFeedback';
type PublishShareModalProps = {
@@ -115,14 +115,12 @@ export function PublishShareModal({
};
return (
<UnifiedModal
<PlatformUtilityInfoModal
open={open && Boolean(payload)}
title="分享给朋友"
onClose={onClose}
size="sm"
overlayClassName={`platform-theme platform-theme--${platformTheme} !items-center`}
panelClassName="platform-remap-surface rounded-[1.75rem]"
bodyClassName="space-y-4 px-4 py-4 sm:px-5 sm:py-5"
platformTheme={platformTheme}
panelClassName="rounded-[1.75rem]"
footerClassName="justify-center border-t-0 px-4 pb-5 pt-0 sm:px-5"
footer={
<div className="grid w-full grid-cols-3 gap-3">
@@ -168,6 +166,6 @@ export function PublishShareModal({
actionFullWidth
className="disabled:opacity-55"
/>
</UnifiedModal>
</PlatformUtilityInfoModal>
);
}

View File

@@ -8,9 +8,11 @@ import type {
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformProfileContentRow } from '../common/PlatformProfileContentRow';
import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList';
import { PlatformProfileSummaryHeader } from '../common/PlatformProfileSummaryHeader';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { ResolvedAssetImage } from '../ResolvedAssetImage';
import { PlatformProfileSecondaryModalShell } from './PlatformProfileModalShell';
import {
@@ -143,19 +145,19 @@ export function PlatformProfilePlayedWorksModal({
panelClassName="relative !max-h-[min(92vh,42rem)] !max-w-[38rem] bg-white text-zinc-950 shadow-2xl !rounded-[1.35rem] sm:!rounded-[1.35rem]"
contentClassName="relative max-h-[min(92vh,42rem)] overflow-y-auto px-4 pb-5 pt-4 sm:px-5"
>
<div className="pr-10">
<div className="text-[10px] font-black tracking-[0.22em] text-[#ff4056]">
PLAYED
</div>
<div className="mt-1 text-2xl font-black"></div>
<PlatformPillBadge
tone="profile"
icon={<Clock3 className="h-3.5 w-3.5 text-[#ff4056]" />}
className="mt-2"
>
{formatTotalPlayTimeHours(stats?.totalPlayTimeMs ?? 0)}
</PlatformPillBadge>
</div>
<PlatformProfileSummaryHeader
kicker="PLAYED"
title="玩过"
badge={
<PlatformPillBadge
tone="profile"
icon={<Clock3 className="h-3.5 w-3.5 text-[#ff4056]" />}
>
{formatTotalPlayTimeHours(stats?.totalPlayTimeMs ?? 0)}
</PlatformPillBadge>
}
badgeClassName="mt-2"
/>
{error ? (
<PlatformStatusMessage tone="error" className="mt-4">
@@ -171,14 +173,11 @@ export function PlatformProfilePlayedWorksModal({
<PlatformAsyncStatePanel
isLoading={isLoading}
loadingState={
<div className="mt-5 space-y-3">
{Array.from({ length: 4 }).map((_, index) => (
<div
key={index}
className="h-20 animate-pulse rounded-xl bg-zinc-100"
/>
))}
</div>
<PlatformProfileSkeletonList
count={4}
containerClassName="mt-5 space-y-3"
itemClassName="h-20"
/>
}
isEmpty={!hasArchiveEntries && !hasPlayedWorks}
emptyState={
@@ -218,9 +217,8 @@ export function PlatformProfilePlayedWorksModal({
</PlatformFieldLabel>
<div className="space-y-3">
{playedWorks.map((work) => (
<PlatformSubpanel
<PlatformProfileContentRow
as="button"
type="button"
key={`${work.worldKey}:${work.lastPlayedAt}`}
onClick={() => onOpenWork?.(work)}
surface="flat"
@@ -244,20 +242,20 @@ export function PlatformProfilePlayedWorksModal({
tone="profileAccent"
size="xs"
className="shrink-0 border-transparent"
>
{formatPlayedWorkType(work.worldType)}
</PlatformPillBadge>
</div>
<div className="mt-3 grid gap-2 text-xs text-zinc-500 sm:grid-cols-3">
>
{formatPlayedWorkType(work.worldType)}
</PlatformPillBadge>
</div>
<div className="mt-3 grid gap-2 text-xs text-zinc-500 sm:grid-cols-3">
<span className="truncate"> {formatPlayedWorkId(work)}</span>
<span className="truncate">
{formatSnapshotTime(work.lastPlayedAt)}
</span>
<span className="truncate">
{formatCompactPlayTime(work.lastObservedPlayTimeMs)}
</span>
</div>
</PlatformSubpanel>
{formatCompactPlayTime(work.lastObservedPlayTimeMs)}
</span>
</div>
</PlatformProfileContentRow>
))}
</div>
</section>

View File

@@ -9,6 +9,7 @@ import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList';
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
@@ -220,14 +221,11 @@ export function PlatformProfileRechargeModal({
}
isLoading={isLoading}
loadingState={
<div className="mt-4 grid gap-3 sm:grid-cols-2">
{Array.from({ length: 4 }).map((_, index) => (
<div
key={index}
className="h-28 animate-pulse rounded-[1.15rem] bg-white/10"
/>
))}
</div>
<PlatformProfileSkeletonList
count={4}
containerClassName="mt-4 grid gap-3 sm:grid-cols-2"
itemClassName="h-28 rounded-[1.15rem] bg-white/10"
/>
}
isEmpty={products.length === 0}
emptyState={

View File

@@ -9,6 +9,8 @@ import { CopyFeedbackButton } from '../common/CopyFeedbackButton';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformProfileContentRow } from '../common/PlatformProfileContentRow';
import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformTextField } from '../common/PlatformTextField';
@@ -139,10 +141,11 @@ export function PlatformProfileReferralModal({
<PlatformAsyncStatePanel
isLoading={isLoading}
loadingState={
<div className="mt-5 space-y-3">
<div className="h-12 animate-pulse rounded-xl bg-zinc-100" />
<div className="h-11 animate-pulse rounded-xl bg-zinc-100" />
</div>
<PlatformProfileSkeletonList
count={2}
containerClassName="mt-5 space-y-3"
itemClassName="h-12 even:h-11"
/>
}
isEmpty={Boolean(center?.hasRedeemedCode)}
emptyState={
@@ -193,10 +196,11 @@ export function PlatformProfileReferralModal({
<PlatformAsyncStatePanel
isLoading={isLoading}
loadingState={
<div className="mt-5 space-y-3">
<div className="h-20 animate-pulse rounded-xl bg-zinc-100" />
<div className="h-10 animate-pulse rounded-xl bg-zinc-100" />
</div>
<PlatformProfileSkeletonList
count={2}
containerClassName="mt-5 space-y-3"
itemClassName="h-20 odd:h-20 even:h-10"
/>
}
>
<div className="mt-5 space-y-3">
@@ -251,8 +255,7 @@ export function PlatformProfileReferralModal({
{center?.invitedUsers?.length ? (
<div className="mt-3 max-h-44 space-y-2 overflow-y-auto pr-1">
{center.invitedUsers.map((user) => (
<PlatformSubpanel
as="div"
<PlatformProfileContentRow
key={`${user.userId}-${user.boundAt}`}
surface="soft"
radius="xs"
@@ -268,7 +271,7 @@ export function PlatformProfileReferralModal({
{user.displayName || '玩家'}
</div>
</div>
</PlatformSubpanel>
</PlatformProfileContentRow>
))}
</div>
) : (

View File

@@ -1,9 +1,10 @@
import type { ProfileTaskCenterResponse } from '../../../packages/shared/src/contracts/runtime';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformProfileContentRow } from '../common/PlatformProfileContentRow';
import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformProfileModalShell } from './PlatformProfileModalShell';
import {
buildProfileTaskProgressLabel,
@@ -84,14 +85,11 @@ export function PlatformProfileTaskCenterModal({
}
isLoading={isLoading}
loadingState={
<div className="space-y-3">
{Array.from({ length: 2 }).map((_, index) => (
<div
key={index}
className="h-20 animate-pulse rounded-2xl bg-white/10"
/>
))}
</div>
<PlatformProfileSkeletonList
count={2}
containerClassName="space-y-3"
itemClassName="h-20 rounded-2xl bg-white/10"
/>
}
isEmpty={tasks.length === 0}
emptyState={
@@ -107,8 +105,7 @@ export function PlatformProfileTaskCenterModal({
const progressLabel = buildProfileTaskProgressLabel(task);
return (
<PlatformSubpanel
as="div"
<PlatformProfileContentRow
key={task.taskId}
radius="sm"
padding="md"
@@ -141,7 +138,7 @@ export function PlatformProfileTaskCenterModal({
>
{getProfileTaskClaimButtonLabel(task, isClaiming)}
</PlatformActionButton>
</PlatformSubpanel>
</PlatformProfileContentRow>
);
})}
</div>

View File

@@ -4,9 +4,11 @@ import type { ProfileWalletLedgerResponse } from '../../../packages/shared/src/c
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformProfileContentRow } from '../common/PlatformProfileContentRow';
import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList';
import { PlatformProfileSummaryHeader } from '../common/PlatformProfileSummaryHeader';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformProfileSecondaryModalShell } from './PlatformProfileModalShell';
import { buildWalletLedgerPresentation } from '../rpg-entry/rpgEntryProfileFundsViewModel';
import { formatPlatformWorldTime } from '../rpg-entry/rpgEntryWorldPresentation';
@@ -47,19 +49,19 @@ export function PlatformProfileWalletLedgerModal({
panelClassName="relative !max-h-[min(92vh,42rem)] !max-w-[30rem] bg-[linear-gradient(180deg,#fff7f8_0%,#ffffff_38%,#f8fafc_100%)] text-zinc-950 shadow-2xl !rounded-[1.35rem] sm:!rounded-[1.35rem]"
contentClassName="relative max-h-[min(92vh,42rem)] overflow-y-auto px-4 pb-5 pt-4 sm:px-5"
>
<div className="pr-10">
<div className="text-[10px] font-black tracking-[0.22em] text-[#ff4056]">
LEDGER
</div>
<div className="mt-1 text-2xl font-black"></div>
<PlatformPillBadge
tone="profile"
icon={<Coins className="h-3.5 w-3.5 text-[#ff4056]" />}
className="mt-3 bg-white/70"
>
{walletLedgerPresentation.balanceLabel}
</PlatformPillBadge>
</div>
<PlatformProfileSummaryHeader
kicker="LEDGER"
title="泥点账单"
badge={
<PlatformPillBadge
tone="profile"
icon={<Coins className="h-3.5 w-3.5 text-[#ff4056]" />}
className="bg-white/70"
>
{walletLedgerPresentation.balanceLabel}
</PlatformPillBadge>
}
/>
<PlatformAsyncStatePanel
errorState={
@@ -83,14 +85,11 @@ export function PlatformProfileWalletLedgerModal({
}
isLoading={isLoading}
loadingState={
<div className="mt-5 space-y-3">
{Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className="h-16 animate-pulse rounded-xl bg-zinc-100"
/>
))}
</div>
<PlatformProfileSkeletonList
count={5}
containerClassName="mt-5 space-y-3"
itemClassName="h-16"
/>
}
isEmpty={entries.length === 0}
emptyState={
@@ -105,8 +104,7 @@ export function PlatformProfileWalletLedgerModal({
>
<div className="mt-5 space-y-2.5">
{entries.map((entry) => (
<PlatformSubpanel
as="div"
<PlatformProfileContentRow
key={entry.id}
surface="flat"
radius="xs"
@@ -133,7 +131,7 @@ export function PlatformProfileWalletLedgerModal({
{entry.balanceLabel}
</div>
</div>
</PlatformSubpanel>
</PlatformProfileContentRow>
))}
</div>
</PlatformAsyncStatePanel>