about summary refs log tree commit diff
path: root/src/lib/custom-animations/AccordionAnimation.tsx
blob: 146735aa65cbfa93c4866417e4808da9ae0103f4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import {
  type LayoutChangeEvent,
  type StyleProp,
  View,
  type ViewStyle,
} from 'react-native'
import Animated, {
  Easing,
  FadeInUp,
  FadeOutUp,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated'

import {isIOS, isWeb} from '#/platform/detection'

type AccordionAnimationProps = React.PropsWithChildren<{
  isExpanded: boolean
  duration?: number
  style?: StyleProp<ViewStyle>
}>

function WebAccordion({
  isExpanded,
  duration = 300,
  style,
  children,
}: AccordionAnimationProps) {
  const heightValue = useSharedValue(0)

  const animatedStyle = useAnimatedStyle(() => {
    const targetHeight = isExpanded ? heightValue.get() : 0
    return {
      height: withTiming(targetHeight, {
        duration,
        easing: Easing.out(Easing.cubic),
      }),
      overflow: 'hidden',
    }
  })

  const onLayout = (e: LayoutChangeEvent) => {
    if (heightValue.get() === 0) {
      heightValue.set(e.nativeEvent.layout.height)
    }
  }

  return (
    <Animated.View style={[animatedStyle, style]}>
      <View onLayout={onLayout}>{children}</View>
    </Animated.View>
  )
}

function MobileAccordion({
  isExpanded,
  duration = 200,
  style,
  children,
}: AccordionAnimationProps) {
  if (!isExpanded) return null

  return (
    <Animated.View
      style={style}
      entering={FadeInUp.duration(duration)}
      exiting={FadeOutUp.duration(duration / 2)}
      pointerEvents={isIOS ? 'auto' : 'box-none'}>
      {children}
    </Animated.View>
  )
}

export function AccordionAnimation(props: AccordionAnimationProps) {
  return isWeb ? <WebAccordion {...props} /> : <MobileAccordion {...props} />
}