Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Reanimated V2 / V3? #470

Open
wmonecke opened this issue Jul 22, 2023 · 5 comments
Open

Support for Reanimated V2 / V3? #470

wmonecke opened this issue Jul 22, 2023 · 5 comments

Comments

@wmonecke
Copy link

Will there be support for Reanimated V2?

Are PRs welcome?

@wmonecke
Copy link
Author

wmonecke commented Jul 24, 2023

For anyone looking for a reanimated v2 Collapsible component, here is mine:

import {
  FunctionComponent,
  PropsWithChildren,
  useCallback,
  useEffect,
  useState,
} from 'react';
import {View, ViewStyle} from 'react-native';
import Animated, {
  Easing,
  interpolate,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

type Props = PropsWithChildren<{
  collapsed: boolean;
  style?: ViewStyle;
}>;

const Collpasible: FunctionComponent<Props> = ({
  collapsed,
  children,
  style,
}) => {
  const shareValue = useSharedValue(0);
  const [bodySectionHeight, setBodySectionHeight] = useState(null);

  const bodyHeight = useAnimatedStyle(() => ({
    height: interpolate(shareValue.value, [0, 1], [0, bodySectionHeight ?? 0]),
    opacity: interpolate(shareValue.value, [0, 1], [0, 1]),
  }));

  const toggleCollapsed = useCallback(
    collapsed => {
      const nextValue = collapsed ? 0 : 1;

      shareValue.value = withTiming(nextValue, {
        duration: 500,
        easing: Easing.bezier(0.4, 0.0, 0.2, 1),
      });
    },
    [shareValue],
  );

  useEffect(() => {
    toggleCollapsed(collapsed);
  }, [collapsed, toggleCollapsed]);

  return (
    <Animated.View style={[{overflow: 'hidden'}, bodyHeight]}>
      <View
        style={[
          {
            position: 'absolute',
            bottom: 0,
            left: 0,
            right: 0,
          },
          style,
        ]}
        onLayout={event => {
          setBodySectionHeight(event.nativeEvent.layout.height);
        }}>
        {children}
      </View>
    </Animated.View>
  );
};

export default Collpasible;

@wmonecke wmonecke changed the title Support for Reanimated V2? Support for Reanimated V2 / V3? Aug 13, 2023
@skizzo
Copy link

skizzo commented Sep 17, 2023

Hey @wmonecke , thanks a lot for this!
Any chance I can avoid his from "jumping" (from 0 to its final height) when mounting this with collapsed set to false?

@raxenov0
Copy link

raxenov0 commented Oct 5, 2023

@wmonecke good change! Works much smoother :)

@sleep9
Copy link

sleep9 commented Feb 24, 2024

How to set a collapsedHeight with this implementation?

@RayKay91
Copy link

RayKay91 commented May 7, 2024

How to set a collapsedHeight with this implementation?

I adapted @wmonecke code to make an impkementation in TS that supports a couple more props:

import {
  type PropsWithChildren,
  useCallback,
  useEffect,
  useState,
} from 'react';
import type { LayoutChangeEvent, ViewStyle } from 'react-native';
import {
  Easing,
  interpolate,
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

type CollapsibleProps = PropsWithChildren<{
  collapsed: boolean;
  style?: ViewStyle;
  collapsedHeight?: number;
  testID?: string;
  pinTo?: 'top' | 'bottom'; // Use 'top' to ensure the top of the content is always visible when partially collapsed
  renderChildrenCollapsed?: boolean;
}>;

const ANIMATION_CONFIG = {
  duration: 300,
  easing: Easing.bezier(0.4, 0.0, 0.2, 1),
};

export const Collapsible = ({
  collapsed,
  children,
  style,
  collapsedHeight,
  testID,
  pinTo = 'top',
  renderChildrenCollapsed = true,
}: CollapsibleProps) => {
  const sharedValue = useSharedValue(0);
  const [bodySectionHeight, setBodySectionHeight] = useState<null | number>(
    null,
  );

  const [showTheKids, setShowTheKids] = useState(true);
  const initialHeight = collapsedHeight ?? 0;

  const bodyHeight = useAnimatedStyle(() => ({
    height: interpolate(
      sharedValue.value,
      [initialHeight ?? 0, 1],
      [initialHeight, bodySectionHeight ?? initialHeight],
    ),
  }));

  const onAnimationEnd = useCallback(() => {
    if (collapsed && !renderChildrenCollapsed) {
      setShowTheKids(false);
    }
  }, [collapsed, renderChildrenCollapsed]);

  const toggleCollapsed = useCallback(
    (isCollapsed: CollapsibleProps['collapsed']) => {
      const nextValue = isCollapsed ? initialHeight : 1;

      if (!isCollapsed && !showTheKids) {
        setShowTheKids(true);
      }

      sharedValue.value = withTiming(
        nextValue,
        ANIMATION_CONFIG,
        (didFinish) => {
          if (!didFinish) {
            return;
          }

          runOnJS(onAnimationEnd)();
        },
      );
    },
    [initialHeight, showTheKids, sharedValue, onAnimationEnd],
  );

  const handleLayout = useCallback(
    (event: LayoutChangeEvent) => {
      if (bodySectionHeight !== null) {
        return;
      }

      setBodySectionHeight(event.nativeEvent.layout.height);
    },
    [bodySectionHeight],
  );

  useEffect(() => {
    toggleCollapsed(collapsed);
  }, [collapsed, toggleCollapsed]);

  return (
    <Animated.View
      style={[{overflow: 'hidden'}, bodyHeight]}>
      <View style={[{position: 'absolute', [pinTo]: 0, left: 0, right: 0 }, style]} onLayout={handleLayout}>
        {showTheKids && children}
      </View>
    </Animated.View>
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants