继续收口详情页头部动作组合
沉淀 PlatformDetailTopbar 与 PlatformDetailShareActions 共享骨架 接入 RPG 世界详情与公开作品详情的返回复制分享动作组合 补充测试护栏与文档决策记录
This commit is contained in:
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user