Conversation
Walkthrough홈과 바텀시트에서 상태에 따라 스크롤을 잠그는 유틸리티(handleScrollLock)를 도입하고, Home과 BottomSheet 컴포넌트에서 호출하도록 연결했습니다. 새 유틸 함수는 body 스타일의 overflow/touch-action을 토글합니다. 공개 API 시그니처 변경은 없습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant Home as Home Component
participant BS as BottomSheet
participant SL as handleScrollLock
participant DOM as document.body
rect rgba(230, 240, 255, 0.6)
Note over Home: 홈 최초 진입/상태 변화
U->>Home: 진입/상태 계산
Home->>SL: handleScrollLock(needsMatchingSetup)
SL->>DOM: set/remove overflow,touch-action
end
rect rgba(230, 255, 230, 0.6)
Note over BS: 바텀시트 열림/닫힘
U->>BS: Open/Close
BS->>SL: handleScrollLock(isOpen)
SL->>DOM: set/remove overflow,touch-action
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 없음) Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
MATEBALL-STORYBOOK |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/shared/utils/scroll-lock.ts (1)
1-9: 접근성 고려: pinch-zoom 차단 최소화 검토body에
touch-action: none은 핀치 줌까지 막습니다. 필요 최소 범위(예: 오버레이/시트 래퍼)에 한정하거나manipulation/pan-y등으로 완화할지 검토해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/pages/home/home.tsx(2 hunks)src/shared/components/bottom-sheet/bottom-sheet.tsx(2 hunks)src/shared/utils/scroll-lock.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-29T18:02:42.616Z
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#7
File: src/shared/routes/Router.tsx:4-4
Timestamp: 2025-06-29T18:02:42.616Z
Learning: The Home component file in the MATEBALL-CLIENT project was renamed from Home.tsx to home.tsx, so the correct import path is 'pages/home/home'.
Applied to files:
src/pages/home/home.tsx
🧬 Code graph analysis (2)
src/shared/components/bottom-sheet/bottom-sheet.tsx (1)
src/shared/utils/scroll-lock.ts (1)
handleScrollLock(1-9)
src/pages/home/home.tsx (1)
src/shared/utils/scroll-lock.ts (1)
handleScrollLock(1-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
src/pages/home/home.tsx (1)
43-44: 렌더 중 DOM 사이드 이펙트 제거: useEffect로 이동 및 클린업 추가 필요
렌더 단계에서document.body를 조작하면 React StrictMode 하에서 이중 호출 또는 언마운트 누락 시 스크롤 락이 해제되지 않을 수 있습니다. 아래처럼useEffect로 옮기고 클린업을 등록해주세요.- handleScrollLock(needsMatchingSetup); + useEffect(() => { + if (!needsMatchingSetup) return; + handleScrollLock(true); + return () => handleScrollLock(false); + }, [needsMatchingSetup]);온보딩 모달과 캘린더/경기 바텀시트가 동시에 열릴 수 있는지 확인하고, 동시 가능 시 BottomSheet 쪽 락 참조 카운트가 의도한 대로 동작하는지 꼭 검증해주세요.
| handleScrollLock(isOpen); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
렌더 중 body 스타일 변경 제거 — 이펙트+클린업으로 전환
handleScrollLock(isOpen)을 렌더에서 호출하면 리렌더마다 DOM을 건드립니다. 이펙트로 감싸고 언마운트 시 원복해 주세요.
- handleScrollLock(isOpen);
+ useEffect(() => {
+ if (!isOpen) return;
+ handleScrollLock(true);
+ return () => handleScrollLock(false);
+ }, [isOpen]);추가 수정(파일 상단 import):
// 기존
import { useRef } from 'react';
// 변경
import { useEffect, useRef } from 'react';🤖 Prompt for AI Agents
In src/shared/components/bottom-sheet/bottom-sheet.tsx around lines 29-30,
remove the direct call to handleScrollLock(isOpen) from the render path and
instead add a useEffect that calls handleScrollLock(isOpen) when isOpen changes
and returns a cleanup that restores the body styles (i.e., calls
handleScrollLock(false) or reverts whatever changes were applied); also update
the imports at the top to include useEffect alongside useRef. Ensure the effect
runs only when isOpen changes and that cleanup runs on unmount to restore scroll
behavior.
| export const handleScrollLock = (isOpen: boolean) => { | ||
| if (isOpen) { | ||
| document.body.style.overflow = 'hidden'; | ||
| document.body.style.touchAction = 'none'; | ||
| } else { | ||
| document.body.style.removeProperty('overflow'); | ||
| document.body.style.removeProperty('touch-action'); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
동시 오버레이에서 잠금이 풀릴 수 있는 경쟁 상태 — ref-count와 복원 로직으로 교체 필요
Home/BottomSheet가 각각 토글하면 한 쪽이 닫힐 때 다른 쪽이 열려 있어도 스크롤 잠금이 해제될 수 있습니다. SSR/초기 렌더에서도 안전 가드가 필요합니다.
아래처럼 참조 카운트, 이전 스타일 복원, 환경 가드를 추가해 주세요.
-export const handleScrollLock = (isOpen: boolean) => {
- if (isOpen) {
- document.body.style.overflow = 'hidden';
- document.body.style.touchAction = 'none';
- } else {
- document.body.style.removeProperty('overflow');
- document.body.style.removeProperty('touch-action');
- }
-};
+let scrollLockCount = 0;
+let prevOverflow: string | null = null;
+let prevTouchAction: string | null = null;
+
+export const handleScrollLock = (lock: boolean) => {
+ if (typeof document === 'undefined' || !document.body) return;
+ const style = document.body.style;
+
+ if (lock) {
+ if (scrollLockCount === 0) {
+ prevOverflow = style.overflow || '';
+ prevTouchAction = style.touchAction || '';
+ style.overflow = 'hidden';
+ style.touchAction = 'none';
+ }
+ scrollLockCount += 1;
+ } else {
+ scrollLockCount = Math.max(0, scrollLockCount - 1);
+ if (scrollLockCount === 0) {
+ if (prevOverflow) style.overflow = prevOverflow; else style.removeProperty('overflow');
+ if (prevTouchAction) style.touchAction = prevTouchAction; else style.removeProperty('touch-action');
+ prevOverflow = null;
+ prevTouchAction = null;
+ }
+ }
+};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const handleScrollLock = (isOpen: boolean) => { | |
| if (isOpen) { | |
| document.body.style.overflow = 'hidden'; | |
| document.body.style.touchAction = 'none'; | |
| } else { | |
| document.body.style.removeProperty('overflow'); | |
| document.body.style.removeProperty('touch-action'); | |
| } | |
| }; | |
| let scrollLockCount = 0; | |
| let prevOverflow: string | null = null; | |
| let prevTouchAction: string | null = null; | |
| export const handleScrollLock = (lock: boolean) => { | |
| if (typeof document === 'undefined' || !document.body) return; | |
| const style = document.body.style; | |
| if (lock) { | |
| if (scrollLockCount === 0) { | |
| prevOverflow = style.overflow || ''; | |
| prevTouchAction = style.touchAction || ''; | |
| style.overflow = 'hidden'; | |
| style.touchAction = 'none'; | |
| } | |
| scrollLockCount += 1; | |
| } else { | |
| scrollLockCount = Math.max(0, scrollLockCount - 1); | |
| if (scrollLockCount === 0) { | |
| if (prevOverflow) style.overflow = prevOverflow; | |
| else style.removeProperty('overflow'); | |
| if (prevTouchAction) style.touchAction = prevTouchAction; | |
| else style.removeProperty('touch-action'); | |
| prevOverflow = null; | |
| prevTouchAction = null; | |
| } | |
| } | |
| }; |
🤖 Prompt for AI Agents
In src/shared/utils/scroll-lock.ts around lines 1 to 9, the current simple
toggle causes a race where multiple overlays can remove the scroll lock when one
closes; replace it with a reference-counted lock that increments on open and
decrements on close, only restoring styles when the count reaches zero; store
and restore previous body style values (overflow and touch-action) rather than
blindly removing properties; add environment guards so the logic no-ops during
SSR or when document/body is unavailable; ensure the exported API exposes
functions to acquire/release the lock (or keeps the same handleScrollLock
boolean API but internally manages the ref count and restoration).
#️⃣ Related Issue
Closes #367
💎 PR Point
overflow: hidden: 스크롤바를 완전히 숨기고 스크롤 방지
touchAction: none: 모바일에서 모든 터치 제스처(스크롤, 줌, 팬 등) 비활성화
removeProperty(): 스타일 속성을 완전히 제거하여 원래 상태로 복원
📸 Screenshot
개발자 모드에서는 확인이 불가해서 배포하고 폰으로 확인 해봐야 할 것 같아요ㅜㅜ
Summary by CodeRabbit