react-native-safe-area-context#useSafeArea TypeScript Examples

The following examples show how to use react-native-safe-area-context#useSafeArea. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: calculator.tsx    From protect-scotland with Apache License 2.0 6 votes vote down vote up
CalculatorModal: FC<CalculatorModalProps> = () => {
  const {t} = useTranslation();
  const insets = useSafeArea();
  const navigation = useNavigation<StackNavigationProp<any>>();

  return (
    <ScrollView
      keyboardShouldPersistTaps="always"
      style={styles.container}
      contentContainerStyle={[
        styles.contentContainer,
        {paddingBottom: insets.bottom + SPACING_BOTTOM}
      ]}>
      <ModalHeader
        heading="calculator:heading"
        color="amber"
        onClosePress={() => navigation.goBack()}
      />
      <View style={styles.top}>
        <Illustration
          source={CalculatorIllustration}
          accessibilityIgnoresInvertColors={false}
          accessibilityHint={t('calculator:illustrationAlt')}
          accessibilityLabel={t('calculator:illustrationAlt')}
        />
        <Markdown markdownStyles={markdownStyles}>
          {t('calculator:body')}
        </Markdown>
        <Spacing s={18} />
        <Text style={styles.highlight}>{t('calculator:highlight')}</Text>
        <Spacing s={50} />
      </View>
    </ScrollView>
  );
}
Example #2
Source File: test-result-modal.tsx    From protect-scotland with Apache License 2.0 5 votes vote down vote up
TestResultModal: React.FC = () => {
  const {t} = useTranslation();
  const navigation = useNavigation();
  const insets = useSafeArea();
  return (
    <View
      style={[
        styles.modal,
        {marginTop: insets.top + (Platform.OS === 'android' ? 25 : 5)}
      ]}>
      <View style={styles.header}>
        <ModalClose onPress={() => navigation.goBack()} />
      </View>
      <ScrollView contentContainerStyle={styles.contentContainerStyle}>
        <Text
          variant="h2"
          color="darkerPurple"
          accessible
          style={styles.narrow}
          align="center">
          {t('onboarding:testResult:accessibility:howToAddResultLabel')}
        </Text>
        <Spacing s={20} />
        <Illustration source={ModalIllustrationSource} />
        <Spacing s={20} />
        <Markdown markdownStyles={modalMarkdownStyles} style={styles.narrow}>
          {t('onboarding:testResult:modal:content')}
        </Markdown>
        <Illustration
          fullWidth
          source={
            Platform.OS === 'ios' ? IosMessageSource : AndroidMessageSource
          }
          accessible
          accessibilityHint={t(
            'onboarding:testResult:accessibility:messageIllustrationAlt'
          )}
        />
        <Spacing s={32} />
        <Markdown markdownStyles={modalMarkdownStyles} style={styles.narrow}>
          {t('onboarding:testResult:modal:content1')}
        </Markdown>
        <ArrowLink
          containerStyle={styles.narrow}
          externalLink={t('links:a')}
          fullWidth
          accessibilityHint={t('onboarding:testResult:modal:linkHint')}>
          <Text variant="h4" color="primaryPurple">
            {t('onboarding:testResult:modal:link')}
          </Text>
        </ArrowLink>
        <Spacing s={50} />
      </ScrollView>
    </View>
  );
}
Example #3
Source File: index.tsx    From SQUID with MIT License 5 votes vote down vote up
MainApp = () => {
  const inset = useSafeArea()
  const { qrData, qrState, error, refreshQR } = useSelfQR()
  const appVersion = DeviceInfo.getVersion();

  const [location, setLocation] = useState('')
  const getBeacon = async () => {
    //TEST
    // let lc = 'ห้าง Tesco lotus สาขาอโศก ตรงข้ามห้างดัง ตรงรถไฟฟ้ามหานครอมรรัตน'
    // AsyncStorage.setItem('beacon-location', lc);
    let beacon = await AsyncStorage.getItem('beacon-location');
    if (beacon) {
      setLocation(beacon)
    }
  }

  useEffect(() => {
    pushNotification.requestPermissions()
    getBeacon()
  }, [])

  return (
    <View
      style={[styles.container, { paddingTop: inset.top, paddingBottom: 12 }]}
    >
      <StatusBar
        barStyle={qrData?.getTagColor() ? 'light-content' : 'dark-content'}
        backgroundColor={qrData?.getTagColor() ? COLORS.BLACK_1 : COLORS.PRIMARY_LIGHT}
      />
      <QRBackground qr={qrData} />
      <QRAvatar qr={qrData} qrState={qrState} />
      <QRTagLabel qr={qrData} />
      <Beancon location={location} />
      <QRHeader qr={qrData} qrState={qrState} onRefreshQR={refreshQR} />
      <QRSection qr={qrData} qrState={qrState} onRefreshQR={refreshQR} />
      <QRFooter />
      <Text
          style={{
            position: 'absolute',
            bottom: 0,
            right: 0,
            paddingRight: 5,
            fontFamily: FONT_FAMILY,
            fontSize: FONT_SIZES[500] * 0.85,
            textAlign: 'right',
            color: '#0FA7DC'
          }}
        >
        V {appVersion}
      </Text>
    </View>
  )
}
Example #4
Source File: BottomSheet.tsx    From mobile with Apache License 2.0 4 votes vote down vote up
BottomSheet = ({content: ContentComponent, collapsed: CollapsedComponent, extraContent}: BottomSheetProps) => {
  const bottomSheetPosition = useRef(new Animated.Value(1));
  const bottomSheetRef: React.Ref<BottomSheetRaw> = useRef(null);
  const [isExpanded, setIsExpanded] = useState(false);
  const [i18n] = useI18n();
  const toggleExpanded = useCallback(() => {
    if (isExpanded) {
      bottomSheetRef.current?.snapTo(1);
    } else {
      bottomSheetRef.current?.snapTo(0);
    }
  }, [isExpanded]);

  const insets = useSafeArea();
  const renderHeader = useCallback(() => <Box height={insets.top} />, [insets.top]);

  const onOpenEnd = useCallback(() => setIsExpanded(true), []);
  const onCloseEnd = useCallback(() => setIsExpanded(false), []);

  const {width, height} = useWindowDimensions();
  const snapPoints = [height, Math.max(width, height) * (extraContent ? 0.3 : 0.2)];

  // Need to add snapPoints to set enough height when BottomSheet is collapsed
  useEffect(() => {
    bottomSheetRef.current?.snapTo(isExpanded ? 0 : 1);
  }, [width, isExpanded, snapPoints]);

  const expandedContentWrapper = useMemo(
    () => (
      <Animated.View style={{opacity: abs(sub(bottomSheetPosition.current, 1))}}>
        <ContentComponent />
        <TouchableOpacity
          onPress={toggleExpanded}
          style={styles.collapseButton}
          accessibilityLabel={i18n.translate('BottomSheet.Collapse')}
          accessibilityRole="button"
        >
          <Icon name="icon-chevron" />
        </TouchableOpacity>
      </Animated.View>
    ),
    [i18n, toggleExpanded],
  );
  const collapsedContentWrapper = useMemo(
    () => (
      <Animated.View style={{...styles.collapseContent, opacity: pow(bottomSheetPosition.current, 2)}}>
        <View style={styles.collapseContentHandleBar}>
          <Icon name="sheet-handle-bar" />
        </View>
        {CollapsedComponent ? <CollapsedComponent /> : null}
      </Animated.View>
    ),
    [CollapsedComponent],
  );

  const renderContent = useCallback(() => {
    return (
      <SheetContentsContainer isExpanded={isExpanded} toggleExpanded={toggleExpanded}>
        <>
          {collapsedContentWrapper}
          {expandedContentWrapper}
        </>
      </SheetContentsContainer>
    );
  }, [collapsedContentWrapper, expandedContentWrapper, isExpanded, toggleExpanded]);

  return (
    <>
      <BottomSheetRaw
        ref={bottomSheetRef}
        borderRadius={32}
        enabledContentGestureInteraction
        renderContent={renderContent}
        onOpenEnd={onOpenEnd}
        onCloseEnd={onCloseEnd}
        renderHeader={renderHeader}
        snapPoints={snapPoints}
        initialSnap={1}
        callbackNode={bottomSheetPosition.current}
        enabledInnerScrolling
      />
      <Box height={snapPoints[1]} style={styles.spacer} />
    </>
  );
}
Example #5
Source File: sendNotice.tsx    From protect-scotland with Apache License 2.0 4 votes vote down vote up
SendNotice: FC<SendNoticeProps> = () => {
  const {t} = useTranslation();
  const insets = useSafeArea();
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(false);
  const navigation = useNavigation();
  const {contacts} = useExposure();
  const {noticesWebPageURL, isolationDuration} = useSettings();

  const handleOnSendNoticeRequest = useCallback(() => {
    const createNoticeApi = async (selfIsolationDate: string) => {
      try {
        const key = await getKey(selfIsolationDate);

        if (!key) {
          return setError(true);
        }

        setError(false);

        await SecureStore.setItemAsync(
          'createNoticeCertKey',
          JSON.stringify({key, selfIsolationDate})
        );

        const url = `${noticesWebPageURL}?key=${key}`;

        await WebBrowser.openBrowserAsync(url, {
          enableBarCollapsing: true,
          showInRecents: true
        });

        navigation.navigate(ScreenNames.closeContact);
      } catch (err) {
        console.log(err);
        setError(true);
      }
    };
    const selfIsolationEndDate = getIsolationEndDate(
      isolationDuration,
      contacts,
      'yyyy-MM-dd'
    );

    setIsLoading(true);

    if (selfIsolationEndDate && noticesWebPageURL) {
      createNoticeApi(selfIsolationEndDate.formatted);
    }

    if (!noticesWebPageURL) {
      setError(true);
    }
  }, [contacts, noticesWebPageURL, isolationDuration, navigation]);

  return (
    <ScrollView
      keyboardShouldPersistTaps="always"
      style={styles.container}
      contentContainerStyle={[
        styles.contentContainer,
        {paddingBottom: insets.bottom + SPACING_BOTTOM}
      ]}>
      <ModalHeader
        heading="sendNotice:heading"
        onClosePress={navigation.goBack}
      />
      <View style={styles.top}>
        <Illustration
          source={SendNoticeIllustration}
          accessibilityIgnoresInvertColors={false}
        />
        <Markdown markdownStyles={markdownStyles}>
          {t('sendNotice:body')}
        </Markdown>
        <Spacing s={42} />
        {error && (
          <>
            <View style={styles.errorBox}>
              <Text style={styles.errorText}>
                {t('common:tryAgain:description')}
              </Text>
            </View>
            <Spacing s={20} />
          </>
        )}
        <Button
          variant="dark"
          onPress={handleOnSendNoticeRequest}
          label={t('sendNotice:continueLabel')}
          hint={t('sendNotice:continueHint')}
          disabled={isLoading}
          textColor="white"
          style={styles.button}>
          {t('common:continue')}
        </Button>
        <Spacing s={50} />
      </View>
    </ScrollView>
  );
}
Example #6
Source File: index.tsx    From SQUID with MIT License 4 votes vote down vote up
MainApp = () => {
  const inset = useSafeArea()
  const { qrData, qrState, refreshQR } = useSelfQR()
  const { beaconLocationName, enable, disable, isServiceEnabled } = useContactTracer()
  const appVersion = DeviceInfo.getVersion();
  const [location, setLocation] = useState('')
  const popupRef = useRef<NotificationPopup | any>()
  const smallDevice = Dimensions.get('window').height < 600

  useEffect(() => {
    setLocation(beaconLocationName.name)
    if (location && popupRef && popupRef.current) {
      popupRef.current.show({
        slideOutTime: 20 * 1000
      })
    }
  }, [beaconLocationName])

  useEffect(() => {
    pushNotification.requestPermissions()
  }, [])

  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#F9F9F9'}}>
      <View
        style={[styles.container, { paddingTop: inset.top, paddingBottom: inset.bottom }]}
      >
        <StatusBar
          barStyle={qrData?.getTagColor() ? 'light-content' : 'dark-content'}
          backgroundColor={qrData?.getTagColor() ? COLORS.BLACK_1 : COLORS.PRIMARY_LIGHT}
        />
        <View style={styles.containerTop}>
          <View style={styles.containerHeader}>
            <TouchableOpacity style={styles.circularButton} onPress={refreshQR}>
              <FontAwesome name="refresh" color={COLORS.GRAY_4} size={24} style={{ marginLeft: 10 }} />
            </TouchableOpacity>
            <Text style={styles.textHeader}>
              {qrData && (`${qrData.getCreatedDate().format(I18n.t('fully_date'))}`)}
            </Text>
            <TouchableOpacity onPress={() => {
              isServiceEnabled ? disable() : enable()
            }}>
              <FontAwesome
                name="bluetooth-b"
                color={COLORS.GRAY_4}
                size={24}
                style={{ marginRight: 10 }}
              />
              {isServiceEnabled ? (
                <View style={{
                  width: 10,
                  height: 10,
                  backgroundColor: COLORS.GREEN,
                  position: 'absolute',
                  borderRadius: 50,
                  borderTopWidth: Math.floor((4 / 100) * 24),
                  right: Math.floor((8 / 100) * 50)
                }} />
              ) : void 0}
            </TouchableOpacity>
          </View>
            <View style={styles.containerCard}>
              <View style={styles.card}>
                <View style={styles.cardHeader}>
                  <View style={{ flex: 1, padding: 10 }}>
                    <AvatarProfile qr={qrData} qrState={qrState} />
                  </View>
                  <View style={{ flex: 2, alignContent: 'flex-start' }}>
                    <RiskLabel qr={qrData} qrState={qrState} onRefreshQR={refreshQR} />
                  </View>
                </View>
                <View style={{ flex: 3 }}>
                  <QRImage qr={qrData} qrState={qrState} onRefreshQR={refreshQR} />
                </View>
                <View style={styles.cardFooter}>
                  <Image
                    source={require('./logo-pin-morchana.png')}
                    style={{
                      height: smallDevice ? 20 : 30,
                      width: (smallDevice ? 20 : 30) * (260 / 140),
                    }}
                    resizeMode="contain"
                  />
                  <Text style={styles.textVersion}>
                    แอปพลิเคชันหมอชนะ <Text style={{ color: '#0FA7DC', fontSize: FONT_SIZES[600] * 0.85, fontFamily: FONT_FAMILY }}>V{appVersion}</Text>
                  </Text>
                </View>
              </View>
            </View>
        </View>
        <NotificationPopup
          ref={popupRef}
          renderPopupContent={props => (
            <BeaconFoundPopupContent {...props} result={location} />
          )}
        />
      </View>
    </SafeAreaView>
  )
}
Example #7
Source File: QuestionaireForm.tsx    From SQUID with MIT License 4 votes vote down vote up
QuestionaireForm = ({ navigation }) => {
  const { showSpinner, hide } = useHUD()
  const [formValue, setFormValue] = useState({})
  const inset = useSafeArea()
  const [index, setIndex] = useState(0)
  const isFocused = useIsFocused()
  const dataInputTable = useMemo(() => getDataInputTable(), [])
  const di = dataInputTable[index]
  const value = formValue[di.id]

  const setValue = (value) => {
    setFormValue({ ...formValue, [di.id]: value })
  }

  const onBack = useCallback(() => {
    if (index === 0) {
      navigation.pop()
    } else {
      setIndex(index - 1)
    }
  }, [index])
  const footer = (
    <Footer style={{ paddingBottom: inset.bottom }}>
      <PrimaryButton
        title={I18n.t('next')}
        style={{
          width: '100%',
        }}
        containerStyle={{
          width: '100%',
        }}
        disabled={typeof value === 'undefined' || value?.length === 0}
        onPress={async () => {
          if (dataInputTable[index + 1]) {
            setIndex(index + 1)
          } else {
            showSpinner()
            await updateUserData({ questionaire: formValue })
            applicationState.setData('filledQuestionaireV2', true)
            navigation.navigate('QuestionaireSummary')
            hide()
          }
        }}
      />
    </Footer>
  )
  const fixedFooter = Dimensions.get('window').height > 700

  return (
    <Container>
      {isFocused ? <FormBackHandler onBack={onBack} /> : null}
      <StatusBar
        backgroundColor={COLORS.WHITE}
        barStyle="dark-content"
      />
      <FormHeader
        style={{ backgroundColor: 'white', paddingTop: inset.top }}
        onBack={onBack}
      >
        <View style={styles.header}>
          <Text style={styles.title}>{di.title}</Text>
          {di.subtitle ? (
            <Text style={styles.subtitle}>{di.subtitle}</Text>
          ) : (
            void 0
          )}
          <View
            style={{
              flexDirection: 'row',
              alignItems: 'center',
              marginTop: 16,
            }}
          >
            <Text
              style={{ fontFamily: FONT_BOLD, color:'#C4CCD4', marginRight: 12, fontSize: FONT_SIZES[500] }}
            >
              {index + 1} / {dataInputTable.length}
            </Text>
            <Progress
              height={8}
              style={{ flex: 1 }}
              progress={(index + 1) / dataInputTable.length}
            />
          </View>
        </View>
      </FormHeader>
      <ScrollView
        style={{
          flex: 1,
          borderTopColor: '#E3F4FF',
          borderTopWidth: 1,
        }}
        contentContainerStyle={{ justifyContent: 'center'  }}
      >
        {di ? (
          <FormDataInput
            key={di.id}
            di={di}
            setValue={setValue}
            value={value}
          />
        ) : null}
        {fixedFooter? null: footer}
      </ScrollView>
      {fixedFooter? footer: null}
    </Container>
  )
}
Example #8
Source File: BottomSheet.tsx    From mobile with Apache License 2.0 4 votes vote down vote up
BottomSheetInternal = (
  {expandedComponent: ExpandedComponent, collapsedComponent: CollapsedComponent}: BottomSheetProps,
  ref: React.Ref<BottomSheetBehavior>,
) => {
  const bottomSheetPosition = useRef(new Animated.Value(1));
  const bottomSheetRef: React.Ref<BottomSheetRaw> = useRef(null);

  const [isExpanded, setIsExpanded] = useState(false);
  const [extraContent, setExtraContent] = useState(false);
  const [onStateChange, setOnStateChange] = useState<(isExpanded: boolean) => void>();

  const behavior = useMemo<BottomSheetBehavior>(
    () => ({
      expand: () => {
        setIsExpanded(true);
      },
      collapse: () => {
        setIsExpanded(false);
      },
      refreshSnapPoints: setExtraContent,
      callbackNode: bottomSheetPosition.current,
      setOnStateChange: callback => setOnStateChange(() => callback),
      isExpanded,
    }),
    [isExpanded],
  );
  useImperativeHandle(ref, () => behavior, [behavior]);
  useEffect(() => onStateChange?.(isExpanded), [isExpanded, onStateChange]);
  const {orientation} = useOrientation();
  const bottomPadding = orientation === 'landscape' ? 120 : 140;
  const insets = useSafeArea();
  const renderHeader = useCallback(() => <Box height={insets.top} />, [insets.top]);

  const onOpenEnd = useCallback(() => setIsExpanded(true), []);
  const onCloseEnd = useCallback(() => setIsExpanded(false), []);

  const {width, height} = useWindowDimensions();
  const snapPoints = [height, extraContent ? 200 + insets.bottom : bottomPadding + insets.bottom];

  // Need to add snapPoints to set enough height when BottomSheet is collapsed
  useEffect(() => {
    bottomSheetRef.current?.snapTo(isExpanded ? 0 : 1);
  }, [width, isExpanded, snapPoints]);

  const expandedComponentWrapper = useMemo(() => <ExpandedComponent {...behavior} />, [behavior]);
  const collapsedComponentWrapper = useMemo(() => <CollapsedComponent {...behavior} />, [behavior]);

  const renderContent = useCallback(() => {
    return (
      <SheetContentsContainer>
        <>
          <View
            style={styles.collapseContent}
            accessibilityElementsHidden={isExpanded}
            importantForAccessibility={isExpanded ? 'no-hide-descendants' : undefined}
            pointerEvents={isExpanded ? 'none' : undefined}
          >
            {collapsedComponentWrapper}
          </View>
          <View
            pointerEvents={isExpanded ? undefined : 'none'}
            accessibilityElementsHidden={!isExpanded}
            importantForAccessibility={isExpanded ? undefined : 'no-hide-descendants'}
          >
            {expandedComponentWrapper}
          </View>
        </>
      </SheetContentsContainer>
    );
  }, [collapsedComponentWrapper, expandedComponentWrapper, isExpanded]);

  return (
    <>
      <BottomSheetRaw
        ref={bottomSheetRef}
        borderRadius={32}
        enabledContentGestureInteraction
        renderContent={renderContent}
        onOpenEnd={onOpenEnd}
        onCloseEnd={onCloseEnd}
        renderHeader={renderHeader}
        snapPoints={snapPoints}
        initialSnap={1}
        callbackNode={bottomSheetPosition.current}
        enabledInnerScrolling
      />
      <Box height={snapPoints[1]} />
    </>
  );
}
Example #9
Source File: BottomTabBar.tsx    From nlw2-proffy with MIT License 4 votes vote down vote up
export default function BottomTabBar({
  state,
  navigation,
  descriptors,
  activeBackgroundColor,
  activeTintColor,
  adaptive = true,
  allowFontScaling,
  inactiveBackgroundColor,
  inactiveTintColor,
  keyboardHidesTabBar = false,
  labelPosition,
  labelStyle,
  iconStyle,
  safeAreaInsets,
  showLabel,
  style,
  tabStyle,
}: Props) {
  const { colors } = useTheme();
  const buildLink = useLinkBuilder();

  const focusedRoute = state.routes[state.index];
  const focusedDescriptor = descriptors[focusedRoute.key];
  const focusedOptions = focusedDescriptor.options;

  const dimensions = useWindowDimensions();
  const isKeyboardShown = useIsKeyboardShown();

  const shouldShowTabBar =
    focusedOptions.tabBarVisible !== false &&
    !(keyboardHidesTabBar && isKeyboardShown);

  const visibilityAnimationConfigRef = React.useRef(
    focusedOptions.tabBarVisibilityAnimationConfig
  );

  React.useEffect(() => {
    visibilityAnimationConfigRef.current =
      focusedOptions.tabBarVisibilityAnimationConfig;
  });

  const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);

  const [visible] = React.useState(
    () => new Animated.Value(shouldShowTabBar ? 1 : 0)
  );

  React.useEffect(() => {
    const visibilityAnimationConfig = visibilityAnimationConfigRef.current;

    if (shouldShowTabBar) {
      const animation =
        visibilityAnimationConfig?.show?.animation === 'spring'
          ? Animated.spring
          : Animated.timing;

      animation(visible, {
        toValue: 1,
        useNativeDriver,
        duration: 250,
        ...visibilityAnimationConfig?.show?.config,
      }).start(({ finished }) => {
        if (finished) {
          setIsTabBarHidden(false);
        }
      });
    } else {
      setIsTabBarHidden(true);

      const animation =
        visibilityAnimationConfig?.hide?.animation === 'spring'
          ? Animated.spring
          : Animated.timing;

      animation(visible, {
        toValue: 0,
        useNativeDriver,
        duration: 200,
        ...visibilityAnimationConfig?.hide?.config,
      }).start();
    }
  }, [visible, shouldShowTabBar]);

  const [layout, setLayout] = React.useState({
    height: 0,
    width: dimensions.width,
  });

  const handleLayout = (e: LayoutChangeEvent) => {
    const { height, width } = e.nativeEvent.layout;

    setLayout((layout) => {
      if (height === layout.height && width === layout.width) {
        return layout;
      } else {
        return {
          height,
          width,
        };
      }
    });
  };

  const { routes } = state;
  const shouldUseHorizontalLabels = () => {
    if (labelPosition) {
      return labelPosition === 'beside-icon';
    }

    if (!adaptive) {
      return false;
    }

    if (layout.width >= 768) {
      // Screen size matches a tablet
      let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;

      const flattenedStyle = StyleSheet.flatten(tabStyle);

      if (flattenedStyle) {
        if (typeof flattenedStyle.width === 'number') {
          maxTabItemWidth = flattenedStyle.width;
        } else if (typeof flattenedStyle.maxWidth === 'number') {
          maxTabItemWidth = flattenedStyle.maxWidth;
        }
      }

      return routes.length * maxTabItemWidth <= layout.width;
    } else {
      const isLandscape = dimensions.width > dimensions.height;

      return isLandscape;
    }
  };

  const defaultInsets = useSafeArea();

  const insets = {
    top: safeAreaInsets?.top ?? defaultInsets.top,
    right: safeAreaInsets?.right ?? defaultInsets.right,
    bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
    left: safeAreaInsets?.left ?? defaultInsets.left,
  };

  const paddingBottom = Math.max(
    insets.bottom - Platform.select({ ios: 4, default: 0 }),
    0
  );

  return (
    <Animated.View
      style={[
        styles.tabBar,
        {
          backgroundColor: colors.card,
          borderTopColor: colors.border,
        },
        {
          transform: [
            {
              translateY: visible.interpolate({
                inputRange: [0, 1],
                outputRange: [layout.height + paddingBottom, 0],
              }),
            },
          ],
          // Absolutely position the tab bar so that the content is below it
          // This is needed to avoid gap at bottom when the tab bar is hidden
          position: isTabBarHidden ? 'absolute' : (null as any),
        },
        {
          height: DEFAULT_TABBAR_HEIGHT + paddingBottom,
          paddingBottom,
          paddingHorizontal: Math.max(insets.left, insets.right),
        },
        style,
      ]}
      pointerEvents={isTabBarHidden ? 'none' : 'auto'}
    >
      <View style={styles.content} onLayout={handleLayout}>
        {routes.map((route, index) => {
          const focused = index === state.index;
          const { options } = descriptors[route.key];

          const onPress = () => {
            const event = navigation.emit({
              type: 'tabPress',
              target: route.key,
              canPreventDefault: true,
            });

            if (!focused && !event.defaultPrevented) {
              navigation.dispatch({
                ...CommonActions.navigate(route.name),
                target: state.key,
              });
            }
          };

          const onLongPress = () => {
            navigation.emit({
              type: 'tabLongPress',
              target: route.key,
            });
          };

          const label =
            options.tabBarLabel !== undefined
              ? options.tabBarLabel
              : options.title !== undefined
              ? options.title
              : route.name;

          const accessibilityLabel =
            options.tabBarAccessibilityLabel !== undefined
              ? options.tabBarAccessibilityLabel
              : typeof label === 'string'
              ? `${label}, tab, ${index + 1} of ${routes.length}`
              : undefined;

          return (
            <NavigationContext.Provider
              key={route.key}
              value={descriptors[route.key].navigation}
            >
              <NavigationRouteContext.Provider value={route}>
                <BottomTabItem
                  route={route}
                  focused={focused}
                  horizontal={shouldUseHorizontalLabels()}
                  onPress={onPress}
                  onLongPress={onLongPress}
                  accessibilityLabel={accessibilityLabel}
                  to={buildLink(route.name, route.params)}
                  testID={options.tabBarTestID}
                  allowFontScaling={allowFontScaling}
                  activeTintColor={activeTintColor}
                  inactiveTintColor={inactiveTintColor}
                  activeBackgroundColor={activeBackgroundColor}
                  inactiveBackgroundColor={inactiveBackgroundColor}
                  button={options.tabBarButton}
                  icon={options.tabBarIcon}
                  badge={options.tabBarBadge}
                  label={label}
                  showLabel={showLabel}
                  labelStyle={labelStyle}
                  iconStyle={iconStyle}
                  style={tabStyle}
                />
              </NavigationRouteContext.Provider>
            </NavigationContext.Provider>
          );
        })}
      </View>
    </Animated.View>
  );
}
Example #10
Source File: Snackbar.tsx    From react-native-template with MIT License 4 votes vote down vote up
Snackbar = ({
  visible,
  message,
  onDismiss,
  btnTitle,
  onPress,
  unsafeView,
}: SnackbarProps) => {
  const timeoutRef = useRef(-1)
  const insets = useSafeArea()
  const safeArea = !unsafeView
    ? insets
    : { top: 0, bottom: 0, left: 0, right: 0 }
  const snackbarHeight =
    SNACKBAR_HEIGHT + safeArea.bottom + safeArea.bottom / 2 + 10
  const translateY = useValue(snackbarHeight)
  const opacity = useMemo(
    () =>
      timing({
        to: 1,
        from: 0.2,
        duration: 200,
      }),
    // eslint-disable-next-line
    [message]
  )

  useCode(
    () => [
      cond(
        bin(visible),
        set(
          translateY,
          timing({
            from: translateY,
            to: 0,
            duration: 250,
          })
        ),
        cond(
          neq(translateY, snackbarHeight),
          set(
            translateY,
            timing({
              from: translateY,
              to: snackbarHeight,
              duration: 150,
            })
          )
        )
      ),
    ],
    [visible, snackbarHeight, translateY]
  )

  useEffect(() => {
    if (visible) {
      timeoutRef.current = setTimeout(() => {
        onDismiss()
      }, 3000)
    }

    return clearTimeoutRef
  }, [onDismiss, visible])

  const clearTimeoutRef = () => clearTimeout(timeoutRef.current)

  const handleOnPress = () => {
    onDismiss()
    clearTimeout(timeoutRef.current)
    setTimeout(() => {
      onPress!()
    }, 150)
  }

  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.snackbar,
          {
            transform: [{ translateY }],
            height: snackbarHeight,
            backgroundColor: "#1D2226",
          },
        ]}
      >
        <Animated.Text
          style={[
            styles.text,
            {
              marginBottom: safeArea.bottom,
              opacity,
            },
          ]}
        >
          {message}
        </Animated.Text>
        {onPress && (
          <TouchableOpacity
            onPress={handleOnPress}
            style={styles.touchable}
            activeOpacity={0.6}
          >
            <Text
              style={[
                styles.dismissText,
                {
                  marginBottom: safeArea.bottom,
                  color: "#15AAE1",
                },
              ]}
            >
              {btnTitle}
            </Text>
          </TouchableOpacity>
        )}
        <TouchableOpacity
          onPress={onDismiss}
          style={styles.touchable}
          activeOpacity={0.6}
        >
          <Text
            style={[
              styles.dismissText,
              {
                marginBottom: safeArea.bottom,
                color: "#2E97C8",
              },
            ]}
          >
            Dismiss
          </Text>
        </TouchableOpacity>
      </Animated.View>
    </View>
  )
}