-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Changes from 8 commits
6752583
918b166
91b3d5a
7f02df4
ac0849c
15c4353
46cd762
ba83cfd
63fd701
95b161b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import { cloneElement, useCallback, useEffect, useRef, useState } from 'react'; | ||
import { Box, useCurrentTheme, usePopover, useResponsiveValue } from '@vibrant-ui/core'; | ||
import { Box, getWindowDimensions, useCurrentTheme, usePopover, useResponsiveValue } from '@vibrant-ui/core'; | ||
import { Icon } from '@vibrant-ui/icons'; | ||
import { Transition } from '@vibrant-ui/motion'; | ||
import { isDefined, useCallbackRef } from '@vibrant-ui/utils'; | ||
import { getElementRect, isDefined, useCallbackRef } from '@vibrant-ui/utils'; | ||
import type { Position } from '@vibrant-ui/utils'; | ||
import { Body } from '../Body'; | ||
import { HStack } from '../HStack'; | ||
|
@@ -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, | ||
|
@@ -50,20 +51,19 @@ 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 popoverWidth = popoverRef?.current?.offsetWidth ?? 0; | ||
const popoverHeight = popoverRef?.current?.offsetHeight ?? 0; | ||
const halfPopoverWidth = popoverWidth / 2; | ||
const halfPopoverHeight = popoverHeight / 2; | ||
const calcuratePositionValue = useCallback( | ||
async (position: Position) => { | ||
const { width: popoverWidth, height: popoverHeight } = await getElementRect(popoverRef.current); | ||
const { width: childWidth, height: childHeight } = await getElementRect(childRef.current); | ||
|
||
const childWidth = childRef?.current?.offsetWidth ?? 0; | ||
const childHeight = childRef?.current?.offsetHeight ?? 0; | ||
const halfChildWidth = childWidth / 2; | ||
const halfChildHeight = childHeight / 2; | ||
const halfPopoverWidth = popoverWidth / 2; | ||
const halfPopoverHeight = popoverHeight / 2; | ||
const halfChildWidth = childWidth / 2; | ||
const halfChildHeight = childHeight / 2; | ||
|
||
const calcuratePositionValue = useCallback( | ||
(position: Position) => { | ||
if (position.includes('top')) { | ||
setArrowStyle({ | ||
borderRightColor: backgroundColor, | ||
|
@@ -236,23 +236,92 @@ export const Popover = ({ | |
} | ||
} | ||
}, | ||
[ | ||
arrowHeight, | ||
arrowOffset, | ||
backgroundColor, | ||
childHeight, | ||
childWidth, | ||
computedOffset, | ||
halfChildHeight, | ||
halfChildWidth, | ||
halfPopoverHeight, | ||
halfPopoverWidth, | ||
popoverHeight, | ||
popoverWidth, | ||
] | ||
[arrowHeight, arrowOffset, backgroundColor, computedOffset] | ||
); | ||
|
||
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 | ||
) { | ||
setPositionValue(positionValue.replace('top', 'bottom') as Position); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그런데 position 위치를 다시 셋팅하면 뚝딱거리지는 않나요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Popover에 애니메이션을 걸어놔서 첨부한 영상처럼 되긴 합니다~ There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요런 식들 공통의 함수로 묶어서 사용하긴 좀 어려울라나요 .. 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
최대한 공통 함수로 묶는 것 도전해볼게요 ㅋㅋㅋ