美化买断制定价字段:分组卡片、胶囊付费方式、带单位的价格输入
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m18s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m6s
Project CI / Repository checks (pull_request) Failing after 34s
Project CI / Frontend tests (pull_request) Successful in 3m26s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m34s
Project CI / Native shell tests (pull_request) Successful in 8m59s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m18s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m6s
Project CI / Repository checks (pull_request) Failing after 34s
Project CI / Frontend tests (pull_request) Successful in 3m26s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m34s
Project CI / Native shell tests (pull_request) Successful in 8m59s
- packages/shared:`PlatformGamePricingField` 改用 `PlatformSubpanel` 承载分组外壳(描边卡片 + 14px 黑体标题 `titleVariant="strong"`),与网页 `game-publish-fieldset`、AGC `__publication` 同族观感。 - packages/shared:付费方式从裸原生 radio 改为「原生 radio(屏外裁切,保留单选组语义/方向键/.checked/label 关联)+ 共享 chip 外观 `getPlatformCategoryChipClassName`」(未选=浅底描边,选中=实心品牌填充反白)。 - packages/shared:买断价改用共享 `PlatformTextField`(size=md / density=compact),标签与输入分离(label htmlFor),输入右对齐等宽数字、右侧留出「泥点」单位位;错误态红边红底(对网页宿主 `platform-subpanel` 输入框 remap 的 `!important` 同权重例外)、禁用态降透明度。 - packages/shared:CSS 补齐分组间距与层次、44px 触控胶囊、窄屏(≤480px)两枚等宽胶囊、`focus-visible` 焦点环;文案、props、校验与提交逻辑一律未改。 - 未触碰 AGC 发布面板「项目版本」一组及其包装/样式,也未改 `GameDistributionPublishPanel.tsx`。
This commit is contained in:
@@ -1,82 +1,168 @@
|
||||
/*
|
||||
* 买断制定价字段:网页发布页与 AGC 发布面板共用同一份表现与文案。
|
||||
*
|
||||
* 这里只补平台原语没覆盖的部分,不另造一套控件观感:
|
||||
* - 外壳:`PlatformSubpanel`(`platform` 面 + `xs` 圆角 ≈ 网页 `.game-publish-fieldset` 的
|
||||
* 0.7rem 描边卡片,AGC 侧同族容器是 `.game-distribution-publish-panel__publication`)。
|
||||
* - 付费方式:原生 radio(屏外裁切保留语义 / 键盘 / `.checked`)+ 共享 chip 外观
|
||||
* (`getPlatformCategoryChipClassName`:未选=浅底描边,选中=实心品牌填充反白)。
|
||||
* - 买断价:`PlatformTextField`,本文件只补右对齐等宽数字、单位留位、错误态与层次。
|
||||
*
|
||||
* 选择器统一写成「根类 + 子类」而不是裸类:AGC 发布面板的
|
||||
* `.game-distribution-publish-panel__fields label/input` 是后代选择器,裸类会被它
|
||||
* 覆盖。提高一档特异性后,无论宿主在前后引入样式,组件外观都由这里唯一决定。
|
||||
* `.game-distribution-publish-panel__fields label/input` 是后代选择器(0,1,1),会盖掉
|
||||
* 原语里的 Tailwind 工具类(0,1,0)。提高一档特异性后,组件外观在两端都由这里唯一决定。
|
||||
*/
|
||||
.platform-game-pricing-field {
|
||||
display: grid;
|
||||
gap: 0.55rem;
|
||||
gap: 0.6rem;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
font-size: 0.76rem;
|
||||
color: var(--platform-text-strong, #3d1f10);
|
||||
}
|
||||
|
||||
.platform-game-pricing-field > legend {
|
||||
padding: 0 0.3rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__modes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.9rem;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__option {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-size: 0.76rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__option input {
|
||||
width: auto;
|
||||
gap: 0;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 原生 radio 只做语义与键盘载体:`display: none` / `visibility: hidden` 会把可聚焦性和
|
||||
读屏可见性一起拿掉,所以用「屏外裁切」隐藏,焦点仍落在它身上。 */
|
||||
.platform-game-pricing-field .platform-game-pricing-field__option-input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
clip-path: inset(50%);
|
||||
}
|
||||
|
||||
/* chip 自带高度 36px(窄屏还会降到 33px),这里统一抬到 44px 触控下限;
|
||||
「免费 / 买断制」字数不同,按内容宽度会一宽一窄,给个共同下限让两枚等宽。 */
|
||||
.platform-game-pricing-field .platform-game-pricing-field__option-chip {
|
||||
min-width: 5.5rem;
|
||||
min-height: 2.75rem;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field
|
||||
.platform-game-pricing-field__option-input:focus-visible
|
||||
+ .platform-game-pricing-field__option-chip {
|
||||
outline: 2px solid var(--platform-input-focus-ring, #b6623f);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field
|
||||
.platform-game-pricing-field__option-input:disabled
|
||||
+ .platform-game-pricing-field__option-chip {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__price {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
font-weight: 500;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__price input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--platform-surface-border, rgba(226, 203, 184, 0.72));
|
||||
border-radius: 0.7rem;
|
||||
padding: 0.55rem 0.7rem;
|
||||
background: var(--platform-panel-fill, #fffdfa);
|
||||
.platform-game-pricing-field .platform-game-pricing-field__price-label {
|
||||
display: block;
|
||||
color: var(--platform-text-strong, #3d1f10);
|
||||
font: inherit;
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__price-control {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 边框 / 圆角 / 底色 / 焦点环都来自 `PlatformTextField`(AGC 宿主还会用面板自己的输入框
|
||||
token 覆盖,与同面板其它输入框一致),这里只补三件事:数字右对齐且等宽、给单位留位、错误态。 */
|
||||
.platform-game-pricing-field .platform-game-pricing-field__price-input {
|
||||
padding-right: 2.9rem;
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 错误态。网页宿主对子面板内的输入框做了统一 remap,且带 `!important`
|
||||
(`src/index.css` 的 `.platform-theme :where(.platform-subpanel …) :where(input…)` 规则,
|
||||
用来把弹窗/子面板里的输入框remap成平台输入框底色)。错误态是必须压过它的例外,所以同权重声明。
|
||||
不改 `box-shadow`:那是宿主的输入高光与焦点环,压掉会让非法字段失去键盘焦点提示。 */
|
||||
.platform-game-pricing-field
|
||||
.platform-game-pricing-field__price-input[aria-invalid='true'] {
|
||||
border-color: var(--platform-button-danger-text, #a6402f) !important;
|
||||
background: var(
|
||||
--platform-button-danger-fill,
|
||||
rgba(255, 237, 229, 0.94)
|
||||
) !important;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__price-unit {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.75rem;
|
||||
transform: translateY(-50%);
|
||||
color: var(--platform-text-soft, #988476);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field
|
||||
.platform-game-pricing-field__price
|
||||
input:focus-visible {
|
||||
border-color: var(--platform-accent, #c7653d);
|
||||
outline: 2px solid rgba(199, 101, 61, 0.2);
|
||||
outline-offset: 1px;
|
||||
.platform-game-pricing-field__price-input[aria-invalid='true']
|
||||
+ .platform-game-pricing-field__price-unit {
|
||||
color: var(--platform-button-danger-text, #a6402f);
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__hint {
|
||||
color: var(--platform-text-soft, #988476);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__error {
|
||||
color: var(--platform-button-danger-text, #a6402f);
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* 窄屏(≈390px):两枚胶囊等分整行,不靠换行兜底,也不出现横向溢出。 */
|
||||
@media (max-width: 480px) {
|
||||
.platform-game-pricing-field .platform-game-pricing-field__modes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.platform-game-pricing-field .platform-game-pricing-field__option-chip {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ import './PlatformGamePricingField.css';
|
||||
|
||||
import { useId } from 'react';
|
||||
|
||||
import { getPlatformCategoryChipClassName } from './platformCategoryChipModel';
|
||||
import {
|
||||
PLATFORM_GAME_MAX_PRICE_MUD_POINTS,
|
||||
type PlatformGamePricingMode,
|
||||
} from './platformGamePricingModel';
|
||||
import { PlatformSubpanel } from './PlatformSubpanel';
|
||||
import { PlatformTextField } from './PlatformTextField';
|
||||
|
||||
export type PlatformGamePricingFieldProps = {
|
||||
/** 付费方式;`free` 等价于 0 泥点。 */
|
||||
@@ -26,6 +29,11 @@ export type PlatformGamePricingFieldProps = {
|
||||
*
|
||||
* 网页发布页与 AGC 发布面板共用同一份表现和文案;组件只表达输入 UI,价格校验、提交
|
||||
* 与业务状态由使用方持有。上限取自共享常量,两端不各写一份。
|
||||
*
|
||||
* 组成全部复用平台原语:外壳是 `PlatformSubpanel`(与网页发布页 `game-publish-fieldset`
|
||||
* 同族的描边卡片),付费方式是「原生 radio + 标签 chip」,价格是 `PlatformTextField`。
|
||||
* 两个选项是互斥单选,所以语义仍由原生 radio 承载(读屏的单选组、方向键切换、
|
||||
* `.checked` / `.disabled`、label 关联全都照旧),chip 只负责外观。
|
||||
*/
|
||||
export function PlatformGamePricingField({
|
||||
mode,
|
||||
@@ -40,48 +48,92 @@ export function PlatformGamePricingField({
|
||||
const errorId = `${useId()}-price-error`;
|
||||
const priceInputId = `${useId()}-price-input`;
|
||||
return (
|
||||
<fieldset className="platform-game-pricing-field" disabled={disabled}>
|
||||
<legend>付费方式</legend>
|
||||
<div className="platform-game-pricing-field__modes">
|
||||
<PlatformSubpanel
|
||||
as="div"
|
||||
className="platform-game-pricing-field"
|
||||
radius="xs"
|
||||
padding="xs"
|
||||
surface="platform"
|
||||
title="付费方式"
|
||||
titleVariant="strong"
|
||||
>
|
||||
<div
|
||||
className="platform-game-pricing-field__modes"
|
||||
role="radiogroup"
|
||||
aria-label="付费方式"
|
||||
>
|
||||
<label className="platform-game-pricing-field__option">
|
||||
<input
|
||||
className="platform-game-pricing-field__option-input"
|
||||
type="radio"
|
||||
name={radioName}
|
||||
checked={mode === 'free'}
|
||||
disabled={disabled}
|
||||
onChange={() => onModeChange('free')}
|
||||
/>
|
||||
免费
|
||||
<span
|
||||
className={[
|
||||
'platform-game-pricing-field__option-chip',
|
||||
getPlatformCategoryChipClassName(mode === 'free'),
|
||||
].join(' ')}
|
||||
>
|
||||
免费
|
||||
</span>
|
||||
</label>
|
||||
<label className="platform-game-pricing-field__option">
|
||||
<input
|
||||
className="platform-game-pricing-field__option-input"
|
||||
type="radio"
|
||||
name={radioName}
|
||||
checked={mode === 'paid'}
|
||||
disabled={disabled}
|
||||
onChange={() => onModeChange('paid')}
|
||||
/>
|
||||
买断制
|
||||
<span
|
||||
className={[
|
||||
'platform-game-pricing-field__option-chip',
|
||||
getPlatformCategoryChipClassName(mode === 'paid'),
|
||||
].join(' ')}
|
||||
>
|
||||
买断制
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{mode === 'paid' ? (
|
||||
<label className="platform-game-pricing-field__price">
|
||||
买断价(泥点)
|
||||
<input
|
||||
id={priceInputId}
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
value={priceInput}
|
||||
disabled={disabled}
|
||||
aria-invalid={error ? true : undefined}
|
||||
aria-describedby={error ? errorId : undefined}
|
||||
onChange={(event) => onPriceInputChange(event.target.value)}
|
||||
/>
|
||||
<div className="platform-game-pricing-field__price">
|
||||
<label
|
||||
className="platform-game-pricing-field__price-label"
|
||||
htmlFor={priceInputId}
|
||||
>
|
||||
买断价(泥点)
|
||||
</label>
|
||||
<div className="platform-game-pricing-field__price-control">
|
||||
<PlatformTextField
|
||||
id={priceInputId}
|
||||
className="platform-game-pricing-field__price-input"
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
size="md"
|
||||
density="compact"
|
||||
value={priceInput}
|
||||
disabled={disabled}
|
||||
aria-invalid={error ? true : undefined}
|
||||
aria-describedby={error ? errorId : undefined}
|
||||
onChange={(event) => onPriceInputChange(event.target.value)}
|
||||
/>
|
||||
{/* 单位是价格的视觉附属:label 里已经写了「(泥点)」,这里对读屏重复一次没有意义。 */}
|
||||
<span
|
||||
className="platform-game-pricing-field__price-unit"
|
||||
aria-hidden="true"
|
||||
>
|
||||
泥点
|
||||
</span>
|
||||
</div>
|
||||
<span className="platform-game-pricing-field__hint">
|
||||
买断一次永久拥有;价格随本次版本审核通过后生效,最多{' '}
|
||||
{PLATFORM_GAME_MAX_PRICE_MUD_POINTS} 泥点
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
) : null}
|
||||
{error ? (
|
||||
<span
|
||||
@@ -92,6 +144,6 @@ export function PlatformGamePricingField({
|
||||
{error}
|
||||
</span>
|
||||
) : null}
|
||||
</fieldset>
|
||||
</PlatformSubpanel>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user