修复分类 chip 条叠字:分段页签子项补内容宽度下限

- packages/shared/src/components/PlatformSegmentedTabs.tsx:容器补 `platform-segmented-tabs`
  稳定钩子类,供共享样式表精确命中子项(不依赖 Tailwind 任意值扫描)
- packages/shared/src/components/styles.css:新增 `.platform-segmented-tabs > button`
  的 `min-width: max-content`,给每个子项一个「内容宽度」下限;放不下时由容器横向滚动兜底
- apps/ai-game-creator-shell/src/view/project-development/ResourceClassificationPanel.tsx:
  「资源分类与标签」的 6 类分类条由 `columns="threeToSix"` 改为 `layout="scroll"` +
  `frame="bare"` + `surface="transparent"`,与 `@` 面板和资源画布的分类条同一条既有形态
- packages/shared/src/components/PlatformResourceFilterBar.test.tsx:新增两条声明级断言,
  钉住「子项不换行 + 容器横向可滚 + 子项有不低于内容宽度的下限」
This commit is contained in:
2026-09-11 14:54:27 +08:00
parent ff287c19fc
commit 4f3516a5d9
4 changed files with 71 additions and 1 deletions
@@ -214,8 +214,10 @@ export function ResourceClassificationPanel({
items={RESOURCE_CLASSIFICATION_CATEGORY_OPTIONS}
activeId={category}
onChange={setCategory}
columns="threeToSix"
layout="scroll"
gap="sm"
frame="bare"
surface="transparent"
size="compact"
/>
<PlatformTextField
@@ -1,15 +1,31 @@
/* @vitest-environment jsdom */
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';
import { PlatformResourceFilterBar } from './PlatformResourceFilterBar';
import { PlatformSegmentedTabs } from './PlatformSegmentedTabs';
const CATEGORY_ITEMS = [
{ id: 'all', label: '全部' },
{ id: 'character', label: '角色与对象' },
];
/**
* 分段页签的子项宽度下限是「不叠字」的唯一判据:grid 的轨道按容器等分、scroll 的
* flex 子项默认可收缩,两者都会把中文标签压到内容宽度以下,标签虽然 nowrap,
* 文字仍会画出自己的盒子压到相邻项上。这条规则一旦被删掉就要失败。
*/
function segmentedTabsStylesheet() {
return readFileSync(
resolve(process.cwd(), 'packages/shared/src/components/styles.css'),
'utf8',
);
}
describe('PlatformResourceFilterBar', () => {
test('renders a controlled search box, category tabs and tag chips', () => {
render(
@@ -93,4 +109,45 @@ describe('PlatformResourceFilterBar', () => {
expect(screen.queryByRole('group', { name: '素材筛选标签' })).toBeNull();
expect(screen.getByRole('button', { name: '全部' })).toBeTruthy();
});
test('never lets a category chip shrink below its label width', () => {
const { container } = render(
<PlatformResourceFilterBar
ariaLabel="素材筛选"
categoryItems={CATEGORY_ITEMS}
activeCategoryId="all"
onCategoryChange={() => {}}
/>,
);
// 分类条是 scroll 布局:容器横向可滚,标签不换行。
const row = container.querySelector('.platform-segmented-tabs');
expect(row).not.toBeNull();
expect(row?.className).toContain('overflow-x-auto');
for (const button of row?.querySelectorAll('button') ?? []) {
expect(button.className).toContain('whitespace-nowrap');
}
// 单靠 nowrap 不够:子项必须有不低于内容宽度的下限,否则文字会画出盒子压到相邻项。
expect(segmentedTabsStylesheet()).toMatch(
/\.platform-segmented-tabs\s*>\s*button\s*\{[^}]*min-width:\s*max-content/s,
);
});
test('grid 布局的分段页签同样带宽度下限钩子', () => {
render(
<PlatformSegmentedTabs
items={CATEGORY_ITEMS}
activeId="all"
onChange={() => {}}
columns="two"
/>,
);
const row = screen
.getByRole('button', { name: '角色与对象' })
.closest('div');
expect(row?.className).toContain('platform-segmented-tabs');
expect(row?.className).toContain('grid-cols-2');
});
});
@@ -191,6 +191,7 @@ export function PlatformSegmentedTabs<TId extends string = string>({
role={semantics === 'tabs' ? 'tablist' : undefined}
aria-label={semantics === 'tabs' ? ariaLabel : undefined}
className={[
'platform-segmented-tabs',
layout === 'scroll'
? 'flex min-w-0 items-center overflow-x-auto scrollbar-hide'
: 'grid',
+10
View File
@@ -519,6 +519,16 @@
display: none;
}
/* 分段页签的子项不得被压到内容宽度以下。中文标签(例如「角色与对象」需要一个
`1.5rem 内边距 + 4 个字`的宽度)在窄容器里一旦被压扁,标签虽然 `nowrap`,文字仍会
画出自己的盒子压到相邻项上——现场表现就是「后几项挤成一团、文字互相重叠」。
`min-width: max-content` 给每个子项一个「内容宽度」下限:放得下就照原样排,放不下
就由容器横向滚动兜底,任何情况下都不叠字。grid 与 scroll 两条 layout 都需要它:
grid 的轨道按容器等分,scroll 的 flex 子项默认可收缩,两者都会压到内容宽度以下。 */
.platform-segmented-tabs > button {
min-width: max-content;
}
.platform-category-chip {
display: inline-flex;
min-height: 2.25rem;