c5200c7d45
## 变更内容 - 新增基于 shadcn open-code 模式的共享 UI canonical 组件、样式与导出。 - 新增 `/components` 共享组件展示页,并覆盖平台组件、Token、状态和移动端布局。 - 补齐平台展示页的筛选、排序、上传预览、标签、开关和异步状态交互。 - 修复排序导致预览主题变化、筛选弹窗误关闭和入口状态不更新的问题。 - 同步网站与客户端构建别名、依赖、路由测试和项目文档。 ## 验证 - `npm run test -- src/components/shared-components/SharedComponentsShowcasePage.test.tsx` - `npm run typecheck` - `npm run check:encoding` - `npm run build:raw` - `git diff --check` - pre-push 门禁通过 Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/202 Co-authored-by: kdletters <kdletters@qq.com> Co-committed-by: kdletters <kdletters@qq.com>
153 lines
3.8 KiB
TypeScript
153 lines
3.8 KiB
TypeScript
import type {
|
|
ButtonHTMLAttributes,
|
|
HTMLAttributes,
|
|
KeyboardEvent,
|
|
LabelHTMLAttributes,
|
|
ReactNode,
|
|
Ref,
|
|
} from 'react';
|
|
import { forwardRef } from 'react';
|
|
|
|
export type PlatformIconButtonVariant =
|
|
| 'platformIcon'
|
|
| 'surfaceFloating'
|
|
| 'darkMini';
|
|
export type PlatformIconButtonSize = 'sm' | 'md' | 'lg';
|
|
|
|
type PlatformIconButtonBaseProps = {
|
|
label: string;
|
|
icon: ReactNode;
|
|
children?: ReactNode;
|
|
variant?: PlatformIconButtonVariant;
|
|
};
|
|
|
|
type PlatformIconButtonButtonProps = Omit<
|
|
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
'aria-label' | 'children'
|
|
> &
|
|
PlatformIconButtonBaseProps & {
|
|
asChild?: false;
|
|
};
|
|
|
|
type PlatformIconButtonLabelProps = Omit<
|
|
LabelHTMLAttributes<HTMLLabelElement>,
|
|
'aria-label' | 'children'
|
|
> &
|
|
PlatformIconButtonBaseProps & {
|
|
asChild: 'label';
|
|
};
|
|
|
|
type PlatformIconButtonSpanButtonProps = Omit<
|
|
HTMLAttributes<HTMLSpanElement>,
|
|
'aria-label' | 'children' | 'role'
|
|
> &
|
|
PlatformIconButtonBaseProps & {
|
|
asChild: 'spanButton';
|
|
disabled?: boolean;
|
|
};
|
|
|
|
export type PlatformIconButtonProps =
|
|
| PlatformIconButtonButtonProps
|
|
| PlatformIconButtonLabelProps
|
|
| PlatformIconButtonSpanButtonProps;
|
|
|
|
/**
|
|
* 平台通用图标动作按钮。
|
|
* 统一承接纯图标动作、图标上传 label 和带短标签的浮动图标动作。
|
|
*/
|
|
export const PlatformIconButton = forwardRef<
|
|
HTMLButtonElement | HTMLLabelElement | HTMLSpanElement,
|
|
PlatformIconButtonProps
|
|
>(function PlatformIconButton(
|
|
{
|
|
label,
|
|
icon,
|
|
children,
|
|
variant = 'platformIcon',
|
|
title,
|
|
className,
|
|
asChild,
|
|
...actionProps
|
|
},
|
|
ref,
|
|
) {
|
|
const variantClassName = {
|
|
platformIcon: 'platform-icon-button',
|
|
surfaceFloating:
|
|
'inline-flex items-center justify-center rounded-full border border-white/80 bg-white/94 text-[var(--platform-text-strong)] shadow-sm backdrop-blur transition hover:text-[var(--platform-accent)] disabled:cursor-not-allowed disabled:opacity-55',
|
|
darkMini:
|
|
'inline-flex items-center justify-center rounded-full border border-white/16 bg-black/55 text-white transition-colors hover:bg-black/70 disabled:cursor-not-allowed disabled:opacity-55',
|
|
}[variant];
|
|
|
|
const actionClassName = [variantClassName, className]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
|
|
if (asChild === 'label') {
|
|
return (
|
|
<label
|
|
{...(actionProps as LabelHTMLAttributes<HTMLLabelElement>)}
|
|
ref={ref as Ref<HTMLLabelElement>}
|
|
title={title}
|
|
className={actionClassName}
|
|
>
|
|
<span className="sr-only">{label}</span>
|
|
{icon}
|
|
{children}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
if (asChild === 'spanButton') {
|
|
const { disabled, onClick, onKeyDown, tabIndex, ...spanProps } =
|
|
actionProps as HTMLAttributes<HTMLSpanElement> & {
|
|
disabled?: boolean;
|
|
};
|
|
const handleKeyDown = (event: KeyboardEvent<HTMLSpanElement>) => {
|
|
onKeyDown?.(event);
|
|
if (event.defaultPrevented || disabled) {
|
|
return;
|
|
}
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
event.preventDefault();
|
|
event.currentTarget.click();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<span
|
|
{...spanProps}
|
|
ref={ref as Ref<HTMLSpanElement>}
|
|
aria-disabled={disabled}
|
|
aria-label={label}
|
|
className={actionClassName}
|
|
onClick={disabled ? undefined : onClick}
|
|
onKeyDown={handleKeyDown}
|
|
role="button"
|
|
tabIndex={disabled ? -1 : (tabIndex ?? 0)}
|
|
title={title}
|
|
>
|
|
{icon}
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
const { type = 'button', ...buttonProps } =
|
|
actionProps as ButtonHTMLAttributes<HTMLButtonElement>;
|
|
|
|
return (
|
|
<button
|
|
{...buttonProps}
|
|
ref={ref as Ref<HTMLButtonElement>}
|
|
type={type}
|
|
aria-label={label}
|
|
title={title}
|
|
className={actionClassName}
|
|
>
|
|
{icon}
|
|
{children}
|
|
</button>
|
|
);
|
|
});
|