diff --git a/apps/web/src/components/ai/openclaw-state-machine.tsx b/apps/web/src/components/ai/openclaw-state-machine.tsx index 3e2bec026..48b75eeb2 100644 --- a/apps/web/src/components/ai/openclaw-state-machine.tsx +++ b/apps/web/src/components/ai/openclaw-state-machine.tsx @@ -421,25 +421,24 @@ export function OpenClawStateMachine({ total={pendingApprovals.length} > {selectedApproval && ( -
- { - handleApprove(selectedApproval.id) - // 簽核後移到下一個或關閉 - if (selectedIndex !== null && selectedIndex < pendingApprovals.length - 1) { - setSelectedIndex(selectedIndex + 1) - } else { - setSelectedIndex(null) - } - }} - onReject={() => { - handleReject(selectedApproval.id) + { + handleApprove(selectedApproval.id) + // 簽核後移到下一個或關閉 + if (selectedIndex !== null && selectedIndex < pendingApprovals.length - 1) { + setSelectedIndex(selectedIndex + 1) + } else { setSelectedIndex(null) - }} - holdDuration={1000} - /> -
+ } + }} + onReject={() => { + handleReject(selectedApproval.id) + setSelectedIndex(null) + }} + holdDuration={1000} + fullHeight + /> )} diff --git a/apps/web/src/components/approval/approval-card.tsx b/apps/web/src/components/approval/approval-card.tsx index 946ded02d..ad6682072 100644 --- a/apps/web/src/components/approval/approval-card.tsx +++ b/apps/web/src/components/approval/approval-card.tsx @@ -82,6 +82,8 @@ export interface ApprovalCardProps { readOnly?: boolean /** 最終狀態標籤 (歷史紀錄用) */ finalStatus?: 'approved' | 'rejected' | 'executed' | 'failed' + /** 在 Modal 中使用時,移除高度限制 */ + fullHeight?: boolean } // ============================================================================= @@ -263,6 +265,7 @@ export function ApprovalCard({ isLoading = false, readOnly = false, finalStatus, + fullHeight = false, }: ApprovalCardProps) { const t = useTranslations('approval') const tRisk = useTranslations('risk') @@ -331,8 +334,8 @@ export function ApprovalCard({ padding="none" className={cn( 'relative transition-all duration-300 flex flex-col', - // UX 優化: 限制最大高度,內容可滾動 - 'max-h-[70vh]', + // UX 優化: 限制最大高度,內容可滾動 (Modal 模式除外) + !fullHeight && 'max-h-[70vh]', // Nothing.tech 精密極簡風格 !readOnly && 'hover:-translate-y-0.5 hover:shadow-card-hover', // CRITICAL: 左側紅色粗邊框 (乾淨、無光害) diff --git a/apps/web/src/components/ui/approval-modal.tsx b/apps/web/src/components/ui/approval-modal.tsx index 2987c3cfe..75548c1c5 100644 --- a/apps/web/src/components/ui/approval-modal.tsx +++ b/apps/web/src/components/ui/approval-modal.tsx @@ -10,11 +10,13 @@ * - 全屏遮罩強制用戶注意,防止誤操作 * - 居中對話框提供足夠空間顯示所有資訊 * - 符合 Nothing.tech「一次專注一件事」設計哲學 + * - 使用 Portal 渲染到 body,確保不受父容器限制 * * 參考: GitHub PR Review, AWS IAM Console */ -import { useEffect, useCallback } from 'react' +import { useEffect, useCallback, useState } from 'react' +import { createPortal } from 'react-dom' import { cn } from '@/lib/utils' import { X, ChevronLeft, ChevronRight, Shield } from 'lucide-react' @@ -48,6 +50,13 @@ export function ApprovalModal({ if (e.key === 'ArrowRight' && onNext) onNext() }, [onClose, onPrev, onNext]) + // Portal 目標 + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + // 鎖定背景滾動 useEffect(() => { if (open) { @@ -60,10 +69,11 @@ export function ApprovalModal({ } }, [open, handleKeyDown]) - if (!open) return null + if (!open || !mounted) return null - return ( -
+ // 使用 Portal 渲染到 body,確保全屏覆蓋 + return createPortal( +
{/* Backdrop - 深色遮罩,強調聚焦 */}
{/* Content - 可滾動 */} -
+
{children}
-
+
, + document.body ) }