리사이징 유지와 연속작성 구현 모달 살짝 늘어나는 문제 해결

This commit is contained in:
leeheejin 2025-11-24 15:52:45 +09:00
parent 55204dd38c
commit f286b6c695
4 changed files with 275 additions and 144 deletions

View File

@ -57,16 +57,18 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
// 폼 데이터 상태 추가
const [formData, setFormData] = useState<Record<string, any>>({});
// 연속 등록 모드 상태 (localStorage에 저장하여 리렌더링에 영향받지 않도록)
const continuousModeRef = useRef(false);
const [, setForceUpdate] = useState(0); // 강제 리렌더링용 (값은 사용하지 않음)
// 연속 등록 모드 상태 (state로 변경 - 체크박스 UI 업데이트를 위해)
const [continuousMode, setContinuousMode] = useState(false);
// 화면 리셋 키 (컴포넌트 강제 리마운트용)
const [resetKey, setResetKey] = useState(0);
// localStorage에서 연속 모드 상태 복원
useEffect(() => {
const savedMode = localStorage.getItem("screenModal_continuousMode");
if (savedMode === "true") {
continuousModeRef.current = true;
// console.log("🔄 연속 모드 복원: true");
setContinuousMode(true);
console.log("🔄 연속 모드 복원: true");
}
}, []);
@ -162,29 +164,39 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
});
setScreenData(null);
setFormData({});
continuousModeRef.current = false;
setContinuousMode(false);
localStorage.setItem("screenModal_continuousMode", "false"); // localStorage에 저장
// console.log("🔄 연속 모드 초기화: false");
console.log("🔄 연속 모드 초기화: false");
};
// 저장 성공 이벤트 처리 (연속 등록 모드 지원)
const handleSaveSuccess = () => {
const isContinuousMode = continuousModeRef.current;
// console.log("💾 저장 성공 이벤트 수신");
// console.log("📌 현재 연속 모드 상태 (ref):", isContinuousMode);
// console.log("📌 localStorage:", localStorage.getItem("screenModal_continuousMode"));
const isContinuousMode = continuousMode;
console.log("💾 저장 성공 이벤트 수신");
console.log("📌 현재 연속 모드 상태:", isContinuousMode);
console.log("📌 localStorage:", localStorage.getItem("screenModal_continuousMode"));
if (isContinuousMode) {
// 연속 모드: 폼만 초기화하고 모달은 유지
// console.log("✅ 연속 모드 활성화 - 폼만 초기화");
console.log("✅ 연속 모드 활성화 - 폼 초기화 및 화면 리셋");
// 폼만 초기화 (연속 모드 상태는 localStorage에 저장되어 있으므로 유지됨)
// 1. 폼 데이터 초기화
setFormData({});
// 2. 리셋 키 변경 (컴포넌트 강제 리마운트)
setResetKey(prev => prev + 1);
console.log("🔄 resetKey 증가 - 컴포넌트 리마운트");
// 3. 화면 데이터 다시 로드 (채번 규칙 새로 생성)
if (modalState.screenId) {
console.log("🔄 화면 데이터 다시 로드:", modalState.screenId);
loadScreenData(modalState.screenId);
}
toast.success("저장되었습니다. 계속 입력하세요.");
} else {
// 일반 모드: 모달 닫기
// console.log("❌ 일반 모드 - 모달 닫기");
console.log("❌ 일반 모드 - 모달 닫기");
handleCloseModal();
}
};
@ -198,7 +210,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
window.removeEventListener("closeSaveModal", handleCloseModal);
window.removeEventListener("saveSuccessInModal", handleSaveSuccess);
};
}, []); // 의존성 제거 (ref 사용으로 최신 상태 참조)
}, [continuousMode]); // continuousMode 의존성 추가
// 화면 데이터 로딩
useEffect(() => {
@ -415,18 +427,21 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
setFormData({}); // 폼 데이터 초기화
};
// 모달 크기 설정 - 화면 내용에 맞게 동적 조정
// 모달 크기 설정 - 화면관리 설정 크기 + 헤더/푸터
const getModalStyle = () => {
if (!screenDimensions) {
return {
className: "w-fit min-w-[400px] max-w-4xl max-h-[90vh] overflow-hidden p-0",
style: {},
style: undefined, // undefined로 변경 - defaultWidth/defaultHeight 사용
};
}
// 헤더 높이를 최소화 (제목 영역만)
const headerHeight = 60; // DialogHeader 최소 높이 (타이틀 + 최소 패딩)
const totalHeight = screenDimensions.height + headerHeight;
// 화면관리에서 설정한 크기 = 컨텐츠 영역 크기
// 실제 모달 크기 = 컨텐츠 + 헤더 + 연속등록 체크박스
const headerHeight = 60; // DialogHeader (타이틀 + 패딩)
const footerHeight = 52; // 연속 등록 모드 체크박스 영역
const totalHeight = screenDimensions.height + headerHeight + footerHeight;
return {
className: "overflow-hidden p-0",
@ -504,7 +519,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
<ResizableDialog open={modalState.isOpen} onOpenChange={handleClose}>
<ResizableDialogContent
className={`${modalStyle.className} ${className || ""}`}
style={modalStyle.style}
{...(modalStyle.style && { style: modalStyle.style })} // undefined일 때는 prop 자체를 전달하지 않음
defaultWidth={600}
defaultHeight={800}
minWidth={500}
@ -530,7 +545,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
</div>
</ResizableDialogHeader>
<div className="flex-1 overflow-auto p-6">
<div className="flex-1 overflow-auto">
{loading ? (
<div className="flex h-full items-center justify-center">
<div className="text-center">
@ -568,7 +583,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
return (
<InteractiveScreenViewerDynamic
key={component.id}
key={`${component.id}-${resetKey}`}
component={adjustedComponent}
allComponents={screenData.components}
formData={formData}
@ -607,13 +622,12 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
<div className="flex items-center gap-2">
<Checkbox
id="continuous-mode"
checked={continuousModeRef.current}
checked={continuousMode}
onCheckedChange={(checked) => {
const isChecked = checked === true;
continuousModeRef.current = isChecked;
setContinuousMode(isChecked);
localStorage.setItem("screenModal_continuousMode", String(isChecked));
setForceUpdate((prev) => prev + 1); // 체크박스 UI 업데이트를 위한 강제 리렌더링
// console.log("🔄 연속 모드 변경:", isChecked);
console.log("🔄 연속 모드 변경:", isChecked);
}}
/>
<Label htmlFor="continuous-mode" className="cursor-pointer text-sm font-normal select-none">

View File

@ -92,18 +92,18 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
const contentWidth = maxX - minX;
const contentHeight = maxY - minY;
// 적절한 여백 추가
const paddingX = 40;
const paddingY = 40;
// 적절한 여백 추가 (주석처리 - 사용자 설정 크기 그대로 사용)
// const paddingX = 40;
// const paddingY = 40;
const finalWidth = Math.max(contentWidth + paddingX, 400);
const finalHeight = Math.max(contentHeight + paddingY, 300);
const finalWidth = Math.max(contentWidth, 400); // padding 제거
const finalHeight = Math.max(contentHeight, 300); // padding 제거
return {
width: Math.min(finalWidth, window.innerWidth * 0.95),
height: Math.min(finalHeight, window.innerHeight * 0.9),
offsetX: Math.max(0, minX - paddingX / 2),
offsetY: Math.max(0, minY - paddingY / 2),
offsetX: Math.max(0, minX), // paddingX 제거
offsetY: Math.max(0, minY), // paddingY 제거
};
};
@ -269,16 +269,18 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
}
};
// 모달 크기 설정 - ScreenModal과 동일
// 모달 크기 설정 - 화면관리 설정 크기 + 헤더
const getModalStyle = () => {
if (!screenDimensions) {
return {
className: "w-fit min-w-[400px] max-w-4xl max-h-[90vh] overflow-hidden p-0",
style: {},
style: undefined, // undefined로 변경 - defaultWidth/defaultHeight 사용
};
}
const headerHeight = 60;
// 화면관리에서 설정한 크기 = 컨텐츠 영역 크기
// 실제 모달 크기 = 컨텐츠 + 헤더
const headerHeight = 60; // DialogHeader
const totalHeight = screenDimensions.height + headerHeight;
return {

View File

@ -216,10 +216,16 @@ export const SaveModal: React.FC<SaveModalProps> = ({
return y + height;
}));
const padding = 40;
// 컨텐츠 영역 크기 (화면관리 설정 크기)
const contentWidth = Math.max(maxX, 400);
const contentHeight = Math.max(maxY, 300);
// 실제 모달 크기 = 컨텐츠 + 헤더
const headerHeight = 60; // DialogHeader
return {
width: Math.max(maxX + padding, 400),
height: Math.max(maxY + padding, 300),
width: contentWidth,
height: contentHeight + headerHeight, // 헤더 높이 포함
};
};
@ -229,8 +235,12 @@ export const SaveModal: React.FC<SaveModalProps> = ({
<ResizableDialog open={isOpen} onOpenChange={(open) => !isSaving && !open && onClose()}>
<ResizableDialogContent
modalId={`save-modal-${screenId}`}
defaultWidth={dynamicSize.width + 48}
defaultHeight={dynamicSize.height + 120}
style={{
width: `${dynamicSize.width}px`,
height: `${dynamicSize.height}px`, // 화면관리 설정 크기 그대로 사용
}}
defaultWidth={600} // 폴백용 기본값
defaultHeight={400} // 폴백용 기본값
minWidth={400}
minHeight={300}
className="gap-0 p-0"

View File

@ -122,6 +122,10 @@ const ResizableDialogContent = React.forwardRef<
// 1순위: userStyle에서 크기 추출 (화면관리에서 지정한 크기 - 항상 초기값으로 사용)
if (userStyle) {
console.log("🔍 userStyle 감지:", userStyle);
console.log("🔍 userStyle.width 타입:", typeof userStyle.width, "값:", userStyle.width);
console.log("🔍 userStyle.height 타입:", typeof userStyle.height, "값:", userStyle.height);
const styleWidth = typeof userStyle.width === 'string'
? parseInt(userStyle.width)
: userStyle.width;
@ -129,24 +133,41 @@ const ResizableDialogContent = React.forwardRef<
? parseInt(userStyle.height)
: userStyle.height;
console.log("📏 파싱된 크기:", {
styleWidth,
styleHeight,
"styleWidth truthy?": !!styleWidth,
"styleHeight truthy?": !!styleHeight,
minWidth,
maxWidth,
minHeight,
maxHeight
});
if (styleWidth && styleHeight) {
return {
const finalSize = {
width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
};
console.log("✅ userStyle 크기 사용:", finalSize);
return finalSize;
} else {
console.log("❌ styleWidth 또는 styleHeight가 falsy:", { styleWidth, styleHeight });
}
}
// 2순위: 현재 렌더링된 크기 사용
if (contentRef.current) {
const rect = contentRef.current.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
return {
width: Math.max(minWidth, Math.min(maxWidth, rect.width)),
height: Math.max(minHeight, Math.min(maxHeight, rect.height)),
};
}
}
console.log("⚠️ userStyle 없음, defaultWidth/defaultHeight 사용:", { defaultWidth, defaultHeight });
// 2순위: 현재 렌더링된 크기 사용 (주석처리 - 모달이 열린 후 늘어나는 현상 방지)
// if (contentRef.current) {
// const rect = contentRef.current.getBoundingClientRect();
// if (rect.width > 0 && rect.height > 0) {
// return {
// width: Math.max(minWidth, Math.min(maxWidth, rect.width)),
// height: Math.max(minHeight, Math.min(maxHeight, rect.height)),
// };
// }
// }
// 3순위: defaultWidth/defaultHeight 사용
return { width: defaultWidth, height: defaultHeight };
@ -156,6 +177,58 @@ const ResizableDialogContent = React.forwardRef<
const [isResizing, setIsResizing] = React.useState(false);
const [resizeDirection, setResizeDirection] = React.useState<string>("");
const [isInitialized, setIsInitialized] = React.useState(false);
// userStyle이 변경되면 크기 업데이트 (화면 데이터 로딩 완료 시)
React.useEffect(() => {
// 1. localStorage에서 사용자가 리사이징한 크기 확인
let savedSize: { width: number; height: number; userResized: boolean } | null = null;
if (effectiveModalId && typeof window !== 'undefined') {
try {
const storageKey = `modal_size_${effectiveModalId}_${userId}`;
const saved = localStorage.getItem(storageKey);
if (saved) {
const parsed = JSON.parse(saved);
if (parsed.userResized) {
savedSize = {
width: Math.max(minWidth, Math.min(maxWidth, parsed.width)),
height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
userResized: true,
};
console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
}
}
} catch (error) {
console.error("❌ 모달 크기 복원 실패:", error);
}
}
// 2. 우선순위: 사용자 리사이징 > userStyle > 기본값
if (savedSize && savedSize.userResized) {
// 사용자가 리사이징한 크기 우선
setSize({ width: savedSize.width, height: savedSize.height });
setUserResized(true);
console.log("✅ 사용자 리사이징 크기 적용:", savedSize);
} else if (userStyle && userStyle.width && userStyle.height) {
// 화면관리에서 설정한 크기
const styleWidth = typeof userStyle.width === 'string'
? parseInt(userStyle.width)
: userStyle.width;
const styleHeight = typeof userStyle.height === 'string'
? parseInt(userStyle.height)
: userStyle.height;
if (styleWidth && styleHeight) {
const newSize = {
width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
};
console.log("🔄 userStyle 크기 적용:", newSize);
setSize(newSize);
}
}
}, [userStyle, minWidth, maxWidth, minHeight, maxHeight, effectiveModalId, userId]);
const [lastModalId, setLastModalId] = React.useState<string | null>(null);
const [userResized, setUserResized] = React.useState(false); // 사용자가 실제로 리사이징했는지 추적
@ -192,97 +265,98 @@ const ResizableDialogContent = React.forwardRef<
}, [effectiveModalId, lastModalId, isInitialized]);
// 모달이 열릴 때 초기 크기 설정 (localStorage와 내용 크기 중 큰 값 사용)
React.useEffect(() => {
// console.log("🔍 초기 크기 설정 useEffect 실행:", { isInitialized, hasContentRef: !!contentRef.current, effectiveModalId });
if (!isInitialized) {
// 내용의 실제 크기 측정 (약간의 지연 후, contentRef가 준비될 때까지 대기)
// 여러 번 시도하여 contentRef가 준비될 때까지 대기
let attempts = 0;
const maxAttempts = 10;
const measureContent = () => {
attempts++;
// scrollHeight/scrollWidth를 사용하여 실제 내용 크기 측정 (스크롤 포함)
let contentWidth = defaultWidth;
let contentHeight = defaultHeight;
if (contentRef.current) {
// scrollHeight/scrollWidth 그대로 사용 (여유 공간 제거)
contentWidth = contentRef.current.scrollWidth || defaultWidth;
contentHeight = contentRef.current.scrollHeight || defaultHeight;
// console.log("📏 모달 내용 크기 측정:", { attempt: attempts, scrollWidth: contentRef.current.scrollWidth, scrollHeight: contentRef.current.scrollHeight, clientWidth: contentRef.current.clientWidth, clientHeight: contentRef.current.clientHeight, contentWidth, contentHeight });
} else {
// console.log("⚠️ contentRef 없음, 재시도:", { attempt: attempts, maxAttempts, defaultWidth, defaultHeight });
// contentRef가 아직 없으면 재시도
if (attempts < maxAttempts) {
setTimeout(measureContent, 100);
return;
}
}
// 패딩 추가 (p-6 * 2 = 48px)
const paddingAndMargin = 48;
const initialSize = getInitialSize();
// 내용 크기 기반 최소 크기 계산
const contentBasedSize = {
width: Math.max(minWidth, Math.min(maxWidth, Math.max(contentWidth + paddingAndMargin, initialSize.width))),
height: Math.max(minHeight, Math.min(maxHeight, Math.max(contentHeight + paddingAndMargin, initialSize.height))),
};
// console.log("📐 내용 기반 크기:", contentBasedSize);
// localStorage에서 저장된 크기 확인
let finalSize = contentBasedSize;
if (effectiveModalId && typeof window !== 'undefined') {
try {
const storageKey = `modal_size_${effectiveModalId}_${userId}`;
const saved = localStorage.getItem(storageKey);
// console.log("📦 localStorage 확인:", { effectiveModalId, userId, storageKey, saved: saved ? "있음" : "없음" });
if (saved) {
const parsed = JSON.parse(saved);
// userResized 플래그 확인
if (parsed.userResized) {
const savedSize = {
width: Math.max(minWidth, Math.min(maxWidth, parsed.width)),
height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
};
// console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
// ✅ 중요: 사용자가 명시적으로 리사이징한 경우, 사용자 크기를 우선 사용
// (사용자가 의도적으로 작게 만든 것을 존중)
finalSize = savedSize;
setUserResized(true);
// console.log("✅ 최종 크기 (사용자가 설정한 크기 우선 적용):", { savedSize, contentBasedSize, finalSize, note: "사용자가 리사이징한 크기를 그대로 사용합니다" });
} else {
// console.log(" 자동 계산된 크기는 무시, 내용 크기 사용");
}
} else {
// console.log(" localStorage에 저장된 크기 없음, 내용 크기 사용");
}
} catch (error) {
// console.error("❌ 모달 크기 복원 실패:", error);
}
}
setSize(finalSize);
setIsInitialized(true);
};
// 첫 시도는 300ms 후에 시작
setTimeout(measureContent, 300);
}
}, [isInitialized, getInitialSize, effectiveModalId, userId, minWidth, maxWidth, minHeight, maxHeight, defaultWidth, defaultHeight]);
// 주석처리 - 사용자가 설정한 크기(userStyle)만 사용하도록 변경
// React.useEffect(() => {
// // console.log("🔍 초기 크기 설정 useEffect 실행:", { isInitialized, hasContentRef: !!contentRef.current, effectiveModalId });
//
// if (!isInitialized) {
// // 내용의 실제 크기 측정 (약간의 지연 후, contentRef가 준비될 때까지 대기)
// // 여러 번 시도하여 contentRef가 준비될 때까지 대기
// let attempts = 0;
// const maxAttempts = 10;
//
// const measureContent = () => {
// attempts++;
//
// // scrollHeight/scrollWidth를 사용하여 실제 내용 크기 측정 (스크롤 포함)
// let contentWidth = defaultWidth;
// let contentHeight = defaultHeight;
//
// // if (contentRef.current) {
// // // scrollHeight/scrollWidth 그대로 사용 (여유 공간 제거)
// // contentWidth = contentRef.current.scrollWidth || defaultWidth;
// // contentHeight = contentRef.current.scrollHeight || defaultHeight;
// //
// // // console.log("📏 모달 내용 크기 측정:", { attempt: attempts, scrollWidth: contentRef.current.scrollWidth, scrollHeight: contentRef.current.scrollHeight, clientWidth: contentRef.current.clientWidth, clientHeight: contentRef.current.clientHeight, contentWidth, contentHeight });
// // } else {
// // // console.log("⚠️ contentRef 없음, 재시도:", { attempt: attempts, maxAttempts, defaultWidth, defaultHeight });
// //
// // // contentRef가 아직 없으면 재시도
// // if (attempts < maxAttempts) {
// // setTimeout(measureContent, 100);
// // return;
// // }
// // }
//
// // 패딩 추가 (p-6 * 2 = 48px)
// const paddingAndMargin = 48;
// const initialSize = getInitialSize();
//
// // 내용 크기 기반 최소 크기 계산
// const contentBasedSize = {
// width: Math.max(minWidth, Math.min(maxWidth, Math.max(contentWidth + paddingAndMargin, initialSize.width))),
// height: Math.max(minHeight, Math.min(maxHeight, Math.max(contentHeight + paddingAndMargin, initialSize.height))),
// };
//
// // console.log("📐 내용 기반 크기:", contentBasedSize);
//
// // localStorage에서 저장된 크기 확인
// let finalSize = contentBasedSize;
//
// if (effectiveModalId && typeof window !== 'undefined') {
// try {
// const storageKey = `modal_size_${effectiveModalId}_${userId}`;
// const saved = localStorage.getItem(storageKey);
//
// // console.log("📦 localStorage 확인:", { effectiveModalId, userId, storageKey, saved: saved ? "있음" : "없음" });
//
// if (saved) {
// const parsed = JSON.parse(saved);
//
// // userResized 플래그 확인
// if (parsed.userResized) {
// const savedSize = {
// width: Math.max(minWidth, Math.min(maxWidth, parsed.width)),
// height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
// };
//
// // console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
//
// // ✅ 중요: 사용자가 명시적으로 리사이징한 경우, 사용자 크기를 우선 사용
// // (사용자가 의도적으로 작게 만든 것을 존중)
// finalSize = savedSize;
// setUserResized(true);
//
// // console.log("✅ 최종 크기 (사용자가 설정한 크기 우선 적용):", { savedSize, contentBasedSize, finalSize, note: "사용자가 리사이징한 크기를 그대로 사용합니다" });
// } else {
// // console.log(" 자동 계산된 크기는 무시, 내용 크기 사용");
// }
// } else {
// // console.log(" localStorage에 저장된 크기 없음, 내용 크기 사용");
// }
// } catch (error) {
// // console.error("❌ 모달 크기 복원 실패:", error);
// }
// }
//
// setSize(finalSize);
// setIsInitialized(true);
// };
//
// // 첫 시도는 300ms 후에 시작
// setTimeout(measureContent, 300);
// }
// }, [isInitialized, getInitialSize, effectiveModalId, userId, minWidth, maxWidth, minHeight, maxHeight, defaultWidth, defaultHeight]);
const startResize = (direction: string) => (e: React.MouseEvent) => {
e.preventDefault();
@ -433,6 +507,37 @@ const ResizableDialogContent = React.forwardRef<
onMouseDown={startResize("nw")}
/>
{/* 리셋 버튼 (사용자가 리사이징한 경우만 표시) */}
{userResized && (
<button
onClick={() => {
// localStorage에서 저장된 크기 삭제
if (effectiveModalId && typeof window !== 'undefined') {
const storageKey = `modal_size_${effectiveModalId}_${userId}`;
localStorage.removeItem(storageKey);
console.log("🗑️ 저장된 모달 크기 삭제:", storageKey);
}
// 화면관리 설정 크기로 복원
const initialSize = getInitialSize();
setSize(initialSize);
setUserResized(false);
console.log("🔄 기본 크기로 리셋:", initialSize);
}}
className="absolute right-12 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
style={{ zIndex: 20 }}
title="기본 크기로 리셋"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
<path d="M21 3v5h-5"/>
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
<path d="M3 21v-5h5"/>
</svg>
<span className="sr-only"> </span>
</button>
)}
<DialogPrimitive.Close
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
style={{ zIndex: 20 }}