继续收口详情页头部动作组合
沉淀 PlatformDetailTopbar 与 PlatformDetailShareActions 共享骨架 接入 RPG 世界详情与公开作品详情的返回复制分享动作组合 补充测试护栏与文档决策记录
This commit is contained in:
52
src/components/common/PlatformDetailShareActions.test.tsx
Normal file
52
src/components/common/PlatformDetailShareActions.test.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { PlatformDetailShareActions } from './PlatformDetailShareActions';
|
||||
|
||||
test('renders overlay detail share actions with copied share state', () => {
|
||||
render(
|
||||
<PlatformDetailShareActions
|
||||
workCode="CW-001"
|
||||
copyState="idle"
|
||||
onCopyWorkCode={vi.fn()}
|
||||
shareState="copied"
|
||||
onShare={vi.fn()}
|
||||
shareAriaLabel="分享作品 测试世界"
|
||||
leading={<span>已发布</span>}
|
||||
variant="overlay"
|
||||
/>,
|
||||
);
|
||||
|
||||
const codeButton = screen.getByRole('button', { name: '复制作品号 CW-001' });
|
||||
const shareButton = screen.getByRole('button', { name: '分享作品 测试世界' });
|
||||
|
||||
expect(screen.getByText('已发布')).toBeTruthy();
|
||||
expect(codeButton.className).toContain('bg-white/72');
|
||||
expect(codeButton.className).toContain('tracking-[0.18em]');
|
||||
expect(shareButton.className).toContain('bg-white/72');
|
||||
expect(screen.getByText('已复制')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('renders solid detail share actions with compact work code chip', () => {
|
||||
render(
|
||||
<PlatformDetailShareActions
|
||||
workCode="PZ-001"
|
||||
copyState="idle"
|
||||
onCopyWorkCode={vi.fn()}
|
||||
shareState="idle"
|
||||
onShare={vi.fn()}
|
||||
shareAriaLabel="分享作品 拼图世界"
|
||||
leading={<span>已发布</span>}
|
||||
variant="solid"
|
||||
/>,
|
||||
);
|
||||
|
||||
const codeButton = screen.getByRole('button', { name: 'PZ-001' });
|
||||
const shareButton = screen.getByRole('button', { name: '分享作品 拼图世界' });
|
||||
|
||||
expect(codeButton.className).toContain('bg-[var(--platform-neutral-bg)]');
|
||||
expect(shareButton.className).toContain('bg-[var(--platform-neutral-bg)]');
|
||||
expect(screen.getByText('已发布')).toBeTruthy();
|
||||
});
|
||||
143
src/components/common/PlatformDetailShareActions.tsx
Normal file
143
src/components/common/PlatformDetailShareActions.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { Copy, Share2 } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { CopyCodeButton } from './CopyCodeButton';
|
||||
import { CopyFeedbackButton } from './CopyFeedbackButton';
|
||||
import type { CopyFeedbackState } from './useCopyFeedback';
|
||||
|
||||
type PlatformDetailShareActionsProps = {
|
||||
workCode?: string | null;
|
||||
copyState: CopyFeedbackState;
|
||||
onCopyWorkCode?: () => void;
|
||||
shareState: CopyFeedbackState;
|
||||
onShare?: () => void;
|
||||
shareAriaLabel?: string;
|
||||
shareTitle?: string;
|
||||
leading?: ReactNode;
|
||||
showCopyAction?: boolean;
|
||||
showShareAction?: boolean;
|
||||
variant?: 'overlay' | 'solid';
|
||||
className?: string;
|
||||
copyClassName?: string;
|
||||
shareClassName?: string;
|
||||
copyCodeLabel?: ReactNode;
|
||||
copyAccessibleLabel?: string;
|
||||
};
|
||||
|
||||
const VARIANT_COPY_CLASS = {
|
||||
overlay: 'px-3 tracking-[0.18em]',
|
||||
solid: '',
|
||||
} as const;
|
||||
|
||||
const VARIANT_SHARE_CLASS = {
|
||||
overlay: 'px-3 tracking-[0.18em]',
|
||||
solid: '',
|
||||
} as const;
|
||||
|
||||
const VARIANT_PILL_TONE = {
|
||||
overlay: 'neutral',
|
||||
solid: 'neutralSolid',
|
||||
} as const;
|
||||
|
||||
const VARIANT_PILL_SIZE = {
|
||||
overlay: 'xxs',
|
||||
solid: 'sm',
|
||||
} as const;
|
||||
|
||||
const VARIANT_ICON_CLASS = {
|
||||
overlay: 'h-3 w-3',
|
||||
solid: 'h-4 w-4',
|
||||
} as const;
|
||||
|
||||
const VARIANT_SUFFIX_CLASS = {
|
||||
overlay: 'text-xs',
|
||||
solid: 'text-[11px]',
|
||||
} as const;
|
||||
|
||||
function renderShareLabel(suffix: ReactNode | null, suffixClassName: string) {
|
||||
return (
|
||||
<>
|
||||
<span>分享作品</span>
|
||||
{suffix ? <span className={suffixClassName}>{suffix}</span> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页作品号 / 分享动作组合。
|
||||
* 共享层只承接状态 badge 槽位、复制作品号和分享按钮这组稳定骨架。
|
||||
*/
|
||||
export function PlatformDetailShareActions({
|
||||
workCode,
|
||||
copyState,
|
||||
onCopyWorkCode,
|
||||
shareState,
|
||||
onShare,
|
||||
shareAriaLabel,
|
||||
shareTitle = '分享作品',
|
||||
leading,
|
||||
showCopyAction = true,
|
||||
showShareAction = true,
|
||||
variant = 'overlay',
|
||||
className,
|
||||
copyClassName,
|
||||
shareClassName,
|
||||
copyCodeLabel,
|
||||
copyAccessibleLabel,
|
||||
}: PlatformDetailShareActionsProps) {
|
||||
const canShowCopyAction = showCopyAction && Boolean(workCode);
|
||||
const canShowShareAction = showShareAction && Boolean(workCode);
|
||||
|
||||
if (!leading && !canShowCopyAction && !canShowShareAction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const iconClassName = VARIANT_ICON_CLASS[variant];
|
||||
const shareSuffixClassName = VARIANT_SUFFIX_CLASS[variant];
|
||||
const resolvedCopyCodeLabel =
|
||||
copyCodeLabel ?? (variant === 'solid' ? null : '作品号');
|
||||
const resolvedCopyAccessibleLabel =
|
||||
copyAccessibleLabel ?? (variant === 'solid' ? workCode ?? undefined : undefined);
|
||||
|
||||
return (
|
||||
<div className={['flex flex-wrap items-center gap-2', className].filter(Boolean).join(' ')}>
|
||||
{leading}
|
||||
{canShowCopyAction ? (
|
||||
<CopyCodeButton
|
||||
state={copyState}
|
||||
code={workCode ?? ''}
|
||||
codeLabel={resolvedCopyCodeLabel}
|
||||
accessibleLabel={resolvedCopyAccessibleLabel}
|
||||
title="复制作品号"
|
||||
onClick={onCopyWorkCode}
|
||||
disabled={!onCopyWorkCode}
|
||||
actionAppearance="pill"
|
||||
actionPillTone={VARIANT_PILL_TONE[variant]}
|
||||
actionPillSize={VARIANT_PILL_SIZE[variant]}
|
||||
className={[VARIANT_COPY_CLASS[variant], copyClassName].filter(Boolean).join(' ')}
|
||||
idleIcon={<Copy className={iconClassName} />}
|
||||
copiedIcon={<Copy className={iconClassName} />}
|
||||
suffixClassName={shareSuffixClassName}
|
||||
/>
|
||||
) : null}
|
||||
{canShowShareAction ? (
|
||||
<CopyFeedbackButton
|
||||
state={shareState}
|
||||
onClick={onShare}
|
||||
disabled={!onShare}
|
||||
actionAppearance="pill"
|
||||
actionPillTone={VARIANT_PILL_TONE[variant]}
|
||||
actionPillSize={VARIANT_PILL_SIZE[variant]}
|
||||
className={[VARIANT_SHARE_CLASS[variant], shareClassName].filter(Boolean).join(' ')}
|
||||
aria-label={shareAriaLabel}
|
||||
title={shareTitle}
|
||||
idleLabel={renderShareLabel(null, shareSuffixClassName)}
|
||||
copiedLabel={renderShareLabel('已复制', shareSuffixClassName)}
|
||||
failedLabel={renderShareLabel('复制失败', shareSuffixClassName)}
|
||||
idleIcon={<Share2 className={iconClassName} />}
|
||||
copiedIcon={<Share2 className={iconClassName} />}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
src/components/common/PlatformDetailTopbar.test.tsx
Normal file
49
src/components/common/PlatformDetailTopbar.test.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { PlatformDetailTopbar } from './PlatformDetailTopbar';
|
||||
|
||||
test('renders pill back action with trailing slot', () => {
|
||||
const onBack = vi.fn();
|
||||
|
||||
render(
|
||||
<PlatformDetailTopbar
|
||||
onBack={onBack}
|
||||
className="grid-cols-[auto,minmax(0,1fr),auto]"
|
||||
backButtonClassName="px-3"
|
||||
trailing={<span>已发布</span>}
|
||||
/>,
|
||||
);
|
||||
|
||||
const button = screen.getByRole('button', { name: '返回' });
|
||||
|
||||
expect(button.className).toContain('platform-button--ghost');
|
||||
expect(button.className).toContain('px-3');
|
||||
expect(screen.getByText('已发布')).toBeTruthy();
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(onBack).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('renders icon back action and centered title', () => {
|
||||
render(
|
||||
<PlatformDetailTopbar
|
||||
onBack={vi.fn()}
|
||||
backVariant="icon"
|
||||
backButtonClassName="detail-icon-back"
|
||||
title="详情"
|
||||
titleClassName="detail-topbar-title"
|
||||
trailing={<span className="invisible">占位</span>}
|
||||
/>,
|
||||
);
|
||||
|
||||
const button = screen.getByRole('button', { name: '返回' });
|
||||
const title = screen.getByText('详情');
|
||||
|
||||
expect(button.className).toContain('platform-icon-button');
|
||||
expect(button.className).toContain('detail-icon-back');
|
||||
expect(title.className).toContain('detail-topbar-title');
|
||||
});
|
||||
89
src/components/common/PlatformDetailTopbar.tsx
Normal file
89
src/components/common/PlatformDetailTopbar.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { PlatformBackActionButton } from './PlatformBackActionButton';
|
||||
import { PlatformIconButton } from './PlatformIconButton';
|
||||
|
||||
type PlatformDetailTopbarProps = {
|
||||
onBack: () => void;
|
||||
title?: ReactNode;
|
||||
trailing?: ReactNode;
|
||||
backVariant?: 'icon' | 'pill';
|
||||
backLabel?: string;
|
||||
className?: string;
|
||||
backButtonClassName?: string;
|
||||
titleClassName?: string;
|
||||
trailingClassName?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 详情页顶部动作骨架。
|
||||
* 只统一返回、标题和右侧动作槽位的布局,不吸收页面自己的标题文案或业务动作。
|
||||
*/
|
||||
export function PlatformDetailTopbar({
|
||||
onBack,
|
||||
title,
|
||||
trailing,
|
||||
backVariant = 'pill',
|
||||
backLabel = '返回',
|
||||
className,
|
||||
backButtonClassName,
|
||||
titleClassName,
|
||||
trailingClassName,
|
||||
}: PlatformDetailTopbarProps) {
|
||||
const backAction =
|
||||
backVariant === 'icon' ? (
|
||||
<PlatformIconButton
|
||||
label={backLabel}
|
||||
title={backLabel}
|
||||
className={backButtonClassName}
|
||||
onClick={onBack}
|
||||
icon={<ArrowLeft className="h-6 w-6" />}
|
||||
/>
|
||||
) : (
|
||||
<PlatformBackActionButton
|
||||
onClick={onBack}
|
||||
label={backLabel}
|
||||
className={backButtonClassName}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
'grid min-w-0 grid-cols-[auto,minmax(0,1fr),auto] items-center gap-3',
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className="min-w-0 justify-self-start">
|
||||
{backAction}
|
||||
</div>
|
||||
{title ? (
|
||||
<div
|
||||
className={[
|
||||
'min-w-0 text-center',
|
||||
titleClassName,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
) : (
|
||||
<div aria-hidden="true" />
|
||||
)}
|
||||
<div
|
||||
className={[
|
||||
'min-w-0 justify-self-end',
|
||||
trailingClassName,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{trailing}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import {
|
||||
ArrowLeft,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
CircleHelp,
|
||||
Clock3,
|
||||
Copy,
|
||||
Gamepad2,
|
||||
GitFork,
|
||||
Heart,
|
||||
@@ -16,8 +14,9 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
|
||||
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
|
||||
import { CopyCodeButton } from '../common/CopyCodeButton';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformDetailShareActions } from '../common/PlatformDetailShareActions';
|
||||
import { PlatformDetailTopbar } from '../common/PlatformDetailTopbar';
|
||||
import { PlatformIconButton } from '../common/PlatformIconButton';
|
||||
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
@@ -254,24 +253,27 @@ export function PlatformWorkDetailView({
|
||||
|
||||
return (
|
||||
<div className="platform-work-detail">
|
||||
<div className="platform-work-detail__topbar">
|
||||
<PlatformIconButton
|
||||
label="返回"
|
||||
className="platform-work-detail__icon-button"
|
||||
onClick={onBack}
|
||||
title="返回"
|
||||
icon={<ArrowLeft className="h-6 w-6" />}
|
||||
/>
|
||||
<div className="platform-work-detail__title">详情</div>
|
||||
<PlatformIconButton
|
||||
label="分享"
|
||||
className="platform-work-detail__icon-button"
|
||||
onClick={sharePublicWork}
|
||||
disabled={!publicWorkCode}
|
||||
title="分享"
|
||||
icon={<Share2 className="h-5 w-5" />}
|
||||
/>
|
||||
</div>
|
||||
<PlatformDetailTopbar
|
||||
onBack={onBack}
|
||||
backVariant="icon"
|
||||
title={
|
||||
<div className="platform-work-detail__title">
|
||||
详情
|
||||
</div>
|
||||
}
|
||||
className="platform-work-detail__topbar"
|
||||
backButtonClassName="platform-work-detail__icon-button"
|
||||
trailing={
|
||||
<PlatformIconButton
|
||||
label="分享"
|
||||
className="platform-work-detail__icon-button"
|
||||
onClick={sharePublicWork}
|
||||
disabled={!publicWorkCode}
|
||||
title="分享"
|
||||
icon={<Share2 className="h-5 w-5" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="platform-work-detail__scroll">
|
||||
<section className="platform-work-detail__cover">
|
||||
@@ -439,22 +441,18 @@ export function PlatformWorkDetailView({
|
||||
))}
|
||||
</div>
|
||||
<p className="platform-work-detail__copy">{entry.summaryText}</p>
|
||||
{publicWorkCode ? (
|
||||
<CopyCodeButton
|
||||
state={copyState}
|
||||
code={publicWorkCode}
|
||||
codeLabel={null}
|
||||
accessibleLabel={publicWorkCode}
|
||||
title="复制作品号"
|
||||
actionAppearance="pill"
|
||||
actionPillTone="neutralSolid"
|
||||
actionPillSize="sm"
|
||||
className="platform-work-detail__code"
|
||||
onClick={copyPublicWorkCode}
|
||||
idleIcon={<Copy className="h-4 w-4" />}
|
||||
copiedIcon={<Copy className="h-4 w-4" />}
|
||||
/>
|
||||
) : null}
|
||||
<PlatformDetailShareActions
|
||||
workCode={publicWorkCode}
|
||||
copyState={copyState}
|
||||
onCopyWorkCode={copyPublicWorkCode}
|
||||
shareState={shareState}
|
||||
onShare={sharePublicWork}
|
||||
shareAriaLabel={`分享作品 ${entry.worldName}`}
|
||||
leading={null}
|
||||
showShareAction={false}
|
||||
variant="solid"
|
||||
className="platform-work-detail__code"
|
||||
/>
|
||||
{shareState !== 'idle' ? (
|
||||
<PlatformStatusMessage
|
||||
tone={shareState === 'copied' ? 'success' : 'error'}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Copy, Share2 } from 'lucide-react';
|
||||
|
||||
import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime';
|
||||
import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets';
|
||||
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
import { CopyCodeButton } from '../common/CopyCodeButton';
|
||||
import { CopyFeedbackButton } from '../common/CopyFeedbackButton';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
|
||||
import { PlatformDetailShareActions } from '../common/PlatformDetailShareActions';
|
||||
import { PlatformDetailTopbar } from '../common/PlatformDetailTopbar';
|
||||
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
@@ -80,19 +77,20 @@ export function RpgEntryWorldDetailView({
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<div className="mb-4 flex items-center justify-between gap-3">
|
||||
<PlatformBackActionButton
|
||||
onClick={onBack}
|
||||
className="px-3"
|
||||
/>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xs"
|
||||
className="px-3 py-1.5 tracking-[0.08em]"
|
||||
>
|
||||
{entry.visibility === 'published' ? '已发布' : '草稿'}
|
||||
</PlatformPillBadge>
|
||||
</div>
|
||||
<PlatformDetailTopbar
|
||||
onBack={onBack}
|
||||
className="mb-4"
|
||||
backButtonClassName="px-3"
|
||||
trailing={
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xs"
|
||||
className="px-3 py-1.5 tracking-[0.08em]"
|
||||
>
|
||||
{entry.visibility === 'published' ? '已发布' : '草稿'}
|
||||
</PlatformPillBadge>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-1 scrollbar-hide">
|
||||
<div className="space-y-4 pb-2">
|
||||
@@ -114,72 +112,44 @@ export function RpgEntryWorldDetailView({
|
||||
) : null}
|
||||
<div className="absolute inset-0 bg-[var(--platform-hero-overlay-strong)]" />
|
||||
<div className="relative z-10">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<PlatformPillBadge
|
||||
tone="warning"
|
||||
size="xxs"
|
||||
className="tracking-[0.18em]"
|
||||
>
|
||||
{formatPlatformWorkDisplayTag(
|
||||
describePlatformThemeLabel(entry.themeMode),
|
||||
)}
|
||||
</PlatformPillBadge>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
>
|
||||
{entry.authorDisplayName}
|
||||
</PlatformPillBadge>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
>
|
||||
{entry.visibility === 'published'
|
||||
? `发布于 ${formatPlatformWorldTime(entry.publishedAt)}`
|
||||
: '仅自己可见'}
|
||||
</PlatformPillBadge>
|
||||
{publicWorkCode ? (
|
||||
<PlatformDetailShareActions
|
||||
workCode={publicWorkCode}
|
||||
copyState={copyState}
|
||||
onCopyWorkCode={copyPublicWorkCode}
|
||||
shareState={shareState}
|
||||
onShare={sharePublicWork}
|
||||
shareAriaLabel={`分享作品 ${entry.worldName}`}
|
||||
leading={
|
||||
<>
|
||||
<CopyCodeButton
|
||||
state={copyState}
|
||||
code={publicWorkCode}
|
||||
onClick={copyPublicWorkCode}
|
||||
actionAppearance="pill"
|
||||
actionPillSize="xxs"
|
||||
<PlatformPillBadge
|
||||
tone="warning"
|
||||
size="xxs"
|
||||
className="tracking-[0.18em]"
|
||||
>
|
||||
{formatPlatformWorkDisplayTag(
|
||||
describePlatformThemeLabel(entry.themeMode),
|
||||
)}
|
||||
</PlatformPillBadge>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
idleIcon={<Copy className="h-3 w-3" />}
|
||||
copiedIcon={<Copy className="h-3 w-3" />}
|
||||
suffixClassName="text-xs"
|
||||
/>
|
||||
<CopyFeedbackButton
|
||||
state={shareState}
|
||||
onClick={sharePublicWork}
|
||||
actionAppearance="pill"
|
||||
actionPillSize="xxs"
|
||||
>
|
||||
{entry.authorDisplayName}
|
||||
</PlatformPillBadge>
|
||||
<PlatformPillBadge
|
||||
tone="neutral"
|
||||
size="xxs"
|
||||
className="px-3 tracking-[0.18em]"
|
||||
aria-label={`分享作品 ${entry.worldName}`}
|
||||
title="分享作品"
|
||||
idleLabel="分享作品"
|
||||
copiedLabel={
|
||||
<>
|
||||
<span>分享作品</span>
|
||||
<span className="text-xs">已复制</span>
|
||||
</>
|
||||
}
|
||||
failedLabel={
|
||||
<>
|
||||
<span>分享作品</span>
|
||||
<span className="text-xs">复制失败</span>
|
||||
</>
|
||||
}
|
||||
idleIcon={<Share2 className="h-3 w-3" />}
|
||||
copiedIcon={<Share2 className="h-3 w-3" />}
|
||||
/>
|
||||
>
|
||||
{entry.visibility === 'published'
|
||||
? `发布于 ${formatPlatformWorldTime(entry.publishedAt)}`
|
||||
: '仅自己可见'}
|
||||
</PlatformPillBadge>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
variant="overlay"
|
||||
/>
|
||||
<div className="mt-4 text-3xl font-black text-white">
|
||||
{displayName}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user