diff --git a/frontend/src/app/playtest-export-page.tsx b/frontend/src/app/playtest-export-page.tsx index 8cec2141..e45cc488 100644 --- a/frontend/src/app/playtest-export-page.tsx +++ b/frontend/src/app/playtest-export-page.tsx @@ -20,7 +20,7 @@ const renderToolbar: NonNullable = ({ ) } catch { diff --git a/frontend/src/entities/character/directions.test.ts b/frontend/src/entities/character/directions.test.ts index f9e5086c..03dc6306 100644 --- a/frontend/src/entities/character/directions.test.ts +++ b/frontend/src/entities/character/directions.test.ts @@ -11,8 +11,8 @@ describe('direction grid layouts', () => { it('maps single, four-way, and eight-way assets to their display grids', () => { expect(getDirectionGridLayout('single')).toEqual({ columns: 1, cells: ['east'] }) expect(getDirectionGridLayout('four-way')).toEqual({ - columns: 2, - cells: ['north', 'south', 'west', 'east'], + columns: 3, + cells: [null, 'north', null, 'west', null, 'east', null, 'south', null], }) expect(getDirectionGridLayout('eight-way')).toEqual({ columns: 3, diff --git a/frontend/src/entities/character/directions.ts b/frontend/src/entities/character/directions.ts index 402591ef..a77f605d 100644 --- a/frontend/src/entities/character/directions.ts +++ b/frontend/src/entities/character/directions.ts @@ -59,9 +59,10 @@ const DIRECTION_PROFILES: Record = { const DIRECTION_GRID_LAYOUTS: Record = { single: { columns: 1, cells: ['east'] }, + /** 四向也走九宫格:方位落在它本来的位置上,四角和中心空着,不挤成 2×2 方阵。 */ 'four-way': { - columns: 2, - cells: ['north', 'south', 'west', 'east'], + columns: 3, + cells: [null, 'north', null, 'west', null, 'east', null, 'south', null], }, 'eight-way': { columns: 3, diff --git a/frontend/src/features/export-package/export-panel.tsx b/frontend/src/features/export-package/export-panel.tsx index 1fcb3587..57cc922d 100644 --- a/frontend/src/features/export-package/export-panel.tsx +++ b/frontend/src/features/export-package/export-panel.tsx @@ -1,5 +1,13 @@ import { useEffect, useId, useRef, useState, type ReactNode } from 'react' -import { ArrowClockwise, Check, CircleNotch, DownloadSimple } from '@phosphor-icons/react' +import { + ArrowClockwise, + ArrowSquareIn, + CaretDown, + Check, + CircleNotch, + DownloadSimple, + FileArrowDown, +} from '@phosphor-icons/react' import { productMenuItemClass, @@ -53,6 +61,11 @@ export interface ExportButtonProps { cocosImporter?: CocosImporter cocosPairer?: CocosPairer enableCocosExport?: boolean + /** + * stack 竖排(卡片内默认);menu 只露一个「导出」按钮,把资产包和 Cocos 两条路径 + * 收进二级菜单 —— 页面顶部的操作行放不下三个同级导出入口。 + */ + layout?: 'stack' | 'menu' } type ExportState = @@ -87,6 +100,18 @@ const STAGE_LABELS: Readonly> = { playtest: 'Playtest 运行包', } +/** + * 导出入口共用的结构类。这里刻意不写 padding、min-height、边框色和字号: + * 它们由调用方的 className 给,基础类再写一份会和调用方的同类 utility 撞车, + * 谁生效取决于 CSS 产出顺序而不是拼接顺序。 + */ +function exportActionClass(pill: boolean, className: string): string { + return `${pill ? 'rounded-full' : ''} inline-flex items-center justify-center gap-2 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-app-accent disabled:cursor-not-allowed disabled:opacity-50 ${className}` +} + +const EXPORT_ALERT_CLASS = + 'rounded-app-compact border border-app-danger-line bg-app-danger-soft px-3 py-2 text-[11px] font-medium leading-4 text-app-danger' + const defaultExporter: AssetExporter = (model, onPhase) => exportGameAssets(model, { onPhase }) const defaultCocosExporter: AssetExporter = (model, onPhase) => exportGameAssets(model, { targets: [cocosCreatorTarget], onPhase }) @@ -221,12 +246,20 @@ function useCocosImportAction( return { state, working, pairingCode, setPairingCode, startImport, pairAndImport } } -function CocosImportFeedback({ action }: { action: ReturnType }) { +function CocosImportFeedback({ + action, + inline = false, +}: { + action: ReturnType + inline?: boolean +}) { + // inline 时反馈块独占一行,不跟按钮抢同一行的横向空间。 + const block = inline ? 'basis-full' : '' if (action.state.status === 'working') { return (

{COCOS_PHASE_LABELS[action.state.phase]}

@@ -234,11 +267,13 @@ function CocosImportFeedback({ action }: { action: ReturnType -

+

+

{action.state.message}

-