Merge branch 'feature/site-features-and-mockup'
This commit is contained in:
commit
8d6e6d7cd0
@ -1,6 +1,7 @@
|
||||
import {useEffect, useRef} from 'react';
|
||||
import type {ReactNode} from 'react';
|
||||
import {X} from 'lucide-react';
|
||||
import {useScrollLock} from './use-scroll-lock';
|
||||
|
||||
/**
|
||||
* 바텀시트(모바일) · 가운데 카드(데스크톱) 모달 한 장.
|
||||
@ -29,30 +30,15 @@ export function Modal({
|
||||
const onCloseRef = useRef(onClose);
|
||||
onCloseRef.current = onClose;
|
||||
|
||||
useScrollLock(open);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') onCloseRef.current();
|
||||
};
|
||||
const scrollY = window.scrollY;
|
||||
const body = document.body;
|
||||
const prevPosition = body.style.position;
|
||||
const prevTop = body.style.top;
|
||||
const prevWidth = body.style.width;
|
||||
const prevOverflow = body.style.overflow;
|
||||
body.style.position = 'fixed';
|
||||
body.style.top = `-${scrollY}px`;
|
||||
body.style.width = '100%';
|
||||
body.style.overflow = 'hidden';
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => {
|
||||
body.style.position = prevPosition;
|
||||
body.style.top = prevTop;
|
||||
body.style.width = prevWidth;
|
||||
body.style.overflow = prevOverflow;
|
||||
window.scrollTo(0, scrollY);
|
||||
window.removeEventListener('keydown', onKey);
|
||||
};
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [open]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
@ -3,3 +3,4 @@ export {Carousel, CarouselSlide} from './Carousel';
|
||||
export {Card, Chip, Field, ActionLink} from './primitives';
|
||||
export {ShowMore} from './ShowMore';
|
||||
export {Modal} from './Modal';
|
||||
export {useScrollLock} from './use-scroll-lock';
|
||||
|
||||
39
solution/site/src/lib/ui/use-scroll-lock.ts
Normal file
39
solution/site/src/lib/ui/use-scroll-lock.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 모달·오버레이가 열려 있는 동안 배경 스크롤을 잠근다 — **한 벌만 둔다.**
|
||||
*
|
||||
* ★ 왜 훅으로 뺐나 (2026-09-21, 사장님: "이거 목업 하나만 해결하면 끝이야? 훅으로 만들어놓던가")
|
||||
* 같은 잠금·복귀 코드가 Modal · GallerySection · EventSection 세 곳에 따로 박혀 있었다.
|
||||
* 모달 하나만 고치고 나니 나머지 둘은 그대로 남아 같은 스크롤 버그가 다시 났다 —
|
||||
* 복사한 코드는 복사한 곳마다 따로 고쳐야 한다.
|
||||
* ★ 복귀는 즉시 이동이어야 한다. `index.css` 의 전역 `html { scroll-behavior: smooth }` 때문에
|
||||
* `window.scrollTo` 가 애니메이션으로 움직이며 "위로 튀었다 내려오는" 것처럼 보인다 —
|
||||
* 이 한 번만 `scroll-behavior: auto` 로 강제한다.
|
||||
*/
|
||||
import {useEffect} from 'react';
|
||||
|
||||
export function useScrollLock(active: boolean) {
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
const scrollY = window.scrollY;
|
||||
const body = document.body;
|
||||
const prevPosition = body.style.position;
|
||||
const prevTop = body.style.top;
|
||||
const prevWidth = body.style.width;
|
||||
const prevOverflow = body.style.overflow;
|
||||
body.style.position = 'fixed';
|
||||
body.style.top = `-${scrollY}px`;
|
||||
body.style.width = '100%';
|
||||
body.style.overflow = 'hidden';
|
||||
return () => {
|
||||
body.style.position = prevPosition;
|
||||
body.style.top = prevTop;
|
||||
body.style.width = prevWidth;
|
||||
body.style.overflow = prevOverflow;
|
||||
const root = document.documentElement;
|
||||
const prevScrollBehavior = root.style.scrollBehavior;
|
||||
root.style.scrollBehavior = 'auto';
|
||||
window.scrollTo(0, scrollY);
|
||||
root.style.scrollBehavior = prevScrollBehavior;
|
||||
};
|
||||
}, [active]);
|
||||
}
|
||||
@ -2,7 +2,7 @@ import {useCallback, useEffect, useState} from 'react';
|
||||
import {ChevronLeft, ChevronRight, X} from 'lucide-react';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
import {galleryImages} from '@site/lib/derive';
|
||||
import {Carousel, CarouselSlide, Section} from '@site/lib/ui';
|
||||
import {Carousel, CarouselSlide, Section, useScrollLock} from '@site/lib/ui';
|
||||
|
||||
/**
|
||||
* 사진 갤러리.
|
||||
@ -29,6 +29,8 @@ export function GallerySection() {
|
||||
|
||||
// 크게 보기 중에는 Esc 로 닫고 좌우 키로 넘긴다. 뒤 배경은 스크롤을 잠근다 —
|
||||
// 잠그지 않으면 모달 위에서 휠을 굴렸을 때 뒤 페이지가 밀린다.
|
||||
useScrollLock(openIndex !== null);
|
||||
|
||||
useEffect(() => {
|
||||
if (openIndex === null) return;
|
||||
const onKey = (event: KeyboardEvent) => {
|
||||
@ -36,25 +38,8 @@ export function GallerySection() {
|
||||
if (event.key === 'ArrowLeft') step(-1);
|
||||
if (event.key === 'ArrowRight') step(1);
|
||||
};
|
||||
const scrollY = window.scrollY;
|
||||
const body = document.body;
|
||||
const prevPosition = body.style.position;
|
||||
const prevTop = body.style.top;
|
||||
const prevWidth = body.style.width;
|
||||
const prevOverflow = body.style.overflow;
|
||||
body.style.position = 'fixed';
|
||||
body.style.top = `-${scrollY}px`;
|
||||
body.style.width = '100%';
|
||||
body.style.overflow = 'hidden';
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => {
|
||||
body.style.position = prevPosition;
|
||||
body.style.top = prevTop;
|
||||
body.style.width = prevWidth;
|
||||
body.style.overflow = prevOverflow;
|
||||
window.scrollTo(0, scrollY);
|
||||
window.removeEventListener('keydown', onKey);
|
||||
};
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [openIndex, close, step]);
|
||||
|
||||
if (images.length === 0) return null;
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
* 카드가 무엇을 흉내 내는지 한눈에 보여야 목록이 아니라 물건으로 읽힌다.
|
||||
*/
|
||||
import {useCallback, useEffect, useState} from 'react';
|
||||
import {ShowMore} from '@site/lib/ui';
|
||||
import {ShowMore, useScrollLock} from '@site/lib/ui';
|
||||
import {X} from 'lucide-react';
|
||||
import type {EventItem} from '@o2o/shared';
|
||||
import {useSite} from '@site/lib/site-context';
|
||||
@ -90,30 +90,15 @@ export function EventSection() {
|
||||
const close = useCallback(() => setOpen(null), []);
|
||||
|
||||
// 창이 열려 있는 동안 Esc 로 닫고 뒤 배경 스크롤을 잠근다 — 안 잠그면 창 위에서 뒤가 밀린다.
|
||||
useScrollLock(open !== null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open === null) return;
|
||||
const onKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') close();
|
||||
};
|
||||
const scrollY = window.scrollY;
|
||||
const body = document.body;
|
||||
const prevPosition = body.style.position;
|
||||
const prevTop = body.style.top;
|
||||
const prevWidth = body.style.width;
|
||||
const prevOverflow = body.style.overflow;
|
||||
body.style.position = 'fixed';
|
||||
body.style.top = `-${scrollY}px`;
|
||||
body.style.width = '100%';
|
||||
body.style.overflow = 'hidden';
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => {
|
||||
body.style.position = prevPosition;
|
||||
body.style.top = prevTop;
|
||||
body.style.width = prevWidth;
|
||||
body.style.overflow = prevOverflow;
|
||||
window.scrollTo(0, scrollY);
|
||||
window.removeEventListener('keydown', onKey);
|
||||
};
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [open, close]);
|
||||
|
||||
if (parsed.items.length === 0) return null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user