feat(web): Phase 11.3 響應式設計 - Mobile/Tablet 支援

ConversationalView 響應式改造:
- Mobile: 全屏詳情面板 + 返回按鈕
- Tablet: 64px 側邊欄 (w-64)
- Desktop: 80px 側邊欄 (w-80)
- i18n: backToList 翻譯

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-25 13:46:15 +08:00
parent 7a8f869104
commit 805d353892
3 changed files with 47 additions and 7 deletions

View File

@@ -263,6 +263,7 @@
"fetchError": "Failed to fetch approvals",
"noPendingApprovals": "No pending approvals",
"selectApproval": "Select an approval to view details",
"backToList": "Back to list",
"previousApproval": "Previous",
"nextApproval": "Next",
"holdToApproveHint": "Hold button to approve or reject",

View File

@@ -263,6 +263,7 @@
"fetchError": "無法取得授權清單",
"noPendingApprovals": "目前無待授權項目",
"selectApproval": "請選擇一個待授權項目",
"backToList": "返回列表",
"previousApproval": "上一個項目",
"nextApproval": "下一個項目",
"holdToApproveHint": "長按按鈕以批准或拒絕",

View File

@@ -70,6 +70,8 @@ export function ConversationalView({
// 選中的 Approval
const [selectedId, setSelectedId] = useState<string | null>(null)
const [showShortcuts, setShowShortcuts] = useState(false)
// Phase 11.3: Mobile 詳情面板狀態
const [showMobileDetail, setShowMobileDetail] = useState(false)
// 轉換為前端格式
const approvals = useMemo(() => {
@@ -99,9 +101,17 @@ export function ConversationalView({
} else {
setSelectedId(null)
}
// Phase 11.3: 關閉 Mobile 詳情面板
setShowMobileDetail(false)
}
}, [approvals, selectedId])
// Phase 11.3: Mobile 選擇處理
const handleMobileSelect = useCallback((id: string) => {
setSelectedId(id)
setShowMobileDetail(true)
}, [])
// 導航函數
const goToPrev = useCallback(() => {
if (selectedIndex > 0) {
@@ -146,8 +156,15 @@ export function ConversationalView({
return (
<div className={cn('flex h-full bg-nothing-gray-50', className)}>
{/* 左側: 對話列表 */}
<div className="w-80 flex-shrink-0 border-r border-nothing-gray-200 bg-white flex flex-col">
{/* 左側: 對話列表 - 響應式寬度 */}
{/* Mobile: 全寬 | Tablet: w-64 | Desktop: w-80 */}
<div className={cn(
'flex-shrink-0 border-r border-nothing-gray-200 bg-white flex flex-col',
// Phase 11.3: 響應式寬度
'w-full md:w-64 lg:w-80',
// Mobile: 詳情面板打開時隱藏列表
showMobileDetail && 'hidden md:flex'
)}>
{/* 列表 Header */}
<div className="flex-shrink-0 p-4 border-b border-nothing-gray-100">
<div className="flex items-center justify-between">
@@ -235,18 +252,39 @@ export function ConversationalView({
key={approval.id}
approval={approval}
isSelected={approval.id === selectedId}
onSelect={() => setSelectedId(approval.id)}
onSelect={() => handleMobileSelect(approval.id)}
/>
))
)}
</div>
</div>
{/* 右側: 詳情面板 */}
<div className="flex-1 flex flex-col overflow-hidden relative">
{/* 右側: 詳情面板 - 響應式 */}
{/* Mobile: 全屏覆蓋 | Tablet/Desktop: 右側面板 */}
<div className={cn(
'flex flex-col overflow-hidden relative',
// Phase 11.3: Mobile 全屏覆蓋模式
showMobileDetail
? 'fixed inset-0 z-40 bg-nothing-gray-50 md:relative md:inset-auto md:z-auto md:flex-1'
: 'hidden md:flex md:flex-1'
)}>
{/* Mobile 返回按鈕 */}
<div className="flex-shrink-0 flex items-center justify-between p-4 border-b border-nothing-gray-200 bg-white md:hidden">
<button
onClick={() => setShowMobileDetail(false)}
className="flex items-center gap-2 text-claw-blue hover:text-claw-blue/80 transition-colors"
>
<ChevronLeft className="w-5 h-5" />
<span className="text-sm font-medium">{t('backToList')}</span>
</button>
<div className="flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-mono bg-nothing-gray-100">
<span>{selectedIndex + 1} / {approvals.length}</span>
</div>
</div>
{/* Y 鍵長按進度指示器 */}
{isYKeyHolding && (
<div className="absolute top-0 left-0 right-0 h-1 bg-nothing-gray-200 z-10">
<div className="absolute top-0 left-0 right-0 h-1 bg-nothing-gray-200 z-10 md:top-0">
<div
className="h-full bg-status-healthy transition-all duration-100"
style={{ width: `${yKeyProgress}%` }}
@@ -255,7 +293,7 @@ export function ConversationalView({
)}
{selectedApproval ? (
<div className="flex-1 overflow-y-auto p-6">
<div className="flex-1 overflow-y-auto p-4 md:p-6">
<ApprovalCard
request={selectedApproval}
onApprove={handleApprove}