沉淀 PlatformDetailTopbar 与 PlatformDetailShareActions 共享骨架 接入 RPG 世界详情与公开作品详情的返回复制分享动作组合 补充测试护栏与文档决策记录
144 lines
4.2 KiB
TypeScript
144 lines
4.2 KiB
TypeScript
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>
|
|
);
|
|
}
|