fix(web): 審核 Modal 真正全屏 - Portal 渲染到 body
修復: 1. 使用 createPortal 渲染到 document.body 2. z-index 提升到 9999 確保最上層 3. ApprovalCard 新增 fullHeight prop 移除高度限制 4. 移除巢狀 div 避免滾動衝突 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -421,25 +421,24 @@ export function OpenClawStateMachine({
|
||||
total={pendingApprovals.length}
|
||||
>
|
||||
{selectedApproval && (
|
||||
<div className="p-6">
|
||||
<ApprovalCard
|
||||
request={selectedApproval}
|
||||
onApprove={() => {
|
||||
handleApprove(selectedApproval.id)
|
||||
// 簽核後移到下一個或關閉
|
||||
if (selectedIndex !== null && selectedIndex < pendingApprovals.length - 1) {
|
||||
setSelectedIndex(selectedIndex + 1)
|
||||
} else {
|
||||
setSelectedIndex(null)
|
||||
}
|
||||
}}
|
||||
onReject={() => {
|
||||
handleReject(selectedApproval.id)
|
||||
<ApprovalCard
|
||||
request={selectedApproval}
|
||||
onApprove={() => {
|
||||
handleApprove(selectedApproval.id)
|
||||
// 簽核後移到下一個或關閉
|
||||
if (selectedIndex !== null && selectedIndex < pendingApprovals.length - 1) {
|
||||
setSelectedIndex(selectedIndex + 1)
|
||||
} else {
|
||||
setSelectedIndex(null)
|
||||
}}
|
||||
holdDuration={1000}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}}
|
||||
onReject={() => {
|
||||
handleReject(selectedApproval.id)
|
||||
setSelectedIndex(null)
|
||||
}}
|
||||
holdDuration={1000}
|
||||
fullHeight
|
||||
/>
|
||||
)}
|
||||
</ApprovalModal>
|
||||
|
||||
|
||||
@@ -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: 左側紅色粗邊框 (乾淨、無光害)
|
||||
|
||||
@@ -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 (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
// 使用 Portal 渲染到 body,確保全屏覆蓋
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-[9999] flex items-center justify-center">
|
||||
{/* Backdrop - 深色遮罩,強調聚焦 */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200"
|
||||
@@ -144,10 +154,11 @@ export function ApprovalModal({
|
||||
</div>
|
||||
|
||||
{/* Content - 可滾動 */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user