All files / packages/hooks/src useSearchOverlayAnimation.ts

97.67% Statements 42/43
85.71% Branches 12/14
100% Functions 13/13
97.56% Lines 40/41

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                            1x 12x 12x 12x 12x   12x         12x 9x 9x 9x     9x 6x   6x 6x         6x 1x 1x       6x 6x 6x         3x   3x 3x       3x 2x         3x 3x           12x               1x 7x   7x 5x 5x 5x 1x   5x 5x 5x 5x       7x    
import { useEffect, useRef, useState } from "react";
import {
  Easing,
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from "react-native-reanimated";
import { InteractionManager, Keyboard, Platform, TextInput } from "react-native";
 
type UseSearchOverlayAnimationParams = {
  isSearchOpen: boolean;
};
 
export const useSearchOverlayAnimation = ({ isSearchOpen }: UseSearchOverlayAnimationParams) => {
  const searchInputRef = useRef<TextInput>(null);
  const overlayOpacity = useSharedValue(0);
  const overlayTranslateY = useSharedValue(8);
  const [isSearchInteractive, setIsSearchInteractive] = useState(false);
 
  const searchOverlayAnimatedStyle = useAnimatedStyle(() => ({
    opacity: overlayOpacity.value,
    transform: [{ translateY: overlayTranslateY.value }],
  }));
 
  useEffect(() => {
    const openDuration = 220;
    const closeDuration = 180;
    const easing = Easing.out(Easing.cubic);
    let focusTimeout: ReturnType<typeof setTimeout> | undefined;
 
    if (isSearchOpen) {
      setIsSearchInteractive(true);
 
      overlayOpacity.value = withTiming(1, { duration: openDuration, easing });
      overlayTranslateY.value = withTiming(0, {
        duration: openDuration,
        easing,
      });
 
      focusTimeout = setTimeout(() => {
        InteractionManager.runAfterInteractions(() => {
          searchInputRef.current?.focus();
        });
      }, 120);
 
      return () => {
        Eif (focusTimeout) {
          clearTimeout(focusTimeout);
        }
      };
    }
 
    Keyboard.dismiss();
 
    overlayOpacity.value = withTiming(0, { duration: closeDuration, easing });
    overlayTranslateY.value = withTiming(
      8,
      { duration: closeDuration, easing },
      (finished: boolean | undefined) => {
        if (finished) {
          runOnJS(setIsSearchInteractive)(false);
        }
      },
    );
 
    return () => {
      Iif (focusTimeout) {
        clearTimeout(focusTimeout);
      }
    };
  }, [isSearchOpen, overlayOpacity, overlayTranslateY]);
 
  return {
    searchInputRef,
    isSearchInteractive,
    shouldHideBaseContent: isSearchOpen || isSearchInteractive,
    searchOverlayAnimatedStyle,
  };
};
 
export const useKeyboardPadding = (): number => {
  const [keyboardPadding, setKeyboardPadding] = useState(0);
 
  useEffect(() => {
    const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
    const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
    const show = Keyboard.addListener(showEvent, (e) =>
      setKeyboardPadding(e.endCoordinates.height),
    );
    const hide = Keyboard.addListener(hideEvent, () => setKeyboardPadding(0));
    return () => {
      show.remove();
      hide.remove();
    };
  }, []);
 
  return keyboardPadding;
};