일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- fire base
- react native CLI
- react native font
- firebase 라이브러리
- Project
- 리액트
- aws bucket 정책
- error
- img upload
- 리엑트 네이티브 아이콘
- react native 세팅
- react native 개발
- js
- react native
- s3 upload
- Access Key 생성
- 백준
- AWS
- 문자열 대소문자 구별
- babel.config.js
- React
- 문자열 대소문자
- PongWorld
- AWS Access Key
- 에러
- 리액트 네이티브 에러
- Next.js
- GIT
- 리액트 네이티브
- react native picker
- Today
- Total
밝을희 클태
[shadcn / radix-ui] Drawer 위에서 toast 동작이 막힘 본문

이미지처럼 drawer 위에 toast를 띄우는 상황이 있는데 사용자가 toast를 스와이프 해서 없애는 동작이 전혀 되지 않는 상황이 있다.
원인
drawer 내부에는 overlay라는 컴포넌트가 존재한다.
이 overlay는 단순히 배경을 어둡게 처리하는 역할만 하는 게 아니라,
drawer 외부의 모든 사용자 상호작용을 차단하는 역할을 한다.
그렇다면 왜 overlay는 drawer 외부의 동작까지 막고 있을까?
이는 drawer가 Radix UI의 Dialog 컴포넌트를 기반으로 만들어졌기 때문이다.
Radix의 Dialog는 모달 UI의 접근성과 정확한 동작을 보장하기 위해, 다음과 같은 기능들을 제공한다:
- 배경 어둡게 처리 (dimmed background)
- 포커스 트랩 (Focus Trap)
- 외부 클릭 시 닫기 (Outside click to close)
- Esc 키로 닫기 (Escape key to close)
이러한 기능들이 정확하게 동작하려면,
오버레이(overlay)가 시각적으로만 위에 있는 것이 아니라,
기술적으로도 가장 상단 레이어에서 모든 이벤트를 먼저 받아야 한다.
dialog 구조
<Dialog.Root> <Dialog.Trigger /> <Dialog.Portal> <Dialog.Overlay /> <Dialog.Content> <Dialog.Title /> <Dialog.Description /> <Dialog.Close /> </Dialog.Content> </Dialog.Portal> </Dialog.Root>
해결 방법 1 (실패) - portal + zIndex 사용
Drawer 위에 떠야 하는 고정(fixed) 컴포넌트들을,
Drawer와 동일하게 Portal을 이용해 body의 직속 자식 노드로 렌더링했다.
그 후 해당 컴포넌트에 z-index를 DrawerOverlay보다 더 높게 설정했지만,
상호작용(클릭, 포커스 등)이 여전히 차단되는 문제는 해결되지 않았다.
해결 방법 2 (성공)
DrawerContent에 onInteractOutside 핸들러를 사용해,
Drawer 외부에서 발생한 포인터 또는 포커스 이벤트를 감지하고 제어할 수 있도록 처리했다.
이 핸들러는 Radix UI에서 제공하는 기능으로,
외부 영역을 클릭하거나 포커스가 이동했을 때 실행되며,
필요 시 내부에서 event.preventDefault()를 호출해 Drawer가 닫히지 않도록 막을 수 있다.
공식 문서 설명
Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling event.preventDefault.
drawer.tsx 파일 수정
const DrawerContent = React.forwardRef< React.ElementRef<typeof DrawerPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> >(({ className, children, ...props }, ref) => ( <DrawerPortal> <DrawerOverlay /> <DrawerPrimitive.Content .... // ******* 추가 ********* onInteractOutside={e => { const { originalEvent } = e.detail; console.log(originalEvent) if ( originalEvent.target instanceof Element && (originalEvent.target.closest(".toast")) ) { e.preventDefault(); } }} // ********************* {...props} > {children} </DrawerPrimitive.Content> </DrawerPortal> ))
Toaster에 toast class 추가
<Toaster position="bottom-center" className="toast" />
toast 호출 시 className에 toast pointer-events-auto 추가
toast.success('콜 예정 거래처를 업데이트했어요.', { className: 'success-toast toast pointer-events-auto', icon: <Success style={{ width: 24, height: 24 }} />, });
위처럼 외부 요소에 이벤트가 발생했을 때,
해당 요소가 특정 조건(.toast 클래스)을 만족하는지를 판별해,
그 경우에는 event.preventDefault()를 호출함으로써
Drawer의 닫힘을 방지하는 동시에, 해당 요소에서의 터치, 클릭, 스와이프 등 상호작용도 정상적으로 작동하도록 만들 수 있다.
'PongWorld 프로젝트 > 트러블 슈팅' 카테고리의 다른 글
SPA 게임 개발에서 웹소켓 연결 끊김과 서버 다운 문제 해결: 이벤트 리스너 관리 (1) | 2024.03.24 |
---|---|
메세지를 읽지 않았는데 안 읽은 메세지가 안뜬다... (2) | 2024.03.14 |
웹소켓 연결 비동기 문제 해결 (0) | 2024.03.10 |