diff --git a/app/products/agents/components/citations_list/index.tsx b/app/products/agents/components/citations_list/index.tsx index 137b1489a..d990ced72 100644 --- a/app/products/agents/components/citations_list/index.tsx +++ b/app/products/agents/components/citations_list/index.tsx @@ -2,8 +2,8 @@ // See LICENSE.txt for license information. import {TOUCH_TARGET_SIZE} from '@agents/constants'; -import React, {useCallback, useState} from 'react'; -import {Text, TouchableOpacity, View} from 'react-native'; +import React, {useCallback, useEffect, useState} from 'react'; +import {type LayoutChangeEvent, Text, TouchableOpacity, View} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import CompassIcon from '@components/compass_icon'; @@ -41,7 +41,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { marginLeft: 8, }, citationsList: { - marginTop: 4, + overflow: 'hidden', + }, + citationsContentWrapper: { + position: 'absolute', + width: '100%', }, citationItem: { flexDirection: 'row', @@ -90,15 +94,22 @@ const CitationsList = ({annotations}: CitationsListProps) => { const theme = useTheme(); const styles = getStyleSheet(theme); const [isExpanded, setIsExpanded] = useState(false); - const animatedHeight = useSharedValue(0); - const opacity = useSharedValue(0); + const progress = useSharedValue(0); + const contentHeight = useSharedValue(0); + + useEffect(() => { + progress.value = withTiming(isExpanded ? 1 : 0, {duration: 250}); + // eslint-disable-next-line react-hooks/exhaustive-deps -- progress is a stable shared value ref + }, [isExpanded]); const handleToggle = useCallback(() => { - const newExpanded = !isExpanded; - setIsExpanded(newExpanded); - animatedHeight.value = withTiming(newExpanded ? 1 : 0, {duration: 250}); - opacity.value = withTiming(newExpanded ? 1 : 0, {duration: 250}); - }, [isExpanded, animatedHeight, opacity]); + setIsExpanded((prev) => !prev); + }, []); + + const handleContentLayout = useCallback((e: LayoutChangeEvent) => { + contentHeight.value = e.nativeEvent.layout.height; + // eslint-disable-next-line react-hooks/exhaustive-deps -- contentHeight is a stable shared value ref + }, []); const handleCitationPress = useCallback((url: string) => { if (url) { @@ -106,11 +117,14 @@ const CitationsList = ({annotations}: CitationsListProps) => { } }, []); - const listAnimatedStyle = useAnimatedStyle(() => ({ - opacity: opacity.value, - maxHeight: animatedHeight.value === 0 ? 0 : undefined, - overflow: 'hidden', - })); + const collapsibleStyle = useAnimatedStyle(() => { + const p = progress.value; + return { + height: contentHeight.value > 0 ? p * contentHeight.value : 0, + opacity: p, + marginTop: p * 4, + }; + }); return ( @@ -139,11 +153,14 @@ const CitationsList = ({annotations}: CitationsListProps) => { /> - {isExpanded && ( - - {annotations.map((annotation, idx) => ( + + + {annotations.map((annotation) => ( handleCitationPress(annotation.url)} style={styles.citationItem} testID={`citations.list.item.${annotation.index}`} @@ -176,8 +193,8 @@ const CitationsList = ({annotations}: CitationsListProps) => { /> ))} - - )} + + ); };