import * as React from 'react'; import type { ViewProps } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, } from 'react-native-reanimated'; import { LineChartDimensionsContext } from './Chart'; import { CursorContext } from './Cursor'; import { LineChartPriceText, LineChartPriceTextProps } from './PriceText'; import { useLineChart } from './useLineChart'; type LineChartTooltipProps = Animated.AnimateProps<ViewProps> & { children?: React.ReactNode; xGutter?: number; yGutter?: number; cursorGutter?: number; position?: 'top' | 'bottom'; textProps?: LineChartPriceTextProps; textStyle?: LineChartPriceTextProps['style']; }; LineChartTooltip.displayName = 'LineChartTooltip'; export function LineChartTooltip({ children, xGutter = 8, yGutter = 8, cursorGutter = 48, position = 'top', textProps, textStyle, ...props }: LineChartTooltipProps) { const { width, height } = React.useContext(LineChartDimensionsContext); const { type } = React.useContext(CursorContext); const { currentX, currentY, isActive } = useLineChart(); const x = useSharedValue(0); const elementWidth = useSharedValue(0); const elementHeight = useSharedValue(0); const handleLayout = React.useCallback( (event) => { x.value = event.nativeEvent.layout.x; elementWidth.value = event.nativeEvent.layout.width; elementHeight.value = event.nativeEvent.layout.height; }, [elementHeight, elementWidth, x] ); const animatedCursorStyle = useAnimatedStyle(() => { let translateXOffset = elementWidth.value / 2; if (currentX.value < elementWidth.value / 2 + xGutter) { const xOffset = elementWidth.value / 2 + xGutter - currentX.value; translateXOffset = translateXOffset - xOffset; } if (currentX.value > width - elementWidth.value / 2 - xGutter) { const xOffset = currentX.value - (width - elementWidth.value / 2 - xGutter); translateXOffset = translateXOffset + xOffset; } let translateYOffset = 0; if (position === 'top') { translateYOffset = elementHeight.value / 2 + cursorGutter; if (currentY.value - translateYOffset < yGutter) { translateYOffset = currentY.value - yGutter; } } else if (position === 'bottom') { translateYOffset = -(elementHeight.value / 2) - cursorGutter / 2; if ( currentY.value - translateYOffset + elementHeight.value > height - yGutter ) { translateYOffset = currentY.value - (height - yGutter) + elementHeight.value; } } return { transform: [ { translateX: currentX.value - translateXOffset }, { translateY: type === 'crosshair' ? currentY.value - translateYOffset : position === 'top' ? yGutter : height - elementHeight.value - yGutter, }, ], opacity: isActive.value ? 1 : 0, }; }); return ( <Animated.View onLayout={handleLayout} {...props} style={[ { position: 'absolute', padding: 4, alignSelf: 'flex-start', }, animatedCursorStyle, props.style, ]} > {children || <LineChartPriceText style={[textStyle]} {...textProps} />} </Animated.View> ); }