-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implemented accordion effect for macros change overview
- Loading branch information
Showing
9 changed files
with
493 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import React, { useRef, useState } from 'react'; | ||
import { | ||
FlatList, | ||
TouchableOpacity, | ||
StyleSheet, | ||
View, | ||
Text, | ||
NativeScrollEvent, | ||
NativeSyntheticEvent, | ||
Pressable, | ||
} from 'react-native'; | ||
import { Colors } from 'react-native-ui-lib'; | ||
|
||
type Unit = { | ||
id: number; | ||
symbol: string; | ||
}; | ||
|
||
interface HorizontalUnitsPickerProps { | ||
data: Unit[]; | ||
wheelWidth: number; // The visible width of this picker | ||
wheelHeight?: number; // Let the parent specify a fixed height for the picker | ||
} | ||
|
||
export default function HorizontalUnitsPicker({ | ||
data, | ||
wheelWidth, | ||
wheelHeight = 80, // fallback to 80 if none given | ||
}: HorizontalUnitsPickerProps) { | ||
const [selectedIndex, setSelectedIndex] = useState<number>(0); | ||
const flatListRef = useRef<FlatList<Unit>>(null); | ||
|
||
// Each item takes up 1/3 of the available picker width | ||
const ITEM_WIDTH = wheelWidth / 3; | ||
|
||
// Called when scrolling stops. Figure out which item is centered, set it as selected. | ||
const handleMomentumScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => { | ||
const offsetX = e.nativeEvent.contentOffset.x; | ||
const index = Math.round(offsetX / ITEM_WIDTH); | ||
setSelectedIndex(index); | ||
}; | ||
|
||
// Called when user taps an item. Scroll there, set selectedIndex. | ||
const handlePress = (index: number) => { | ||
setSelectedIndex(index); | ||
flatListRef.current?.scrollToOffset({ | ||
offset: index * ITEM_WIDTH, | ||
animated: true, | ||
}); | ||
}; | ||
|
||
return ( | ||
<View | ||
style={{ | ||
width: wheelWidth, | ||
height: wheelHeight, | ||
borderRadius: 20, | ||
overflow: 'hidden', // ensures children respect parent’s border radius | ||
backgroundColor: 'white', | ||
}}> | ||
<FlatList | ||
ref={flatListRef} | ||
data={data} | ||
horizontal | ||
keyExtractor={(item) => String(item.id)} | ||
// Makes the FlatList fill the parent container's height | ||
style={{ flex: 1 }} | ||
showsHorizontalScrollIndicator={false} | ||
getItemLayout={(_, index) => ({ | ||
length: ITEM_WIDTH, | ||
offset: ITEM_WIDTH * index, | ||
index, | ||
})} | ||
// Adds symmetrical horizontal padding so items can center | ||
contentContainerStyle={{ | ||
paddingHorizontal: (wheelWidth - ITEM_WIDTH) / 2, | ||
}} | ||
snapToInterval={ITEM_WIDTH} | ||
snapToAlignment="start" | ||
decelerationRate="fast" | ||
onMomentumScrollEnd={handleMomentumScrollEnd} | ||
renderItem={({ item, index }) => { | ||
const isSelected = index === selectedIndex; | ||
return ( | ||
<Pressable | ||
style={[styles.itemContainer, { width: ITEM_WIDTH }]} | ||
onPress={() => handlePress(index)}> | ||
<Text style={[styles.itemText, isSelected && styles.itemTextSelected]}> | ||
{item.symbol} | ||
</Text> | ||
</Pressable> | ||
); | ||
}} | ||
/> | ||
</View> | ||
); | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
itemContainer: { | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
}, | ||
itemText: { | ||
color: Colors.grey40, | ||
fontSize: 18, | ||
}, | ||
itemTextSelected: { | ||
fontSize: 18, | ||
color: Colors.violet30, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// KcalsTransition.tsx | ||
import React, { useEffect } from 'react'; | ||
import { LayoutChangeEvent, StyleSheet } from 'react-native'; | ||
import Animated, { | ||
useSharedValue, | ||
useAnimatedStyle, | ||
withTiming, | ||
Easing, | ||
} from 'react-native-reanimated'; | ||
import { Text, View } from 'react-native-ui-lib'; | ||
import IconSVG from 'components/Shared/icons/IconSVG'; | ||
import { useColors } from 'context/ColorContext'; | ||
|
||
type KcalsTransitionProps = { | ||
current: number; | ||
after?: number; | ||
expanded: boolean; | ||
expandedHeight: number; // Added prop for expanded height | ||
onLayout?: (event: LayoutChangeEvent) => void; | ||
}; | ||
|
||
export default function KcalsTransition({ | ||
current, | ||
after, | ||
expanded, | ||
expandedHeight, | ||
onLayout, | ||
}: KcalsTransitionProps) { | ||
const colors = useColors(); | ||
const numbersLength = current.toString().length + (after?.toString().length || 0); | ||
const textStyle = { fontSize: numbersLength > 12 ? 14 : 16 }; | ||
|
||
// Shared value for height | ||
const height = useSharedValue(80); // Contracted height | ||
|
||
useEffect(() => { | ||
height.value = withTiming(expanded ? expandedHeight : 80, { | ||
duration: 400, | ||
easing: Easing.inOut(Easing.ease), | ||
}); | ||
}, [expanded, expandedHeight, height]); | ||
|
||
// Animated style | ||
const animatedStyle = useAnimatedStyle(() => ({ | ||
height: height.value, | ||
})); | ||
|
||
return ( | ||
<Animated.View | ||
onLayout={onLayout} | ||
style={[styles.flex, { backgroundColor: colors.get('kcals') }, animatedStyle]}> | ||
<IconSVG color="white" name="ball-pile-solid" width={20} /> | ||
<Text white style={textStyle}> | ||
{current} {!expanded && 'kcal'} | ||
</Text> | ||
{expanded && ( | ||
<> | ||
<IconSVG color={'white'} name="arrow-right-solid" width={16} /> | ||
<Text white style={textStyle}> | ||
{after} | ||
</Text> | ||
</> | ||
)} | ||
</Animated.View> | ||
); | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
flex: { | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
justifyContent: 'space-around', | ||
paddingHorizontal: 8, | ||
overflow: 'hidden', // Ensure content doesn't overflow during animation | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React, { PropsWithChildren, useEffect } from 'react'; | ||
import { ViewStyle } from 'react-native'; | ||
import Animated, { | ||
useSharedValue, | ||
useAnimatedStyle, | ||
withTiming, | ||
Easing, | ||
} from 'react-native-reanimated'; | ||
|
||
interface MacrosAccordionProps extends PropsWithChildren { | ||
expanded: boolean; | ||
leftoverSpace: number; // height the Accordion should animate to | ||
} | ||
|
||
/** | ||
* This animated container starts at height=0 (collapsed) | ||
* and expands to leftoverSpace when 'expanded' is true. | ||
*/ | ||
export default function MacrosAccordion({ | ||
expanded, | ||
leftoverSpace, | ||
children, | ||
}: MacrosAccordionProps) { | ||
const heightValue = useSharedValue(0); | ||
|
||
// Animate from 0 → leftoverSpace or leftoverSpace → 0 | ||
useEffect(() => { | ||
heightValue.value = withTiming(expanded ? leftoverSpace : 0, { | ||
duration: 400, | ||
easing: Easing.inOut(Easing.ease), | ||
}); | ||
}, [expanded, leftoverSpace]); | ||
|
||
// Animated style: we update 'height' and optionally 'opacity' | ||
const containerStyle = useAnimatedStyle<ViewStyle>(() => ({ | ||
height: heightValue.value, | ||
overflow: 'hidden', | ||
})); | ||
|
||
return <Animated.View style={containerStyle}>{children}</Animated.View>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import IconSVG from 'components/Shared/icons/IconSVG'; | ||
import { useColors } from 'context/ColorContext'; | ||
import { StyleSheet } from 'react-native'; | ||
import { Text, View } from 'react-native-ui-lib'; | ||
|
||
type MacrosTransitionProps = { | ||
current: number; | ||
after?: number; | ||
macro: 'carbs' | 'protein' | 'fat'; | ||
}; | ||
|
||
export default function MacrosTransition({ current, after, macro }: MacrosTransitionProps) { | ||
const colors = useColors(); | ||
|
||
const numbersLength = current.toString().length + (after?.toString().length || 0); | ||
const textStyle = { fontSize: numbersLength > 12 ? 14 : 16 }; | ||
|
||
return ( | ||
<View style={[styles.flex, { backgroundColor: colors.get(macro) }]}> | ||
<IconSVG color="white" name={determineIcon(macro)} width={20} /> | ||
<Text white style={textStyle}> | ||
{current} | ||
</Text> | ||
<IconSVG color={'white'} name="arrow-right-solid" width={16} /> | ||
<Text white style={textStyle}> | ||
{after} | ||
</Text> | ||
</View> | ||
); | ||
} | ||
|
||
function determineIcon(macro: 'carbs' | 'protein' | 'fat') { | ||
switch (macro) { | ||
case 'fat': | ||
return 'bacon-solid'; | ||
case 'carbs': | ||
return 'wheat-solid'; | ||
case 'protein': | ||
return 'meat-solid'; | ||
} | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
flex: { | ||
flex: 1, | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
justifyContent: 'space-around', | ||
paddingHorizontal: 8, | ||
}, | ||
}); |
Oops, something went wrong.