新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
147 lines
3.7 KiB
TypeScript
147 lines
3.7 KiB
TypeScript
import { ImagePlus } from 'lucide-react';
|
|
import type {
|
|
ButtonHTMLAttributes,
|
|
LabelHTMLAttributes,
|
|
ReactNode,
|
|
} from 'react';
|
|
|
|
type PlatformUploadTileBaseProps = {
|
|
label: ReactNode;
|
|
hint?: ReactNode;
|
|
icon?: ReactNode;
|
|
disabled?: boolean;
|
|
showLabel?: boolean;
|
|
size?: 'square' | 'compact' | 'panel';
|
|
surface?: 'platform' | 'editorDark';
|
|
};
|
|
|
|
type PlatformUploadTileButtonProps = Omit<
|
|
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
'children' | 'disabled'
|
|
> &
|
|
PlatformUploadTileBaseProps & {
|
|
asChild?: false;
|
|
};
|
|
|
|
type PlatformUploadTileLabelProps = Omit<
|
|
LabelHTMLAttributes<HTMLLabelElement>,
|
|
'children'
|
|
> &
|
|
PlatformUploadTileBaseProps & {
|
|
asChild: 'label';
|
|
};
|
|
|
|
type PlatformUploadTileProps =
|
|
| PlatformUploadTileButtonProps
|
|
| PlatformUploadTileLabelProps;
|
|
|
|
function getPlatformUploadTileClassName(
|
|
size: 'square' | 'compact' | 'panel',
|
|
surface: 'platform' | 'editorDark',
|
|
className?: string,
|
|
disabled?: boolean,
|
|
) {
|
|
const surfaceClassName = {
|
|
platform:
|
|
'border-[var(--platform-subpanel-border)] bg-[var(--platform-input-fill)] text-[var(--platform-text-soft)] hover:border-[var(--platform-surface-hover-border)] hover:text-[var(--platform-text-strong)]',
|
|
editorDark:
|
|
'border-white/12 bg-black/20 text-zinc-300 hover:border-sky-300/45 hover:bg-white/8 hover:text-white',
|
|
}[surface];
|
|
const sizeClassName = {
|
|
square: 'h-[5.75rem] w-[5.75rem] items-center text-center',
|
|
compact: 'h-12 w-full items-center text-center',
|
|
panel: 'min-h-[7rem] w-full items-start px-4 py-4 text-left',
|
|
}[size];
|
|
|
|
return [
|
|
'flex flex-col justify-center rounded-xl border border-dashed transition',
|
|
surfaceClassName,
|
|
sizeClassName,
|
|
disabled ? 'cursor-not-allowed opacity-55' : 'cursor-pointer',
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
}
|
|
|
|
/**
|
|
* 平台通用上传方块。
|
|
* 统一承载图片 / 附件上传入口的虚线方块、图标、主副文案和可访问语义。
|
|
*/
|
|
export function PlatformUploadTile({
|
|
label,
|
|
hint,
|
|
icon = <ImagePlus className="h-6 w-6" />,
|
|
disabled = false,
|
|
showLabel = true,
|
|
size = 'square',
|
|
surface = 'platform',
|
|
className,
|
|
asChild,
|
|
...actionProps
|
|
}: PlatformUploadTileProps) {
|
|
const tileClassName = getPlatformUploadTileClassName(
|
|
size,
|
|
surface,
|
|
className,
|
|
disabled,
|
|
);
|
|
const hasIcon = Boolean(icon);
|
|
const labelClassName =
|
|
size === 'panel'
|
|
? [hasIcon ? 'mt-2' : null, 'text-[11px] font-bold tracking-[0.14em]']
|
|
.filter(Boolean)
|
|
.join(' ')
|
|
: [hasIcon ? 'mt-2' : null, 'text-xs font-medium']
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
const hintClassName =
|
|
size === 'panel'
|
|
? 'mt-2 text-xs leading-5 opacity-80'
|
|
: 'mt-0.5 text-[11px] opacity-80';
|
|
|
|
const content = (
|
|
<>
|
|
{hasIcon ? icon : null}
|
|
<span className={showLabel ? labelClassName : 'sr-only'}>{label}</span>
|
|
{hint ? <span className={hintClassName}>{hint}</span> : null}
|
|
</>
|
|
);
|
|
|
|
if (asChild === 'label') {
|
|
const { onClick, ...labelProps } =
|
|
actionProps as LabelHTMLAttributes<HTMLLabelElement>;
|
|
|
|
return (
|
|
<label
|
|
{...labelProps}
|
|
onClick={(event) => {
|
|
if (disabled) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
onClick?.(event);
|
|
}}
|
|
aria-disabled={disabled || undefined}
|
|
className={tileClassName}
|
|
>
|
|
{content}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
const { type = 'button', ...buttonProps } =
|
|
actionProps as ButtonHTMLAttributes<HTMLButtonElement>;
|
|
|
|
return (
|
|
<button
|
|
{...buttonProps}
|
|
type={type}
|
|
disabled={disabled}
|
|
className={tileClassName}
|
|
>
|
|
{content}
|
|
</button>
|
|
);
|
|
}
|