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 - 可滾動 */}
-
-
+
,
+ document.body
)
}