This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { DetailTabId } from '../../lib/urlState';
|
||||
import { shareTask, unshareTask } from '../../api';
|
||||
|
||||
interface Tab { id: DetailTabId; label: string; }
|
||||
interface Tab { id: DetailTabId; labelKey: string; }
|
||||
|
||||
interface DetailHeaderProps {
|
||||
title: string;
|
||||
@@ -31,6 +32,7 @@ interface DetailHeaderProps {
|
||||
}
|
||||
|
||||
function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; shareToken: string | null; onShareChange?: () => void }) {
|
||||
const { t } = useTranslation('detail');
|
||||
const [copied, setCopied] = useState(false);
|
||||
const qc = useQueryClient();
|
||||
|
||||
@@ -68,8 +70,8 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh
|
||||
<button
|
||||
onClick={() => shareMutation.mutate()}
|
||||
disabled={shareMutation.isPending}
|
||||
title={shareMutation.isPending ? '共有中...' : '公開リンクを発行'}
|
||||
aria-label="公開リンクを発行"
|
||||
title={shareMutation.isPending ? t('share.sharing') : t('share.publish')}
|
||||
aria-label={t('share.publish')}
|
||||
className={`${iconBtnBase} border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface`}
|
||||
>
|
||||
{shareMutation.isPending ? (
|
||||
@@ -100,8 +102,8 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
title={copied ? 'コピーしました' : '共有リンクをコピー'}
|
||||
aria-label="共有リンクをコピー"
|
||||
title={copied ? t('share.copied') : t('share.copy')}
|
||||
aria-label={t('share.copy')}
|
||||
className={`${iconBtnBase} ${copied ? 'border-emerald-200 dark:border-emerald-500/30 bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300' : 'border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface'}`}
|
||||
>
|
||||
{copied ? (
|
||||
@@ -118,8 +120,8 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh
|
||||
<button
|
||||
onClick={() => unshareMutation.mutate()}
|
||||
disabled={unshareMutation.isPending}
|
||||
title="共有を停止"
|
||||
aria-label="共有を停止"
|
||||
title={t('share.stop')}
|
||||
aria-label={t('share.stop')}
|
||||
className={`${iconBtnBase} border-hairline bg-canvas text-slate-500 hover:text-red-700 dark:hover:text-red-300 hover:border-red-200 hover:bg-red-50 dark:hover:bg-red-500/15`}
|
||||
>
|
||||
{unshareMutation.isPending ? (
|
||||
@@ -138,6 +140,7 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh
|
||||
}
|
||||
|
||||
function ContinueButton({ latestJobStatus, onClick }: { latestJobStatus: string | null; onClick: () => void }) {
|
||||
const { t } = useTranslation('detail');
|
||||
// Mirror the spec/backend TERMINAL list (worker maps abort outcomes to
|
||||
// 'failed', so 'aborted' is intentionally absent).
|
||||
const TERMINAL = ['succeeded', 'failed', 'waiting_human', 'cancelled'];
|
||||
@@ -148,8 +151,8 @@ function ContinueButton({ latestJobStatus, onClick }: { latestJobStatus: string
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={!enabled}
|
||||
title={enabled ? '別 piece で続ける' : 'タスクが進行中のため続行できません'}
|
||||
aria-label="別 piece で続ける"
|
||||
title={enabled ? t('continue.label') : t('continue.disabled')}
|
||||
aria-label={t('continue.label')}
|
||||
className={`${iconBtnBase} border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface`}
|
||||
>
|
||||
{/* arrow → divider: 「次のフェーズへ進む」cue。FileBrowser の refresh
|
||||
@@ -164,6 +167,7 @@ function ContinueButton({ latestJobStatus, onClick }: { latestJobStatus: string
|
||||
}
|
||||
|
||||
export function DetailHeader({ title, subtitle, tabs, activeTab, tabTransitionPending, onTabChange, onClose, detailWidth, onWidthToggle, taskId, shareToken, onShareChange, latestJobStatus, onContinue }: DetailHeaderProps) {
|
||||
const { t } = useTranslation('detail');
|
||||
// Mobile (< sm) hides the close button and tab bar because App.tsx
|
||||
// renders its own mobile-level top tab bar with the same controls.
|
||||
// Two close buttons / two tab bars on iPhone was visually redundant.
|
||||
@@ -194,8 +198,8 @@ export function DetailHeader({ title, subtitle, tabs, activeTab, tabTransitionPe
|
||||
{onWidthToggle && detailWidth && (
|
||||
<button
|
||||
onClick={onWidthToggle}
|
||||
title={detailWidth === 'focused' ? '標準表示に戻る' : '集中モード (TASK 列を細い rail に / Chat と Workspace を可変分割)'}
|
||||
aria-label={detailWidth === 'focused' ? '標準表示に戻る' : '集中モードに切替'}
|
||||
title={detailWidth === 'focused' ? t('focus.toStandard') : t('focus.toFocused')}
|
||||
aria-label={detailWidth === 'focused' ? t('focus.toStandard') : t('focus.toFocusedShort')}
|
||||
aria-pressed={detailWidth === 'focused'}
|
||||
className="hidden sm:inline-flex items-center justify-center w-7 h-7 rounded-md text-slate-500 hover:text-slate-700 hover:bg-surface-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-ring"
|
||||
>
|
||||
@@ -214,7 +218,7 @@ export function DetailHeader({ title, subtitle, tabs, activeTab, tabTransitionPe
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
aria-label="詳細パネルを閉じる"
|
||||
aria-label={t('panel.close')}
|
||||
className="hidden sm:inline-flex items-center justify-center w-7 h-7 rounded-md text-slate-400 hover:text-slate-700 hover:bg-surface-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-ring"
|
||||
>
|
||||
<svg className="w-4 h-4" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round">
|
||||
@@ -223,7 +227,7 @@ export function DetailHeader({ title, subtitle, tabs, activeTab, tabTransitionPe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tablist" aria-label="詳細タブ" className="hidden sm:flex gap-4 -mb-px">
|
||||
<div role="tablist" aria-label={t('panel.tabsLabel')} className="hidden sm:flex gap-4 -mb-px">
|
||||
{tabs.map(tab => {
|
||||
const active = activeTab === tab.id;
|
||||
const pending = active && tabTransitionPending;
|
||||
@@ -239,7 +243,7 @@ export function DetailHeader({ title, subtitle, tabs, activeTab, tabTransitionPe
|
||||
: 'border-transparent text-slate-500 font-medium hover:text-slate-800'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
{t(tab.labelKey)}
|
||||
{pending && (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
|
||||
Reference in New Issue
Block a user