Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: popover가 뷰포트 밖으로 나가면 반대 방향으로 이동한다 #823

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
88 changes: 86 additions & 2 deletions packages/vibrant-components/src/lib/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cloneElement, useCallback, useEffect, useRef, useState } from 'react';
import { Box, isNative, useCurrentTheme, usePopover, useResponsiveValue } from '@vibrant-ui/core';
import { Box, getWindowDimensions, isNative, useCurrentTheme, usePopover, useResponsiveValue } from '@vibrant-ui/core';
import { Icon } from '@vibrant-ui/icons';
import { Transition } from '@vibrant-ui/motion';
import { getElementRect, isDefined, useCallbackRef } from '@vibrant-ui/utils';
Expand Down Expand Up @@ -37,6 +37,7 @@ export const Popover = ({
theme: { zIndex: themeZIndex },
} = useCurrentTheme();
const { getResponsiveValue } = useResponsiveValue();
const [positionValue, setPositionValue] = useState<Position>(position);
const [popoverPosition, setPopoverPosition] = useState({ x: 0, y: 0 });
const [arrowPosition, setArrowPosition] = useState({
left: 0,
Expand All @@ -50,6 +51,7 @@ export const Popover = ({
const handleOpen = useCallbackRef(onOpen);
const handleClose = useCallbackRef(onClose);

const { width: windowWidth, height: windowHeight } = getWindowDimensions();
const arrowHeight = Math.sqrt(ARROW_TRIANGLE_SIZE * ARROW_TRIANGLE_SIZE * 2) / 2;

const calcuratePositionValue = useCallback(
Expand Down Expand Up @@ -244,7 +246,89 @@ export const Popover = ({
[arrowHeight, arrowOffset, backgroundColor, computedOffset, title]
);

useEffect(() => (isOpen ? handleOpen?.() : handleClose?.()), [isOpen, handleOpen, handleClose]);
const handlePopoverPositionChange = useCallback(async () => {
if (!popoverRef.current || !childRef.current) return;

const {
x: popoverX,
y: popoverY,
width: popoverWidth,
height: popoverHeight,
} = await getElementRect(popoverRef.current);
const { x: childX, y: childY, width: childWidth, height: childHeight } = await getElementRect(childRef.current);

if (position !== positionValue) {
if (position.includes('top') && childY - computedOffset - popoverHeight > 0) {
setPositionValue(position);
}

if (position.includes('bottom') && childY + childHeight + computedOffset + popoverHeight < windowHeight) {
setPositionValue(position);
}

if (position.includes('left') && childX - computedOffset - popoverWidth > 0) {
setPositionValue(position);
}

if (position.includes('right') && childX + childWidth + computedOffset + popoverWidth < windowWidth) {
setPositionValue(position);
}

return;
}

if (
positionValue.includes('top') &&
popoverY < 0 &&
childY + childHeight + computedOffset + popoverHeight < windowHeight
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요런 식들 공통의 함수로 묶어서 사용하긴 좀 어려울라나요 .. 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최대한 공통 함수로 묶는 것 도전해볼게요 ㅋㅋㅋ

) {
setPositionValue(positionValue.replace('top', 'bottom') as Position);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그런데 position 위치를 다시 셋팅하면 뚝딱거리지는 않나요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Popover에 애니메이션을 걸어놔서 첨부한 영상처럼 되긴 합니다~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 근데 영상은 resize 이벤트 달아놨을 때 기준이긴 해요

}

if (
positionValue.includes('bottom') &&
popoverY + popoverHeight >= windowHeight &&
childY - computedOffset - popoverHeight > 0
) {
setPositionValue(positionValue.replace('bottom', 'top') as Position);
}

if (
positionValue.includes('left') &&
popoverX < 0 &&
childX + childWidth + computedOffset + popoverWidth < windowWidth
) {
setPositionValue(positionValue.replace('left', 'right') as Position);
}

if (
positionValue.includes('right') &&
popoverX + popoverWidth >= windowWidth &&
childX - computedOffset - popoverWidth > 0
) {
setPositionValue(positionValue.replace('right', 'left') as Position);
}
}, [computedOffset, position, positionValue, windowHeight, windowWidth]);

useEffect(() => {
handlePopoverPositionChange();
}, [handlePopoverPositionChange, windowWidth, windowHeight]);

useEffect(() => {
if (isOpen) {
handleOpen?.();

handlePopoverPositionChange();
} else handleClose?.();
}, [isOpen, handleOpen, handleClose, handlePopoverPositionChange]);

useEffect(() => {
setPositionValue(position);
}, [position]);

useEffect(() => {
calcuratePositionValue(positionValue);
}, [calcuratePositionValue, positionValue]);

useEffect(() => {
calcuratePositionValue(position);
Expand Down