Last active
July 26, 2024 14:53
-
-
Save schriker/75e54acca7595d10d7d3ab7c255c7445 to your computer and use it in GitHub Desktop.
Accordion open by default
This file contains hidden or 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
| import { Pressable, Section, Typography } from '@components' | |
| import EntypoIcons from '@expo/vector-icons/Entypo' | |
| import { TypographySize } from '@types' | |
| import { View } from 'react-native' | |
| import Animated, { runOnUI, useAnimatedStyle, withTiming } from 'react-native-reanimated' | |
| import styled, { useTheme } from 'styled-components/native' | |
| import { useAccordion } from '../hooks' | |
| type AccordionItemProps = { | |
| index: number, | |
| label: string, | |
| body: string | |
| } | |
| export const AccordionItem: React.FunctionComponent<AccordionItemProps> = ({ | |
| label, | |
| index, | |
| body | |
| }) => { | |
| const theme = useTheme() | |
| const { setHeight, animatedHeightStyle, animatedRef, isOpened, openOnMounted } = useAccordion() | |
| const animatedChevronStyle = useAnimatedStyle(() => ({ | |
| transform: [ | |
| { | |
| rotate: withTiming(`${isOpened.value ? 90 : 0}deg`, { | |
| duration: 200 | |
| }) | |
| } | |
| ] | |
| })) | |
| return ( | |
| <Section marginBottom={0}> | |
| <ContentWrapper> | |
| <Pressable onPress={() => runOnUI(setHeight)()}> | |
| <LabelWrapper> | |
| <Typography | |
| size={TypographySize.SmallTitle} | |
| fontWeight={500} | |
| > | |
| {`${label} #${index + 1}`} | |
| </Typography> | |
| <Animated.View style={[animatedChevronStyle]}> | |
| <EntypoIcons | |
| name="chevron-small-right" | |
| size={20} | |
| color={theme.colors.typography.secondary} | |
| /> | |
| </Animated.View> | |
| </LabelWrapper> | |
| </Pressable> | |
| <Animated.View | |
| style={[animatedHeightStyle]} | |
| // Open accordion onLayout if it is not already open, we need this to run onLayout to get element height | |
| onLayout={() => { | |
| if (!openOnMounted.value) { | |
| openOnMounted.value = true | |
| } | |
| }} | |
| > | |
| <AccordionWrapper> | |
| <BodyWrapper | |
| ref={animatedRef} | |
| collapsable={false} | |
| > | |
| <Typography size={TypographySize.Body}> | |
| {body} | |
| </Typography> | |
| </BodyWrapper> | |
| </AccordionWrapper> | |
| </Animated.View> | |
| </ContentWrapper> | |
| </Section> | |
| ) | |
| } | |
| const ContentWrapper = styled(View)(() => ({ | |
| overflow: 'hidden' | |
| })) | |
| const AccordionWrapper = styled(View)(() => ({ | |
| position: 'absolute', | |
| left: 0, | |
| top: 0 | |
| })) | |
| const LabelWrapper = styled(View)(() => ({ | |
| flexDirection: 'row', | |
| alignItems: 'center', | |
| justifyContent: 'space-between', | |
| paddingLeft: 20, | |
| paddingRight: 10, | |
| paddingBottom: 10, | |
| paddingTop: 10 | |
| })) | |
| const BodyWrapper = styled(View)(() => ({ | |
| paddingLeft: 20, | |
| paddingRight: 20, | |
| paddingBottom: 20 | |
| })) |
This file contains hidden or 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
| import { View } from 'react-native' | |
| import { measure, useAnimatedReaction, useAnimatedRef, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated' | |
| export const useAccordion = () => { | |
| const openOnMounted = useSharedValue(false) | |
| const animatedRef = useAnimatedRef<View>() | |
| const isOpened = useSharedValue(false) | |
| const height = useSharedValue(0) | |
| const animatedHeightStyle = useAnimatedStyle(() => ({ | |
| height: withTiming(height.value) | |
| })) | |
| const setHeight = () => { | |
| 'worklet' | |
| height.value = !height.value | |
| ? Number(measure(animatedRef).height ?? 0) | |
| : 0 | |
| isOpened.value = !isOpened.value | |
| } | |
| // We can use useAnimatedReaction to run our setHeight method when some animated value changed | |
| useAnimatedReaction( | |
| () => openOnMounted.value, | |
| isOpenOnMounted => { | |
| if (isOpenOnMounted) { | |
| setHeight() | |
| } | |
| } | |
| ) | |
| return { | |
| animatedRef, | |
| setHeight, | |
| isOpened, | |
| animatedHeightStyle, | |
| openOnMounted | |
| } | |
| } |
Also, had to put the onLayout callback on the View that holds the aRef.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looks great, thanks! I tweaked it a bit so that I have a bit so that I can pass the defaultOpen value as an argument. Here's the final hook that worked great for me.