diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/resourceTypePanel.css b/apps/ai-game-creator-shell/src/features/project-workspace/resourceTypePanel.css index f582f3885..fb9260aa6 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/resourceTypePanel.css +++ b/apps/ai-game-creator-shell/src/features/project-workspace/resourceTypePanel.css @@ -1,12 +1,12 @@ /* - * 「设置素材类型」面板的弹窗骨架与信息浮层的类型入口。 + * 「设置素材类型」面板的弹窗骨架、纵向单选列表与信息浮层的类型入口。 * * 单独一个文件而不是塞进 styles.css:与「编辑素材标签」面板当初同样的理由 —— * 这份样式只服务本次的素材类型入口,与工作台其它区块没有共享选择器,独立文件让改动 * 边界更清楚,也不会与同一时段其它 Agent 在 styles.css 里的编辑互相踩。 * * 骨架沿用「编辑素材标签」那套三段式契约(`auto / minmax(0, 1fr)`):标题常驻、 - * 中间一行可压缩可滚动、`max-height` 兜住上界。类型面板没有底部按钮,所以只有两行。 + * 中间一行可压缩、`max-height` 兜住上界。类型面板没有底部按钮,所以只有两行。 */ .game-resource-type-dialog { width: min(480px, 100%); @@ -15,16 +15,16 @@ } /* - * 滚动落在 body 这一行:`min-height: 0` 是网格项能被 `1fr` 压缩的前提, - * 否则内容高度会顶回轨道、`overflow-y` 永远不触发。 + * body 分三段:提示 / 选项列表 / 错误提示。 + * + * `min-height: 0` 是网格项能被 `1fr` 压缩的前提;**滚动不在这里**——滚动权交给选项列表 + * (见下),否则往下滚时素材名和错误提示会跟着跑掉,用户看不到"改的是哪件素材、为什么失败"。 */ .game-resource-type-body { display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; gap: 10px; min-height: 0; - overflow-y: auto; - overscroll-behavior: contain; - scrollbar-gutter: stable; } /* @@ -36,6 +36,67 @@ font-size: 12px; } +/* + * 纵向单选列表(`role="radiogroup"`):**一行一个选项**。 + * + * 之前 6 项横排在一条里(`PlatformSegmentedTabs` 的 3~6 列网格),窄屏上互相叠字读不出来。 + * 单列网格 + 按行流向是"每项一行、互不重叠"的充分条件:只声明一列,6 个子元素必然上下排 6 行, + * 不存在两项挤一行的可能。选项多时列表自己滚(`max-height` + `overflow-y: auto`), + * 面板不会被撑高。移动端优先:360px 宽的窄屏同样是这一套声明(没有按宽度改列数的媒体查询)。 + */ +.game-resource-type-options { + display: grid; + grid-template-columns: minmax(0, 1fr); + grid-auto-flow: row; + align-content: start; + gap: 6px; + min-height: 0; + max-height: min(320px, 40dvh); + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-gutter: stable; +} + +/* + * 单个选项行:复用共享的 `PlatformNavigableListItem` 骨架(w-full / flex / text-left / + * 圆角 / 悬停 / 焦点环都由它给),这里只补"整行可点 + 明确选中态"的表现。 + * + * `width/min-width` 显式写出来,不依赖共享件里的 Tailwind `w-full`:这一行是不是满宽 + * 决定了"一项一行"能不能成立,不能挂在另一份文件的工具类上。 + * `min-height: 44px` 是移动端点击热区下限;`overflow-wrap` 让长选项名在窄屏换行而不是溢出。 + */ +.game-resource-type-option { + width: 100%; + min-width: 0; + min-height: 44px; + padding: 8px 12px; + border: 1px solid var(--platform-subpanel-border); + background: rgb(255 255 255 / 62%); + color: var(--platform-text-base); + font-size: 13px; + font-weight: 600; + overflow-wrap: anywhere; +} + +.game-resource-type-option:hover:not(:disabled) { + border-color: var(--platform-surface-hover-border); +} + +/* + * 选中态完全由 `aria-checked="true"` 驱动:视觉与读屏读的是同一个属性,不会各说一套。 + * + * 选择器显式提权到 (0,3,0) 以上:共享列表行自带的 `.platform-navigable-list-item:hover:not(:disabled)` + * 也是 (0,3,0),只写 `.game-resource-type-option[aria-checked='true']`((0,2,0))会在悬停时 + * 被它的底色顶掉;带 `:hover:not(:disabled)` 的那条 (0,5,0) 保证选中行悬停时也不变色。 + */ +.game-resource-type-options .game-resource-type-option[aria-checked='true'], +.game-resource-type-options + .game-resource-type-option[aria-checked='true']:hover:not(:disabled) { + border-color: var(--platform-warm-border); + background: var(--platform-warm-bg); + color: var(--platform-text-strong); +} + .game-resource-type-error { margin: 0; color: #b3261e; diff --git a/apps/ai-game-creator-shell/src/view/project-development/ResourceTypePanel.tsx b/apps/ai-game-creator-shell/src/view/project-development/ResourceTypePanel.tsx index 07b254267..3a7144160 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/ResourceTypePanel.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/ResourceTypePanel.tsx @@ -1,8 +1,13 @@ import '../../features/project-workspace/resourceTypePanel.css'; -import { useState } from 'react'; +import { Check } from 'lucide-react'; +import { + type KeyboardEvent as ReactKeyboardEvent, + useRef, + useState, +} from 'react'; -import { PlatformSegmentedTabs } from '../../../../../packages/shared/src/components/PlatformSegmentedTabs'; +import { PlatformNavigableListItem } from '../../../../../packages/shared/src/components/PlatformNavigableListItem'; import { GAME_CREATION_APP_ASSET_CATEGORIES, type GameCreationAppAssetCategory, @@ -81,6 +86,36 @@ export function ResourceTypePanel({ const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const activeCategory = pendingCategory ?? gameCreationAppAssetCategory(asset); + const optionsRef = useRef(null); + + /** + * 单选组的键盘口径:Tab 进组只停一次(roving tabindex,见下面的 `tabIndex`); + * 方向键在选项之间移动**焦点**,Enter/Space(`