收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
146
src/components/common/PlatformUploadTile.tsx
Normal file
146
src/components/common/PlatformUploadTile.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user