Fix iOS Sources panel expansion in agent citations (#9567)
* Fix iOS citations panel toggle reliability Remove the animated maxHeight/opacity accordion from agent citations so the Sources section expands and collapses consistently on iOS without hidden or underlapping content. Made-with: Cursor * Restore citations panel animation with stable transitions Use Reanimated enter/exit and layout transitions for the Sources panel so opening and closing stay animated while preserving the iOS reliability fix. Made-with: Cursor * Fix citations panel collapse animation overlapping content below Replace entering/exiting animations with shared-value-driven height and opacity transitions. Reanimated's exiting animations pull the view out of layout flow as an absolute overlay, causing the fading content to overlap the regenerate button during collapse. Using an always-mounted Animated.View with measured content height and overflow clipping ensures smooth expand/collapse without overlap. Made-with: Cursor
This commit is contained in:
parent
c391484cc8
commit
e977303275
1 changed files with 38 additions and 21 deletions
|
|
@ -2,8 +2,8 @@
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {TOUCH_TARGET_SIZE} from '@agents/constants';
|
import {TOUCH_TARGET_SIZE} from '@agents/constants';
|
||||||
import React, {useCallback, useState} from 'react';
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
import {Text, TouchableOpacity, View} from 'react-native';
|
import {type LayoutChangeEvent, Text, TouchableOpacity, View} from 'react-native';
|
||||||
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||||
|
|
||||||
import CompassIcon from '@components/compass_icon';
|
import CompassIcon from '@components/compass_icon';
|
||||||
|
|
@ -41,7 +41,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
marginLeft: 8,
|
marginLeft: 8,
|
||||||
},
|
},
|
||||||
citationsList: {
|
citationsList: {
|
||||||
marginTop: 4,
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
citationsContentWrapper: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
},
|
},
|
||||||
citationItem: {
|
citationItem: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
@ -90,15 +94,22 @@ const CitationsList = ({annotations}: CitationsListProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getStyleSheet(theme);
|
const styles = getStyleSheet(theme);
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const animatedHeight = useSharedValue(0);
|
const progress = useSharedValue(0);
|
||||||
const opacity = 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 handleToggle = useCallback(() => {
|
||||||
const newExpanded = !isExpanded;
|
setIsExpanded((prev) => !prev);
|
||||||
setIsExpanded(newExpanded);
|
}, []);
|
||||||
animatedHeight.value = withTiming(newExpanded ? 1 : 0, {duration: 250});
|
|
||||||
opacity.value = withTiming(newExpanded ? 1 : 0, {duration: 250});
|
const handleContentLayout = useCallback((e: LayoutChangeEvent) => {
|
||||||
}, [isExpanded, animatedHeight, opacity]);
|
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) => {
|
const handleCitationPress = useCallback((url: string) => {
|
||||||
if (url) {
|
if (url) {
|
||||||
|
|
@ -106,11 +117,14 @@ const CitationsList = ({annotations}: CitationsListProps) => {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listAnimatedStyle = useAnimatedStyle(() => ({
|
const collapsibleStyle = useAnimatedStyle(() => {
|
||||||
opacity: opacity.value,
|
const p = progress.value;
|
||||||
maxHeight: animatedHeight.value === 0 ? 0 : undefined,
|
return {
|
||||||
overflow: 'hidden',
|
height: contentHeight.value > 0 ? p * contentHeight.value : 0,
|
||||||
}));
|
opacity: p,
|
||||||
|
marginTop: p * 4,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
|
@ -139,11 +153,14 @@ const CitationsList = ({annotations}: CitationsListProps) => {
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{isExpanded && (
|
<Animated.View style={[styles.citationsList, collapsibleStyle]}>
|
||||||
<Animated.View style={[styles.citationsList, listAnimatedStyle]}>
|
<View
|
||||||
{annotations.map((annotation, idx) => (
|
onLayout={handleContentLayout}
|
||||||
|
style={styles.citationsContentWrapper}
|
||||||
|
>
|
||||||
|
{annotations.map((annotation) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={`citation-${annotation.index}-${idx}`}
|
key={`citation-${annotation.index}-${annotation.url}`}
|
||||||
onPress={() => handleCitationPress(annotation.url)}
|
onPress={() => handleCitationPress(annotation.url)}
|
||||||
style={styles.citationItem}
|
style={styles.citationItem}
|
||||||
testID={`citations.list.item.${annotation.index}`}
|
testID={`citations.list.item.${annotation.index}`}
|
||||||
|
|
@ -176,8 +193,8 @@ const CitationsList = ({annotations}: CitationsListProps) => {
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue