diff --git a/apps/web/src/components/ai/openclaw-state-machine.tsx b/apps/web/src/components/ai/openclaw-state-machine.tsx index 1630eb272..3e2bec026 100644 --- a/apps/web/src/components/ai/openclaw-state-machine.tsx +++ b/apps/web/src/components/ai/openclaw-state-machine.tsx @@ -22,7 +22,7 @@ import { cn } from '@/lib/utils' import { OpenClawPanel, type OpenClawStatus } from './openclaw-panel' import { ThinkingStream, DEFAULT_THINKING_MESSAGES } from './thinking-stream' import { ApprovalCard, type ApprovalRequest } from '@/components/approval/approval-card' -import { SlidePanel } from '@/components/ui/slide-panel' +import { ApprovalModal } from '@/components/ui/approval-modal' import { RefreshCw, AlertCircle, CheckCircle2, AlertTriangle, Shield, XCircle, ChevronRight } from 'lucide-react' // ============================================================================= @@ -410,8 +410,8 @@ export function OpenClawStateMachine({ )} - {/* Slide Panel - 詳情面板 */} - setSelectedIndex(null)} title="審核詳情" @@ -419,10 +419,9 @@ export function OpenClawStateMachine({ onNext={handleNextApproval} current={selectedIndex !== null ? selectedIndex + 1 : undefined} total={pendingApprovals.length} - width="lg" > {selectedApproval && ( -
+
{ @@ -442,7 +441,7 @@ export function OpenClawStateMachine({ />
)} - + {/* Idle state - no pending approvals */} {machineState === 'idle' && pendingApprovals.length === 0 && !isLoading && ( diff --git a/apps/web/src/components/ui/approval-modal.tsx b/apps/web/src/components/ui/approval-modal.tsx new file mode 100644 index 000000000..2987c3cfe --- /dev/null +++ b/apps/web/src/components/ui/approval-modal.tsx @@ -0,0 +1,153 @@ +'use client' + +/** + * ApprovalModal - 全屏審核 Modal + * =============================== + * Phase 17: UI/UX 修復 - 取代 SlidePanel + * + * 設計決策: + * - 審核是關鍵決策,需要用戶全神貫注 + * - 全屏遮罩強制用戶注意,防止誤操作 + * - 居中對話框提供足夠空間顯示所有資訊 + * - 符合 Nothing.tech「一次專注一件事」設計哲學 + * + * 參考: GitHub PR Review, AWS IAM Console + */ + +import { useEffect, useCallback } from 'react' +import { cn } from '@/lib/utils' +import { X, ChevronLeft, ChevronRight, Shield } from 'lucide-react' + +interface ApprovalModalProps { + open: boolean + onClose: () => void + children: React.ReactNode + title?: string + /** 上一個/下一個導航 */ + onPrev?: () => void + onNext?: () => void + /** 當前位置 (1-based) */ + current?: number + total?: number +} + +export function ApprovalModal({ + open, + onClose, + children, + title = '審核詳情', + onPrev, + onNext, + current, + total, +}: ApprovalModalProps) { + // ESC 關閉 + 方向鍵導航 + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + if (e.key === 'ArrowLeft' && onPrev) onPrev() + if (e.key === 'ArrowRight' && onNext) onNext() + }, [onClose, onPrev, onNext]) + + // 鎖定背景滾動 + useEffect(() => { + if (open) { + document.body.style.overflow = 'hidden' + document.addEventListener('keydown', handleKeyDown) + } + return () => { + document.body.style.overflow = '' + document.removeEventListener('keydown', handleKeyDown) + } + }, [open, handleKeyDown]) + + if (!open) return null + + return ( +
+ {/* Backdrop - 深色遮罩,強調聚焦 */} +
+ + {/* Modal Container */} +
+ {/* Header */} +
+
+
+ +
+
+

+ {title} +

+

+ Multi-Sig Authorization Required +

+
+
+ +
+ {/* 導航控制 */} + {(onPrev || onNext) && current && total && total > 1 && ( +
+ + + {current} / {total} + + +
+ )} + + {/* 關閉按鈕 */} + +
+
+ + {/* Content - 可滾動 */} +
+ {children} +
+
+
+ ) +} diff --git a/apps/web/src/components/ui/index.ts b/apps/web/src/components/ui/index.ts index 20219d211..cd6e24069 100644 --- a/apps/web/src/components/ui/index.ts +++ b/apps/web/src/components/ui/index.ts @@ -34,3 +34,6 @@ export { GridLinesBg, type DotMatrixBgProps, } from './dot-matrix-bg' + +// Approval Modal (Phase 17 UI/UX 修復) +export { ApprovalModal } from './approval-modal'