发布入口改名:聊天头「导出」改「发布」,换火箭图标并加悬停上移

- DirectProject 聊天头入口文案与 aria/title 由「导出/导出产物」改为「发布」,图标由 Package 换为 Rocket
- 悬停/聚焦时火箭图标 translateY(-2px) 轻微上移,160ms ease,并在 prefers-reduced-motion 下关闭
- 同步更新聊天头入口相关注释与 directProjectChatHeader 单测
This commit is contained in:
2026-10-07 17:41:29 +08:00
parent efdd29c593
commit a5a5237d19
4 changed files with 60 additions and 18 deletions
+37
View File
@@ -12750,6 +12750,43 @@ button.design-workspace-tree__entry:hover,
cursor: not-allowed;
}
/* 火箭图标悬停轻微上移:一点点「起飞」暗示,位移小到不改变胶囊高度。 */
.game-workbench-chat
.project-chat-surface.is-direct-codex
.project-chat-publish-trigger-icon {
transition: transform 160ms ease;
}
.game-workbench-chat
.project-chat-surface.is-direct-codex
.project-chat-publish-trigger:hover:not(:disabled)
.project-chat-publish-trigger-icon,
.game-workbench-chat
.project-chat-surface.is-direct-codex
.project-chat-publish-trigger:focus-visible
.project-chat-publish-trigger-icon {
transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
.game-workbench-chat
.project-chat-surface.is-direct-codex
.project-chat-publish-trigger-icon {
transition: none;
}
.game-workbench-chat
.project-chat-surface.is-direct-codex
.project-chat-publish-trigger:hover:not(:disabled)
.project-chat-publish-trigger-icon,
.game-workbench-chat
.project-chat-surface.is-direct-codex
.project-chat-publish-trigger:focus-visible
.project-chat-publish-trigger-icon {
transform: none;
}
}
.game-workbench-layout--design .project-chat-topbar-status > span:last-child,
.game-workbench-chat
.project-chat-surface.is-direct-codex
@@ -75,7 +75,7 @@ export type DirectProjectChatViewProps = {
ensureConversationReadAllowed: DirectProjectConversationReadGate;
/** 项目对话写权限门:只在发起回合时检查;同样必填,缺省会让未传入的调用方静默跳过授权。 */
ensureConversationWriteAllowed: DirectProjectConversationWriteGate;
/** 导出产物入口:由工作台壳持有导出面板,聊天只提供触发按钮。 */
/** 发布入口:由工作台壳持有发布面板,聊天只提供触发按钮。 */
onRequestExport?: () => void;
/**
* 工作台壳入队的项目权限确认:聊天只展示并转发确认/取消,
@@ -1,13 +1,14 @@
import { Package, Settings } from 'lucide-react';
import { Rocket, Settings } from 'lucide-react';
/**
* DirectProject 聊天头:运行状态与设置入口。
*
* 状态文案由容器决定(忙态优先),这里只负责渲染;DirectProject 不再把工作台壳的
* 全局状态文案搬进来。发布入口归导出产物面板的陶泥儿 tab,不在聊天头。
* 全局状态文案搬进来。聊天头这颗是**打开发布面板**的入口(面板里再选目标 tab),真正的平台
* 发布动作在陶泥儿 tab 里。
*
* 导出入口在回合在途(`busy`)时禁用:导出会读写项目文件,和 DirectProject 回合的写入并发
* 会互相踩;设置入口不受影响。
* 发布入口在回合在途(`busy`)时禁用:发布面板会读写项目文件,和 DirectProject 回合的写入并发
* 会互相踩;设置入口不受影响。悬停时火箭图标轻微上移(见 `.project-chat-publish-trigger-icon`)。
*/
export function DirectProjectChatHeader({
busy,
@@ -18,7 +19,7 @@ export function DirectProjectChatHeader({
busy: boolean;
statusText: string;
onOpenSettings: () => void;
/** 没有项目时不传;传了才渲染导出产物入口。 */
/** 没有项目时不传;传了才渲染发布入口。 */
onRequestExport?: () => void;
}) {
return (
@@ -35,13 +36,17 @@ export function DirectProjectChatHeader({
<button
type="button"
className="project-chat-publish-trigger"
aria-label="导出产物"
title="导出产物"
aria-label="发布"
title="发布"
disabled={busy}
onClick={onRequestExport}
>
<Package size={16} aria-hidden="true" />
<span>导出</span>
<Rocket
size={16}
aria-hidden="true"
className="project-chat-publish-trigger-icon"
/>
<span>发布</span>
</button>
) : null}
<button
@@ -1,9 +1,9 @@
// @vitest-environment jsdom
/**
* DirectProject 聊天头的导出入口。
* DirectProject 聊天头的发布入口。
*
* 发布入口归导出产物面板的陶泥儿 tab,聊天头不再持有;这里只验证导出产物入口按回调渲染,
* 以及没有回调(例如策划聊天面)时不出现点不动的按钮。
* 聊天头这颗是打开发布面板的入口(真正的平台发布动作在面板的陶泥儿 tab 里);这里只验证发布
* 入口按回调渲染,以及没有回调(例如策划聊天面)时不出现点不动的按钮。
*/
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
@@ -32,18 +32,18 @@ function renderHeader(
}
describe('DirectProjectChatHeader', () => {
it('传入导出回调时渲染导出入口并可触发', () => {
it('传入发布回调时渲染发布入口并可触发', () => {
const { onRequestExport } = renderHeader();
const exportButton = screen.getByRole('button', { name: '导出产物' });
fireEvent.click(exportButton);
const publishButton = screen.getByRole('button', { name: '发布' });
fireEvent.click(publishButton);
expect(onRequestExport).toHaveBeenCalledTimes(1);
});
it('没有导出回调时不渲染导出入口', () => {
it('没有发布回调时不渲染发布入口', () => {
renderHeader({ onRequestExport: undefined });
expect(screen.queryByRole('button', { name: '导出产物' })).toBeNull();
expect(screen.queryByRole('button', { name: '发布' })).toBeNull();
expect(screen.getByRole('button', { name: '设置' })).not.toBeNull();
});