继续收口暗色弹窗底部动作区

新增 PlatformDarkModalFooter 统一 dark modal footer 的布局壳
接入 NPC 弹窗、选择定制弹窗、任务更新弹层与物品详情 footer
补充组件级与弹窗集成测试并更新收口计划和共享决策记录
This commit is contained in:
2026-06-11 03:03:51 +08:00
parent ae1a15cee0
commit 54ff839b0b
10 changed files with 196 additions and 12 deletions

View File

@@ -0,0 +1,86 @@
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
type PlatformDarkModalFooterLayout = 'actions' | 'content';
type PlatformDarkModalFooterPadding = 'compact' | 'roomy' | 'bottom';
type PlatformDarkModalFooterGap = 'sm' | 'md';
type PlatformDarkModalFooterAlign = 'start' | 'center' | 'end' | 'between';
type PlatformDarkModalFooterProps = ComponentPropsWithoutRef<'div'> & {
children: ReactNode;
bordered?: boolean;
layout?: PlatformDarkModalFooterLayout;
padding?: PlatformDarkModalFooterPadding;
gap?: PlatformDarkModalFooterGap;
wrap?: boolean;
align?: PlatformDarkModalFooterAlign;
contentClassName?: string;
};
const PADDING_CLASS: Record<PlatformDarkModalFooterPadding, string> = {
compact: 'px-4 py-3 sm:px-5 sm:py-4',
roomy: 'px-5 py-4',
bottom: 'px-5 pb-5',
};
const GAP_CLASS: Record<PlatformDarkModalFooterGap, string> = {
sm: 'gap-2',
md: 'gap-3',
};
const ALIGN_CLASS: Record<PlatformDarkModalFooterAlign, string> = {
start: 'justify-start',
center: 'justify-center',
end: 'justify-end',
between: 'justify-between',
};
/**
* 暗色 / 像素弹层底部 footer 骨架。
* 统一承接 border、padding 和常见的动作按钮排布,避免业务页重复手写同一套 chrome。
*/
export function PlatformDarkModalFooter({
children,
bordered = true,
layout = 'actions',
padding = 'compact',
gap = 'md',
wrap = false,
align = 'end',
className,
contentClassName,
...props
}: PlatformDarkModalFooterProps) {
const frameClassName = [
'platform-dark-modal-footer',
bordered ? 'border-t border-white/10' : null,
PADDING_CLASS[padding],
className ?? null,
]
.filter(Boolean)
.join(' ');
if (layout === 'content') {
return (
<div className={frameClassName} {...props}>
{children}
</div>
);
}
const actionsClassName = [
'platform-dark-modal-footer__actions',
'flex items-center',
ALIGN_CLASS[align],
GAP_CLASS[gap],
wrap ? 'flex-wrap' : null,
contentClassName ?? null,
]
.filter(Boolean)
.join(' ');
return (
<div className={frameClassName} {...props}>
<div className={actionsClassName}>{children}</div>
</div>
);
}