06bf03a28c
作品架异步状态切换复用 PlatformAsyncStatePanel 复制反馈动作外观改为组合 PlatformActionButton 结果页与调试面板空态继续收口到 PlatformEmptyState 暗色私聊与工坊按钮改为复用 PlatformActionButton 更新 PlatformUiKit 收口文档与团队决策记录
158 lines
3.9 KiB
TypeScript
158 lines
3.9 KiB
TypeScript
import { Check, Copy } from 'lucide-react';
|
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
|
|
import {
|
|
type PlatformActionButtonSize,
|
|
type PlatformActionButtonShape,
|
|
type PlatformActionButtonSurface,
|
|
type PlatformActionButtonTone,
|
|
} from './platformActionButtonModel';
|
|
import { PlatformActionButton } from './PlatformActionButton';
|
|
import {
|
|
getPlatformPillBadgeClassName,
|
|
type PlatformPillBadgeSize,
|
|
type PlatformPillBadgeTone,
|
|
} from './platformPillBadgeModel';
|
|
import type { CopyFeedbackState } from './useCopyFeedback';
|
|
|
|
export type CopyFeedbackButtonActionAppearance = 'plain' | 'pill';
|
|
|
|
type CopyFeedbackButtonProps = Omit<
|
|
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
'children'
|
|
> & {
|
|
state: CopyFeedbackState;
|
|
idleLabel: ReactNode;
|
|
copiedLabel?: ReactNode;
|
|
failedLabel?: ReactNode;
|
|
idleIcon?: ReactNode;
|
|
copiedIcon?: ReactNode;
|
|
failedIcon?: ReactNode;
|
|
showIcon?: boolean;
|
|
showLabel?: boolean;
|
|
labelClassName?: string;
|
|
accessibleLabel?: string;
|
|
actionSurface?: PlatformActionButtonSurface;
|
|
actionTone?: PlatformActionButtonTone;
|
|
actionSize?: PlatformActionButtonSize;
|
|
actionShape?: PlatformActionButtonShape;
|
|
actionFullWidth?: boolean;
|
|
actionAppearance?: CopyFeedbackButtonActionAppearance;
|
|
actionPillTone?: PlatformPillBadgeTone;
|
|
actionPillSize?: PlatformPillBadgeSize;
|
|
};
|
|
|
|
function resolveCopyFeedbackLabel(
|
|
state: CopyFeedbackState,
|
|
idleLabel: ReactNode,
|
|
copiedLabel: ReactNode,
|
|
failedLabel: ReactNode,
|
|
) {
|
|
if (state === 'copied') {
|
|
return copiedLabel;
|
|
}
|
|
if (state === 'failed') {
|
|
return failedLabel;
|
|
}
|
|
return idleLabel;
|
|
}
|
|
|
|
/**
|
|
* 统一复制反馈按钮。
|
|
* useCopyFeedback 负责复制状态,这里只收口按钮里的图标、文案和可访问名称。
|
|
*/
|
|
export function CopyFeedbackButton({
|
|
state,
|
|
idleLabel,
|
|
copiedLabel = '已复制',
|
|
failedLabel = '复制失败',
|
|
idleIcon = <Copy className="h-4 w-4" />,
|
|
copiedIcon = <Check className="h-4 w-4" />,
|
|
failedIcon,
|
|
showIcon = true,
|
|
showLabel = true,
|
|
labelClassName,
|
|
accessibleLabel: accessibleLabelOverride,
|
|
actionSurface,
|
|
actionTone = 'primary',
|
|
actionSize = 'sm',
|
|
actionShape = 'default',
|
|
actionFullWidth = false,
|
|
actionAppearance = 'plain',
|
|
actionPillTone = 'neutral',
|
|
actionPillSize = 'xs',
|
|
className,
|
|
'aria-label': ariaLabel,
|
|
title,
|
|
...buttonProps
|
|
}: CopyFeedbackButtonProps) {
|
|
const label = resolveCopyFeedbackLabel(
|
|
state,
|
|
idleLabel,
|
|
copiedLabel,
|
|
failedLabel,
|
|
);
|
|
const icon =
|
|
state === 'copied'
|
|
? copiedIcon
|
|
: state === 'failed'
|
|
? (failedIcon ?? idleIcon)
|
|
: idleIcon;
|
|
const accessibleLabel =
|
|
accessibleLabelOverride ??
|
|
(typeof label === 'string'
|
|
? label
|
|
: typeof idleLabel === 'string'
|
|
? idleLabel
|
|
: undefined);
|
|
const resolvedAriaLabel = ariaLabel ?? accessibleLabel;
|
|
const resolvedTitle =
|
|
title ?? (typeof accessibleLabel === 'string' ? accessibleLabel : undefined);
|
|
const content = (
|
|
<>
|
|
{showIcon ? icon : null}
|
|
{showLabel ? <span className={labelClassName}>{label}</span> : null}
|
|
</>
|
|
);
|
|
|
|
if (actionSurface) {
|
|
return (
|
|
<PlatformActionButton
|
|
surface={actionSurface}
|
|
tone={actionTone}
|
|
size={actionSize}
|
|
shape={actionShape}
|
|
fullWidth={actionFullWidth}
|
|
className={className}
|
|
{...buttonProps}
|
|
aria-label={resolvedAriaLabel}
|
|
title={resolvedTitle}
|
|
>
|
|
{content}
|
|
</PlatformActionButton>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={[
|
|
actionAppearance === 'pill'
|
|
? getPlatformPillBadgeClassName({
|
|
tone: actionPillTone,
|
|
size: actionPillSize,
|
|
})
|
|
: null,
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')}
|
|
{...buttonProps}
|
|
aria-label={resolvedAriaLabel}
|
|
title={resolvedTitle}
|
|
>
|
|
{content}
|
|
</button>
|
|
);
|
|
}
|