floating-ui란?
- Popover/Tooltip/Dropdown 위치 제어 라이브러리
- 자동 위치 보정, 충돌 방지, offset, flip, shift 제공
floating-ui 사용 예시
placement: "right-start" : 기준 요소(reference)의 오른쪽 상단을 기준으로 패널을 붙임
whileElementsMounted: autoUpdate : 스크롤/리사이즈/레이아웃 변경 시 자동으로 재배치
middleware
- offset(8) : 기준과 패널 사이 여백(px)
- flip() : 배치 방향이 화면 밖으로 나갈 때 반대 방향으로 뒤집기
- shift({ padding: 12 }) : 가장자리(뷰포트 경계)에서 안쪽으로 밀어넣기 / padding만큼 여백 유지
- size({...}) : 가용 공간(가령 높이)에 맞춰 패널 크기(예: maxHeight)를 동적으로 조정
반환값
x, y : 계산된 좌표 / top/left에 그대로 바인딩
strategy : "absolute"(기본) 또는 "fixed"
refs.setReference : 기준 요소(보통 버튼, 아이콘 등)에 연결
refs.setFloating: 실제로 떠야 하는 요소(Popover 패널, Tooltip 등)에 연결
코드 예시
부모 컴포넌트
- 클릭된 셀 DOM(dayEl)을 기준 요소(referenceEl) 로 저장
- 이 값을 상태로 관리해 자식 모달에 props로 전달
const handleDateClick = (arg: DateClickArg) => {
setClickedDate(arg.date);
setReferenceEl(arg.dayEl as HTMLElement);
setIsOpen(true);
};
자식 컴포넌트
- useFloating 훅을 사용해 x, y, strategy 좌표와 refs(reference/floating 연결 ref)를 얻음
- useEffect로 부모에서 받은 referenceEl을 refs.setReference에 연결
- 실제 떠 있는 패널은 refs.setFloating을 ref로 달아서 자동 위치 계산 적용
import {
useFloating,
offset,
flip,
shift,
size,
autoUpdate,
} from "@floating-ui/react";
import { useEffect } from "react";
export function ReservationInfoModal({ isOpen, onClose, referenceEl }: Props) {
const { x, y, refs, strategy } = useFloating({
placement: "right-start",
whileElementsMounted: autoUpdate,
middleware: [
offset(8),
flip(),
shift({ padding: 12 }),
size({
apply({ availableHeight, elements }) {
// 뷰포트 가용 높이에 맞춰 최대 높이 제한 + 스크롤 가능
Object.assign(elements.floating.style, {
maxHeight: `${Math.max(availableHeight, 480)}px`,
overflowY: "auto",
});
},
}),
],
});
useEffect(() => {
if (referenceEl) refs.setReference(referenceEl);
}, [referenceEl, refs]);
if (!isOpen || !referenceEl) return null;
return (
<>
{/* 배경 클릭으로 닫기 (모달처럼 동작시키고 싶을 때) */}
<div className="fixed inset-0 z-40" onClick={onClose} aria-hidden="true" />
{/* floating 패널 */}
<div
ref={refs.setFloating}
style={{ position: strategy, top: y ?? 0, left: x ?? 0 }}
className="z-50 min-w-[370px] max-w-[430px] w-full rounded-[10px] border bg-white"
>
{/* Popover/내용… */}
</div>
</>
);
}