From 609d58a9b1ac7f529188a56bcdfdd435f220e7f0 Mon Sep 17 00:00:00 2001 From: WayneKim92 Date: Wed, 28 Aug 2024 23:08:01 +0900 Subject: [PATCH 1/3] =?UTF-8?q?test:=20=EC=8B=A4=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/src/App.tsx | 135 +++------- src/index.tsx | 599 +++++++++++++++++++++++--------------------- 2 files changed, 337 insertions(+), 397 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 73244aa..a9f303e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,12 +1,16 @@ -import { FlatList, SafeAreaView, StyleSheet, Text, View } from 'react-native'; +import { + FlatList, + SafeAreaView, + StyleSheet, + Text, + TextInput, + View, +} from 'react-native'; import Tooltip from 'react-native-good-tooltip'; +import { useState } from 'react'; export default function App() { - const data = [ - 'In FlatList', - 'zIndex must be specified using', - 'CellRendererComponent.', - ]; + const [inputText, setInputText] = useState(''); return ( @@ -19,17 +23,15 @@ export default function App() { }} > {}} > - + @@ -77,102 +73,27 @@ export default function App() { - {/* Body*/} + + + + { + data={[1, 2, 3]} + renderItem={() => { return ( - ); }} - renderItem={({ item, index }) => ( - { - if (index % 3 === 0) return 'right'; - if (index % 2 === 0) return 'center'; - - return 'left'; - })()} - text={'List Item Tooltip ' + index} - > - - {`${item}`} - - - )} - ListFooterComponent={() => ( - - - - - There may be situations where you need to add the overflow: - 'visible' style. - - - - - - - - - - - - - - - - - )} /> ); diff --git a/src/index.tsx b/src/index.tsx index b74e165..07319c5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Animated, Dimensions, @@ -25,6 +25,8 @@ const TOOLTIP_STYLE = { }; interface ToolTipProps { + visible?: boolean; + rerenderKey?: any; placement: 'top' | 'bottom' | 'left' | 'right'; anchor?: 'center' | 'left' | 'right' | 'top' | 'bottom'; offset?: { @@ -53,153 +55,208 @@ interface ToolTipProps { }; text: string | React.ReactElement; children?: React.ReactElement; - isVisible: boolean; onPress?: () => void; onVisibleChange?: (isVisible: boolean) => void; - disableAutoHide?: boolean; delayShowTime?: number; autoHideTime?: number; - requiredConfirmation?: boolean; } -const Tooltip = ({ - isVisible, - anchor = 'center', - styles = { - tooltipStyle: TOOLTIP_STYLE, - arrowSize: ARROW_SIZE, - closeSize: CLOSE_ICON_SIZE, - }, - text, - children, - placement, - onPress, - offset, - arrowElement, - onVisibleChange, - disableAutoHide = false, - delayShowTime = 0, - autoHideTime = 5000, - requiredConfirmation = false, -}: ToolTipProps) => { - const [currentIsVisible, setCurrentIsVisible] = useState(false); - const [tooltipPosition, setTooltipPosition] = useState({ - top: 0, - left: Platform.OS === 'android' && placement === 'right' ? -4 : 0, - }); - const [tooltipSize, setTooltipSize] = useState({ width: 0, height: 0 }); - const animatedValue = new Animated.Value(0); +const Tooltip = React.memo( + ({ + visible, + anchor = 'center', + styles = { + tooltipStyle: TOOLTIP_STYLE, + arrowSize: ARROW_SIZE, + closeSize: CLOSE_ICON_SIZE, + }, + text, + children, + placement, + onPress, + offset, + arrowElement, + onVisibleChange, + delayShowTime = 0, + autoHideTime = 5000, + }: ToolTipProps) => { + // eslint-disable-next-line react-hooks/exhaustive-deps + const animatedValue = new Animated.Value(0); + + const [tooltipPosition, setTooltipPosition] = useState({ + top: 0, + left: Platform.OS === 'android' && placement === 'right' ? -4 : 0, + }); + const [tooltipSize, setTooltipSize] = useState({ width: 0, height: 0 }); + const isVerticalPlacement = placement === 'top' || placement === 'bottom'; + const tooltipColor = (() => { + if (styles?.color) return styles?.color; - const isVerticalPlacement = placement === 'top' || placement === 'bottom'; - const tooltipColor = (() => { - if (styles?.color) return styles?.color; + return '#3Eb489'; + })(); + const arrowStyle = { + ...createArrowShape( + styles?.arrowSize?.width || ARROW_SIZE.width, + styles?.arrowSize?.height || ARROW_SIZE.height + ), + borderBottomColor: tooltipColor, + }; + const transformsStyle = (() => { + const { x, y } = getAnchorPoint(placement, anchor); - return '#3Eb489'; - })(); + return withAnchorPoint( + { transform: [{ scale: animatedValue }] }, + { x, y }, + { width: tooltipSize.width, height: tooltipSize.height } + ); + })(); - const arrowStyle = { - ...createArrowShape( - styles?.arrowSize?.width || ARROW_SIZE.width, - styles?.arrowSize?.height || ARROW_SIZE.height - ), - borderBottomColor: tooltipColor, - }; + const runAnimation = useCallback( + (isShowAnimation: boolean) => { + if (isShowAnimation) { + Animated.spring(animatedValue, { + toValue: 1, + speed: 6, + useNativeDriver: true, + }).start(() => { + onVisibleChange && onVisibleChange(true); + }); + } else { + Animated.timing(animatedValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start(() => { + onVisibleChange && onVisibleChange(false); + }); + } + }, + [animatedValue, onVisibleChange] + ); + + // show animation + useEffect(() => { + if (visible !== undefined && !visible) { + return; + } - useEffect(() => { - if ( - autoHideTime > 0 && - disableAutoHide === false && - requiredConfirmation === false - ) { setTimeout(() => { - // 기본적으로 5초 뒤 사라짐 처리 - setCurrentIsVisible(false); - }, autoHideTime); - } - }, [autoHideTime, disableAutoHide, requiredConfirmation]); + runAnimation(true); + }, delayShowTime); + }, [delayShowTime, runAnimation, visible]); - useEffect(() => { - if (onVisibleChange) { - onVisibleChange(isVisible); - } - }, [isVisible, onVisibleChange]); + // hide animation + useEffect(() => { + if (onPress) return; - useEffect(() => { - setTimeout(() => { - setCurrentIsVisible(isVisible); - }, delayShowTime); - }, [delayShowTime, isVisible]); + setTimeout(() => { + runAnimation(false); + }, delayShowTime + autoHideTime); + }, [autoHideTime, delayShowTime, onPress, runAnimation]); - const handleVerticalTooltipLayout = (event: LayoutChangeEvent) => { - const { width, height } = event.nativeEvent.layout; - if (placement === 'top') { - setTooltipPosition({ top: -height, left: 0 }); - } - setTooltipSize({ - width, - height, - }); - }; + const handleVerticalTooltipLayout = (event: LayoutChangeEvent) => { + const { width, height } = event.nativeEvent.layout; + if (placement === 'top') { + setTooltipPosition({ top: -height, left: 0 }); + } + setTooltipSize({ + width, + height, + }); + }; - const handleHorizontalTooltipLayout = (event: LayoutChangeEvent) => { - const { width, height } = event.nativeEvent.layout; + const handleHorizontalTooltipLayout = (event: LayoutChangeEvent) => { + const { width, height } = event.nativeEvent.layout; - const newLeft = width - 4; - if (placement === 'left') { - setTooltipPosition({ top: 0, left: -newLeft }); - } - setTooltipSize({ - width, - height, - }); - }; + const newLeft = width - 4; + if (placement === 'left') { + setTooltipPosition({ top: 0, left: -newLeft }); + } + setTooltipSize({ + width, + height, + }); + }; - const renderVerticalArrow = () => ( - { - if (anchor === 'left') return 'flex-start'; - if (anchor === 'right') return 'flex-end'; - return 'center'; - })(), - }, - styles?.containerStyle, - ]} - > + const renderVerticalArrow = () => ( + { + if (anchor === 'left') return 'flex-start'; + if (anchor === 'right') return 'flex-end'; + return 'center'; + })(), + }, + styles?.containerStyle, + ]} + > + { + let x = SIDE_ARROW_INSET + (offset?.arrow?.x || 0); + + if (!isVerticalPlacement) { + x = offset?.arrow?.x || 0; + } + if (anchor === 'center') { + x = offset?.arrow?.x || 0; + } + if (anchor === 'right') { + x = -x; + } + + return x; + })(), + top: (() => { + let y = SIDE_ARROW_INSET + (offset?.arrow?.y || 0); + + if (isVerticalPlacement) { + y = offset?.arrow?.y || 0; + } + if (anchor === 'center') { + y = offset?.arrow?.y || 0; + } + if (anchor === 'bottom') { + y = -y; + } + + // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 + return y + (Platform.OS === 'android' ? 0.1 : 0); + })(), + transform: (() => { + if (placement === 'bottom') return [{ rotate: '0deg' }]; + if (placement === 'top') return [{ rotate: '180deg' }]; + if (placement === 'left') return [{ rotate: '90deg' }]; + if (placement === 'right') return [{ rotate: '-90deg' }]; + return undefined; + })(), + }} + > + {arrowElement === undefined && arrowElement} + + + ); + + const renderHorizontalArrow = () => ( { - let x = SIDE_ARROW_INSET + (offset?.arrow?.x || 0); - - if (!isVerticalPlacement) { - x = offset?.arrow?.x || 0; - } - if (anchor === 'center') { - x = offset?.arrow?.x || 0; + let left = 0; + if (placement === 'left') { + left = -2; } - if (anchor === 'right') { - x = -x; + if (placement === 'right') { + left = 2; } - return x; + // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 + return left + (Platform.OS === 'android' ? 0.1 : 0); })(), top: (() => { - let y = SIDE_ARROW_INSET + (offset?.arrow?.y || 0); - - if (isVerticalPlacement) { - y = offset?.arrow?.y || 0; - } - if (anchor === 'center') { - y = offset?.arrow?.y || 0; - } - if (anchor === 'bottom') { - y = -y; - } - - // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 - return y + (Platform.OS === 'android' ? 0.1 : 0); + return 0; })(), transform: (() => { if (placement === 'bottom') return [{ rotate: '0deg' }]; @@ -212,186 +269,148 @@ const Tooltip = ({ > {arrowElement === undefined && arrowElement} - - ); + ); - const renderHorizontalArrow = () => ( - { - let left = 0; - if (placement === 'left') { - left = -2; - } - if (placement === 'right') { - left = 2; + const renderTooltipContent = () => ( + // View Overflow 영역에 있는 Tooltip을 선택하기 위해 TouchableOpacity 사용 + { + if (!onPress) { + return; } - // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 - return left + (Platform.OS === 'android' ? 0.1 : 0); - })(), - top: (() => { - return 0; - })(), - transform: (() => { - if (placement === 'bottom') return [{ rotate: '0deg' }]; - if (placement === 'top') return [{ rotate: '180deg' }]; - if (placement === 'left') return [{ rotate: '90deg' }]; - if (placement === 'right') return [{ rotate: '-90deg' }]; - return undefined; - })(), - }} - > - {arrowElement === undefined && arrowElement} - - ); - - const transformsStyle = (() => { - const { x, y } = getAnchorPoint(placement, anchor); - - if (currentIsVisible) { - Animated.spring(animatedValue, { - toValue: 1, - speed: 6, - useNativeDriver: true, - }).start(); - } else { - animatedValue.setValue(1); - Animated.timing(animatedValue, { - toValue: 0, - duration: 300, - useNativeDriver: true, - }).start(); - } + runAnimation(false); + onPress && onPress(); + }} + style={{ + backgroundColor: tooltipColor, + borderRadius: 10, + ...(styles?.tooltipStyle || TOOLTIP_STYLE), + }} + > + + {/* content */} + {typeof text !== 'string' ? ( + text + ) : ( + + )} - return withAnchorPoint( - { transform: [{ scale: animatedValue }] }, - { x, y }, - { width: tooltipSize.width, height: tooltipSize.height } + {onPress && ( + <> + + + + )} + + ); - })(); - const renderTooltipContent = () => ( - // View Overflow 영역에 있는 Tooltip을 선택하기 위해 TouchableOpacity 사용 - { - setCurrentIsVisible((prevState) => !prevState); - onPress && onPress(); - }} - style={{ - backgroundColor: tooltipColor, - borderRadius: 10, - ...(styles?.tooltipStyle || TOOLTIP_STYLE), - }} - > + const renderVerticalTooltip = () => ( { + if (anchor === 'left') return 'flex-start'; + if (anchor === 'right') return 'flex-end'; + return 'center'; + })(), + top: tooltipPosition.top + (offset?.position?.y || 0), + left: tooltipPosition.left + (offset?.position?.x || 0), + }} > - {/* content */} - {typeof text !== 'string' ? ( - text - ) : ( - - )} + + {placement === 'top' && renderTooltipContent()} + {renderVerticalArrow()} + {placement === 'bottom' && renderTooltipContent()} + + + ); + const renderHorizontalTooltip = () => ( + // 툴팁 & 화살표 정렬 기준 ( top, center, bottom ) + { + if (anchor === 'top') return 'flex-start'; + if (anchor === 'bottom') return 'flex-end'; + return 'center'; + })(), + }} + > + + {placement === 'left' && renderTooltipContent()} + {renderHorizontalArrow()} + {placement === 'right' && renderTooltipContent()} + + + ); - {requiredConfirmation && ( - <> - - - + return ( + + {placement === 'top' && renderVerticalTooltip()} + {isVerticalPlacement ? ( + children + ) : ( + + {placement === 'left' && renderHorizontalTooltip()} + {children} + {placement === 'right' && renderHorizontalTooltip()} + )} + {placement === 'bottom' && renderVerticalTooltip()} - - ); + ); + }, + (prevProps, nextProps) => { + // 참이면 리덴더링 하지 않아도 됨 + let notNeedToRerender = true; - const renderVerticalTooltip = () => ( - { - if (anchor === 'left') return 'flex-start'; - if (anchor === 'right') return 'flex-end'; - return 'center'; - })(), - top: tooltipPosition.top + (offset?.position?.y || 0), - left: tooltipPosition.left + (offset?.position?.x || 0), - }} - > - - {placement === 'top' && renderTooltipContent()} - {renderVerticalArrow()} - {placement === 'bottom' && renderTooltipContent()} - - - ); - const renderHorizontalTooltip = () => ( - // 툴팁 & 화살표 정렬 기준 ( top, center, bottom ) - { - if (anchor === 'top') return 'flex-start'; - if (anchor === 'bottom') return 'flex-end'; - return 'center'; - })(), - }} - > - - {placement === 'left' && renderTooltipContent()} - {renderHorizontalArrow()} - {placement === 'right' && renderTooltipContent()} - - - ); + if (prevProps.visible !== nextProps.visible) { + notNeedToRerender = false; + } + if (prevProps.rerenderKey !== nextProps.rerenderKey) { + notNeedToRerender = false; + } - return ( - - {placement === 'top' && renderVerticalTooltip()} - {isVerticalPlacement ? ( - children - ) : ( - - {placement === 'left' && renderHorizontalTooltip()} - {children} - {placement === 'right' && renderHorizontalTooltip()} - - )} - {placement === 'bottom' && renderVerticalTooltip()} - - ); -}; + console.log('리렌더링 필요?', notNeedToRerender); + + return notNeedToRerender; + } +); export default Tooltip; From 0989b1c1e4aa43d3db45a4bf7a6c6ce4ce6f9579 Mon Sep 17 00:00:00 2001 From: WayneKim92 Date: Wed, 28 Aug 2024 23:40:24 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=EB=B2=84=EA=B7=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/src/App.tsx | 40 +++++++++++++++++----------------------- src/index.tsx | 2 -- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index a9f303e..8ca3565 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,16 +1,9 @@ -import { - FlatList, - SafeAreaView, - StyleSheet, - Text, - TextInput, - View, -} from 'react-native'; +import { SafeAreaView, StyleSheet, Text, View, TextInput } from 'react-native'; import Tooltip from 'react-native-good-tooltip'; import { useState } from 'react'; export default function App() { - const [inputText, setInputText] = useState(''); + const [inputText, _setInputText] = useState('초기 텍스트'); return ( @@ -76,25 +69,26 @@ export default function App() { - { - return ( - - ); - }} - /> + {/* {*/} + {/* return (*/} + {/* */} + {/* );*/} + {/* }}*/} + {/*/>*/} ); } diff --git a/src/index.tsx b/src/index.tsx index 07319c5..f51c803 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -407,8 +407,6 @@ const Tooltip = React.memo( notNeedToRerender = false; } - console.log('리렌더링 필요?', notNeedToRerender); - return notNeedToRerender; } ); From 842706f78339e031e52426554e285ddc2caffdc3 Mon Sep 17 00:00:00 2001 From: WayneKim92 Date: Thu, 29 Aug 2024 00:27:47 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20animatedValue=20=EA=B0=92=EC=9D=B4?= =?UTF-8?q?=200=EC=9C=BC=EB=A1=9C=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/src/App.tsx | 7 +- src/index.tsx | 600 ++++++++++++++++++++++---------------------- 2 files changed, 302 insertions(+), 305 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 8ca3565..e14c119 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -3,7 +3,7 @@ import Tooltip from 'react-native-good-tooltip'; import { useState } from 'react'; export default function App() { - const [inputText, _setInputText] = useState('초기 텍스트'); + const [inputText, setInputText] = useState('초기 텍스트'); return ( @@ -66,12 +66,11 @@ export default function App() { - + diff --git a/src/index.tsx b/src/index.tsx index f51c803..da128c3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,10 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { Animated, Dimensions, @@ -61,202 +67,175 @@ interface ToolTipProps { autoHideTime?: number; } -const Tooltip = React.memo( - ({ - visible, - anchor = 'center', - styles = { - tooltipStyle: TOOLTIP_STYLE, - arrowSize: ARROW_SIZE, - closeSize: CLOSE_ICON_SIZE, - }, - text, - children, - placement, - onPress, - offset, - arrowElement, - onVisibleChange, - delayShowTime = 0, - autoHideTime = 5000, - }: ToolTipProps) => { - // eslint-disable-next-line react-hooks/exhaustive-deps - const animatedValue = new Animated.Value(0); - - const [tooltipPosition, setTooltipPosition] = useState({ - top: 0, - left: Platform.OS === 'android' && placement === 'right' ? -4 : 0, - }); - const [tooltipSize, setTooltipSize] = useState({ width: 0, height: 0 }); - const isVerticalPlacement = placement === 'top' || placement === 'bottom'; - const tooltipColor = (() => { - if (styles?.color) return styles?.color; +const Tooltip = ({ + visible, + anchor = 'center', + styles = { + tooltipStyle: TOOLTIP_STYLE, + arrowSize: ARROW_SIZE, + closeSize: CLOSE_ICON_SIZE, + }, + text, + children, + placement, + onPress, + offset, + arrowElement, + onVisibleChange, + delayShowTime = 0, + autoHideTime = 5000, +}: ToolTipProps) => { + const showAnimationRef = useRef(false); + const hideAnimationRef = useRef(false); + const animatedValue = useMemo(() => new Animated.Value(0), []); - return '#3Eb489'; - })(); - const arrowStyle = { - ...createArrowShape( - styles?.arrowSize?.width || ARROW_SIZE.width, - styles?.arrowSize?.height || ARROW_SIZE.height - ), - borderBottomColor: tooltipColor, - }; - const transformsStyle = (() => { - const { x, y } = getAnchorPoint(placement, anchor); + const [tooltipPosition, setTooltipPosition] = useState({ + top: 0, + left: Platform.OS === 'android' && placement === 'right' ? -4 : 0, + }); + const [tooltipSize, setTooltipSize] = useState({ width: 0, height: 0 }); + const isVerticalPlacement = placement === 'top' || placement === 'bottom'; + const tooltipColor = (() => { + if (styles?.color) return styles?.color; - return withAnchorPoint( - { transform: [{ scale: animatedValue }] }, - { x, y }, - { width: tooltipSize.width, height: tooltipSize.height } - ); - })(); + return '#3Eb489'; + })(); + const arrowStyle = { + ...createArrowShape( + styles?.arrowSize?.width || ARROW_SIZE.width, + styles?.arrowSize?.height || ARROW_SIZE.height + ), + borderBottomColor: tooltipColor, + }; + const transformsStyle = (() => { + const { x, y } = getAnchorPoint(placement, anchor); - const runAnimation = useCallback( - (isShowAnimation: boolean) => { - if (isShowAnimation) { - Animated.spring(animatedValue, { - toValue: 1, - speed: 6, - useNativeDriver: true, - }).start(() => { - onVisibleChange && onVisibleChange(true); - }); - } else { - Animated.timing(animatedValue, { - toValue: 0, - duration: 300, - useNativeDriver: true, - }).start(() => { - onVisibleChange && onVisibleChange(false); - }); - } - }, - [animatedValue, onVisibleChange] + return withAnchorPoint( + { transform: [{ scale: animatedValue }] }, + { x, y }, + { width: tooltipSize.width, height: tooltipSize.height } ); + })(); - // show animation - useEffect(() => { - if (visible !== undefined && !visible) { - return; - } - - setTimeout(() => { - runAnimation(true); - }, delayShowTime); - }, [delayShowTime, runAnimation, visible]); + const runAnimation = useCallback( + (isShowAnimation: boolean) => { + if (isShowAnimation) { + if (showAnimationRef.current) return; - // hide animation - useEffect(() => { - if (onPress) return; + Animated.spring(animatedValue, { + toValue: 1, + speed: 6, + useNativeDriver: true, + }).start(() => { + onVisibleChange && onVisibleChange(true); + }); + showAnimationRef.current = true; + } else { + if (hideAnimationRef.current) return; - setTimeout(() => { - runAnimation(false); - }, delayShowTime + autoHideTime); - }, [autoHideTime, delayShowTime, onPress, runAnimation]); - - const handleVerticalTooltipLayout = (event: LayoutChangeEvent) => { - const { width, height } = event.nativeEvent.layout; - if (placement === 'top') { - setTooltipPosition({ top: -height, left: 0 }); + Animated.timing(animatedValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start(() => { + onVisibleChange && onVisibleChange(false); + }); + hideAnimationRef.current = true; } - setTooltipSize({ - width, - height, - }); - }; + }, + [animatedValue, onVisibleChange] + ); - const handleHorizontalTooltipLayout = (event: LayoutChangeEvent) => { - const { width, height } = event.nativeEvent.layout; + // show animation + useEffect(() => { + if (visible !== undefined && !visible) { + return; + } - const newLeft = width - 4; - if (placement === 'left') { - setTooltipPosition({ top: 0, left: -newLeft }); - } - setTooltipSize({ - width, - height, - }); - }; + setTimeout(() => { + runAnimation(true); + }, delayShowTime); + }, [delayShowTime, runAnimation, visible]); - const renderVerticalArrow = () => ( - { - if (anchor === 'left') return 'flex-start'; - if (anchor === 'right') return 'flex-end'; - return 'center'; - })(), - }, - styles?.containerStyle, - ]} - > - { - let x = SIDE_ARROW_INSET + (offset?.arrow?.x || 0); + // hide animation + useEffect(() => { + if (onPress) return; - if (!isVerticalPlacement) { - x = offset?.arrow?.x || 0; - } - if (anchor === 'center') { - x = offset?.arrow?.x || 0; - } - if (anchor === 'right') { - x = -x; - } + setTimeout(() => { + runAnimation(false); + }, delayShowTime + autoHideTime); + }, [autoHideTime, delayShowTime, onPress, runAnimation]); - return x; - })(), - top: (() => { - let y = SIDE_ARROW_INSET + (offset?.arrow?.y || 0); + const handleVerticalTooltipLayout = (event: LayoutChangeEvent) => { + const { width, height } = event.nativeEvent.layout; + if (placement === 'top') { + setTooltipPosition({ top: -height, left: 0 }); + } + setTooltipSize({ + width, + height, + }); + }; - if (isVerticalPlacement) { - y = offset?.arrow?.y || 0; - } - if (anchor === 'center') { - y = offset?.arrow?.y || 0; - } - if (anchor === 'bottom') { - y = -y; - } + const handleHorizontalTooltipLayout = (event: LayoutChangeEvent) => { + const { width, height } = event.nativeEvent.layout; - // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 - return y + (Platform.OS === 'android' ? 0.1 : 0); - })(), - transform: (() => { - if (placement === 'bottom') return [{ rotate: '0deg' }]; - if (placement === 'top') return [{ rotate: '180deg' }]; - if (placement === 'left') return [{ rotate: '90deg' }]; - if (placement === 'right') return [{ rotate: '-90deg' }]; - return undefined; - })(), - }} - > - {arrowElement === undefined && arrowElement} - - - ); + const newLeft = width - 4; + if (placement === 'left') { + setTooltipPosition({ top: 0, left: -newLeft }); + } + setTooltipSize({ + width, + height, + }); + }; - const renderHorizontalArrow = () => ( + const renderVerticalArrow = () => ( + { + if (anchor === 'left') return 'flex-start'; + if (anchor === 'right') return 'flex-end'; + return 'center'; + })(), + }, + styles?.containerStyle, + ]} + > { - let left = 0; - if (placement === 'left') { - left = -2; + let x = SIDE_ARROW_INSET + (offset?.arrow?.x || 0); + + if (!isVerticalPlacement) { + x = offset?.arrow?.x || 0; + } + if (anchor === 'center') { + x = offset?.arrow?.x || 0; } - if (placement === 'right') { - left = 2; + if (anchor === 'right') { + x = -x; } - // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 - return left + (Platform.OS === 'android' ? 0.1 : 0); + return x; })(), top: (() => { - return 0; + let y = SIDE_ARROW_INSET + (offset?.arrow?.y || 0); + + if (isVerticalPlacement) { + y = offset?.arrow?.y || 0; + } + if (anchor === 'center') { + y = offset?.arrow?.y || 0; + } + if (anchor === 'bottom') { + y = -y; + } + + // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 + return y + (Platform.OS === 'android' ? 0.1 : 0); })(), transform: (() => { if (placement === 'bottom') return [{ rotate: '0deg' }]; @@ -269,146 +248,165 @@ const Tooltip = React.memo( > {arrowElement === undefined && arrowElement} - ); + + ); - const renderTooltipContent = () => ( - // View Overflow 영역에 있는 Tooltip을 선택하기 위해 TouchableOpacity 사용 - { - if (!onPress) { - return; + const renderHorizontalArrow = () => ( + { + let left = 0; + if (placement === 'left') { + left = -2; + } + if (placement === 'right') { + left = 2; } - runAnimation(false); - onPress && onPress(); - }} - style={{ - backgroundColor: tooltipColor, - borderRadius: 10, - ...(styles?.tooltipStyle || TOOLTIP_STYLE), - }} - > - - {/* content */} - {typeof text !== 'string' ? ( - text - ) : ( - - )} + // 은찬: AOS에서 간혈적으로 박스와 화살표 사이에 틈이 보이는 이슈가 있어서 0.1 추가 + return left + (Platform.OS === 'android' ? 0.1 : 0); + })(), + top: (() => { + return 0; + })(), + transform: (() => { + if (placement === 'bottom') return [{ rotate: '0deg' }]; + if (placement === 'top') return [{ rotate: '180deg' }]; + if (placement === 'left') return [{ rotate: '90deg' }]; + if (placement === 'right') return [{ rotate: '-90deg' }]; + return undefined; + })(), + }} + > + {arrowElement === undefined && arrowElement} + + ); - {onPress && ( - <> - - - - )} - - - ); + const renderTooltipContent = () => ( + // View Overflow 영역에 있는 Tooltip을 선택하기 위해 TouchableOpacity 사용 + { + if (!onPress) { + return; + } - const renderVerticalTooltip = () => ( - { - if (anchor === 'left') return 'flex-start'; - if (anchor === 'right') return 'flex-end'; - return 'center'; - })(), - top: tooltipPosition.top + (offset?.position?.y || 0), - left: tooltipPosition.left + (offset?.position?.x || 0), - }} - > - - {placement === 'top' && renderTooltipContent()} - {renderVerticalArrow()} - {placement === 'bottom' && renderTooltipContent()} - - - ); - const renderHorizontalTooltip = () => ( - // 툴팁 & 화살표 정렬 기준 ( top, center, bottom ) + runAnimation(false); + onPress && onPress(); + }} + style={{ + backgroundColor: tooltipColor, + borderRadius: 10, + ...(styles?.tooltipStyle || TOOLTIP_STYLE), + }} + > { - if (anchor === 'top') return 'flex-start'; - if (anchor === 'bottom') return 'flex-end'; - return 'center'; - })(), - }} + style={{ flexDirection: 'row', justifyContent: 'space-between' }} + pointerEvents={'box-none'} > - - {placement === 'left' && renderTooltipContent()} - {renderHorizontalArrow()} - {placement === 'right' && renderTooltipContent()} - - - ); - - return ( - - {placement === 'top' && renderVerticalTooltip()} - {isVerticalPlacement ? ( - children + {/* content */} + {typeof text !== 'string' ? ( + text ) : ( - - {placement === 'left' && renderHorizontalTooltip()} - {children} - {placement === 'right' && renderHorizontalTooltip()} - + + )} + + {onPress && ( + <> + + + )} - {placement === 'bottom' && renderVerticalTooltip()} - ); - }, - (prevProps, nextProps) => { - // 참이면 리덴더링 하지 않아도 됨 - let notNeedToRerender = true; + + ); - if (prevProps.visible !== nextProps.visible) { - notNeedToRerender = false; - } - if (prevProps.rerenderKey !== nextProps.rerenderKey) { - notNeedToRerender = false; - } + const renderVerticalTooltip = () => ( + { + if (anchor === 'left') return 'flex-start'; + if (anchor === 'right') return 'flex-end'; + return 'center'; + })(), + top: tooltipPosition.top + (offset?.position?.y || 0), + left: tooltipPosition.left + (offset?.position?.x || 0), + }} + > + + {placement === 'top' && renderTooltipContent()} + {renderVerticalArrow()} + {placement === 'bottom' && renderTooltipContent()} + + + ); + const renderHorizontalTooltip = () => ( + // 툴팁 & 화살표 정렬 기준 ( top, center, bottom ) + { + if (anchor === 'top') return 'flex-start'; + if (anchor === 'bottom') return 'flex-end'; + return 'center'; + })(), + }} + > + + {placement === 'left' && renderTooltipContent()} + {renderHorizontalArrow()} + {placement === 'right' && renderTooltipContent()} + + + ); - return notNeedToRerender; - } -); + return ( + + {placement === 'top' && renderVerticalTooltip()} + {isVerticalPlacement ? ( + children + ) : ( + + {placement === 'left' && renderHorizontalTooltip()} + {children} + {placement === 'right' && renderHorizontalTooltip()} + + )} + {placement === 'bottom' && renderVerticalTooltip()} + + ); +}; export default Tooltip;