diff --git a/packages/shared/src/components/PlatformGamePricingField.css b/packages/shared/src/components/PlatformGamePricingField.css index 7cf807904..476e7ab4a 100644 --- a/packages/shared/src/components/PlatformGamePricingField.css +++ b/packages/shared/src/components/PlatformGamePricingField.css @@ -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; + } } diff --git a/packages/shared/src/components/PlatformGamePricingField.tsx b/packages/shared/src/components/PlatformGamePricingField.tsx index 22682c934..5157bc22c 100644 --- a/packages/shared/src/components/PlatformGamePricingField.tsx +++ b/packages/shared/src/components/PlatformGamePricingField.tsx @@ -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 ( -
- 付费方式 -
+ +
{mode === 'paid' ? ( -
+ ); }