All files / src/components/animation UnfoldListItems.tsx

100% Statements 19/19
93.33% Branches 14/15
100% Functions 5/5
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                                              29x                 107x 107x 107x 107x   107x 139x 139x 52x       107x     139x 139x 139x 139x                   29x 107x                 107x   13x                                   94x                        
import { CARD_CONTENT_HEIGHT } from "@shared/constants";
import { ElementType, useEffect, useRef } from "react";
import { useTrail, animated, SpringValue } from "react-spring";
 
type TCustomAnimations = "waterfall" | "";
 
type ItemComponent<T> = (
  data: T,
  index: number,
  height: SpringValue<number>,
  springProps?: any,
) => JSX.Element;
 
type Props<T> = {
  items: T[];
  renderKey: (data: T, index: number) => React.Key | null | undefined;
  ItemComponent: ItemComponent<T>;
  config?: any;
  Wrapper?: ElementType;
  height?: number;
  animationName?: TCustomAnimations;
};
 
const UnfoldListItems = <T extends object | string>({
  items,
  renderKey,
  ItemComponent,
  config,
  Wrapper = animated.div,
  animationName = "",
  height = CARD_CONTENT_HEIGHT,
}: Props<T>) => {
  const defaultConfig = getDefaultConfig(animationName, height);
  const trail = useTrail(items?.length, config ? config : defaultConfig);
  const renderedItemsRef = useRef<Set<React.Key>>(new Set());
  useEffect(() => {
    // Add newly rendered items to the set
    items.forEach((item, index) => {
      const key = renderKey(item, index);
      if (key && !renderedItemsRef.current.has(key)) {
        renderedItemsRef.current.add(key);
      }
    });
  }, [items, renderKey]);
  return (
    <>
      {trail?.map(({ height, ...rest }, index) => {
        const data = items[index];
        const key = renderKey(data, index);
        const isRendered = renderedItemsRef.current.has(key || index);
        return (
          <Wrapper key={renderKey(data, index)} style={isRendered ? {} : rest}>
            {ItemComponent(data, index, height, rest)}
          </Wrapper>
        );
      })}
    </>
  );
};
 
const getDefaultConfig = (animationName: TCustomAnimations, height: number) => {
  const commonConfig = {
    opacity: 1,
    height,
    from: {
      opacity: 0,
      height: 0,
    },
  };
 
  switch (animationName) {
    case "waterfall":
      return {
        config: {
          mass: 1,
          tension: 1500,
          friction: 100,
        },
        x: 0,
        y: 0,
        ...commonConfig,
        transform: "scale(1)",
        from: {
          x: 10,
          y: 50,
          transform: "scale(0.8)",
          ...commonConfig.from,
        },
      };
    default:
      return {
        config: {
          mass: 1,
          tension: 1000,
          friction: 200,
        },
        ...commonConfig,
      };
  }
};
 
export default UnfoldListItems;