• Экраны
  • Доки
1Online
ПодпискаКаталог
  • Экраны
  • Доки
1Online
ПодпискаКаталог
  • Экраны
  • Доки
1Online
ПодпискаКаталог
Промо Airbnb · вариант — экран из раздела «Шторка»
Шторка

Промо Airbnb · вариант

Шторки, всплывающие над затемнением — формы, акции, оплата и вход.

Установка

src/shared/components/blocks/air-bnb-v2
npx astra-rn add air-bnb-v2
pnpm dlx astra-rn add air-bnb-v2
yarn dlx astra-rn add air-bnb-v2
bunx --bun astra-rn add air-bnb-v2

Зависимости

4 пакета
npm install @gorhom/bottom-sheet expo-router react-native-safe-area-context react-native-svg
pnpm add @gorhom/bottom-sheet expo-router react-native-safe-area-context react-native-svg
yarn add @gorhom/bottom-sheet expo-router react-native-safe-area-context react-native-svg
bun add @gorhom/bottom-sheet expo-router react-native-safe-area-context react-native-svg

Исходник

3 файла
import React, { memo, useCallback, useMemo, useRef, useState } from "react";
import { Image, Pressable, StyleSheet, Text, View } from "react-native";
import { useFocusEffect } from "expo-router";
import BottomSheet, {
  BottomSheetBackdrop,
  BottomSheetView,
  type BottomSheetBackdropProps,
} from "@gorhom/bottom-sheet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Svg, { Path } from "react-native-svg";
import type { IAirBnbBottomSheetNotice, ICloseIcon } from "./types";
import {
  ARTWORK_SIZE,
  BACKDROP_OPACITY,
  COLORS,
  CONTENT_BOTTOM_PADDING,
  CONTENT_HORIZONTAL_PADDING,
  DEFAULT_ACTION_LABEL,
  DEFAULT_ARTWORK,
  DEFAULT_TITLE,
  SHEET_CORNER_RADIUS,
} from "./const";

const CloseIcon: React.FC<ICloseIcon> = ({
  size = 18,
  color = COLORS.ink,
}: ICloseIcon) => (
  <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
    <Path
      d="M6 6L18 18M18 6L6 18"
      stroke={color}
      strokeWidth={2}
      strokeLinecap="round"
    />
  </Svg>
);

const AirBnbBottomSheetNotice: React.FC<IAirBnbBottomSheetNotice> = ({
  title = DEFAULT_TITLE,
  actionLabel = DEFAULT_ACTION_LABEL,
  artwork = DEFAULT_ARTWORK,
  hideCloseButton = false,
  openOnFocus = true,
  onActionPress,
  onClose,
}: IAirBnbBottomSheetNotice) => {
  const sheetRef = useRef<BottomSheet>(null);
  const insets = useSafeAreaInsets();
  const [isMounted, setIsMounted] = useState<boolean>(openOnFocus);
  const [isClosePressed, setIsClosePressed] = useState<boolean>(false);
  const [isActionPressed, setIsActionPressed] = useState<boolean>(false);

  useFocusEffect(
    useCallback(() => {
      if (openOnFocus) setIsMounted(true);
      return () => {
        sheetRef.current?.close();
        setIsMounted(false);
      };
    }, [openOnFocus]),
  );

  const handleClose = useCallback((): void => {
    sheetRef.current?.close();
  }, []);

  const handleChange = useCallback(
    (index: number): void => {
      if (index !== -1) return;
      setIsMounted(false);
      onClose?.();
    },
    [onClose],
  );

  const handleClosePressIn = useCallback(
    (): void => setIsClosePressed(true),
    [],
  );
  const handleClosePressOut = useCallback(
    (): void => setIsClosePressed(false),
    [],
  );

  const handleActionPressIn = useCallback(
    (): void => setIsActionPressed(true),
    [],
  );
  const handleActionPressOut = useCallback(
    (): void => setIsActionPressed(false),
    [],
  );

  const renderBackdrop = useCallback(
    (props: BottomSheetBackdropProps) => (
      <BottomSheetBackdrop
        {...props}
        appearsOnIndex={0}
        disappearsOnIndex={-1}
        opacity={BACKDROP_OPACITY}
        pressBehavior="close"
      />
    ),
    [],
  );

  const contentStyle = useMemo(
    () => [
      styles.content,
      { paddingBottom: CONTENT_BOTTOM_PADDING + insets.bottom },
    ],
    [insets.bottom],
  );

  if (!isMounted) return null;

  return (
    <BottomSheet
      ref={sheetRef}
      index={0}
      enablePanDownToClose
      enableDynamicSizing
      onChange={handleChange}
      backdropComponent={renderBackdrop}
      handleComponent={null}
      backgroundStyle={styles.background}
      style={styles.sheet}
    >
      <BottomSheetView style={contentStyle}>
        {hideCloseButton ? null : (
          <Pressable
            accessibilityRole="button"
            accessibilityLabel="Close"
            hitSlop={12}
            onPress={handleClose}
            onPressIn={handleClosePressIn}
            onPressOut={handleClosePressOut}
            style={[
              styles.closeButton,
              isClosePressed ? styles.closeButtonPressed : null,
            ]}
          >
            <CloseIcon />
          </Pressable>
        )}

        <View style={styles.header}>
          <Image
            source={artwork}
            style={styles.artwork}
            resizeMode="contain"
            accessibilityIgnoresInvertColors
          />

          <Text style={styles.title}>{title}</Text>
        </View>

        <Pressable
          accessibilityRole="button"
          accessibilityLabel={actionLabel}
          onPress={onActionPress}
          onPressIn={handleActionPressIn}
          onPressOut={handleActionPressOut}
          style={[styles.action, isActionPressed ? styles.actionPressed : null]}
        >
          <Text style={styles.actionLabel} numberOfLines={1}>
            {actionLabel}
          </Text>
        </Pressable>
      </BottomSheetView>
    </BottomSheet>
  );
};

const styles = StyleSheet.create({
  sheet: {
    shadowColor: "#000000",
    shadowOpacity: 0.18,
    shadowRadius: 24,
    shadowOffset: { width: 0, height: -6 },
    elevation: 16,
  },
  background: {
    backgroundColor: COLORS.sheet,
    borderTopLeftRadius: SHEET_CORNER_RADIUS,
    borderTopRightRadius: SHEET_CORNER_RADIUS,
  },
  content: {
    alignSelf: "stretch",
    paddingHorizontal: CONTENT_HORIZONTAL_PADDING,
    paddingTop: 4,
  },
  closeButton: {
    position: "absolute",
    top: 12,
    right: 14,
    zIndex: 10,
    width: 34,
    height: 34,
    borderRadius: 17,
    alignItems: "center",
    justifyContent: "center",
  },
  closeButtonPressed: {
    backgroundColor: COLORS.closePressed,
  },
  header: {
    alignItems: "center",
    paddingTop: 22,
  },
  artwork: {
    width: ARTWORK_SIZE,
    height: ARTWORK_SIZE,
  },
  title: {
    alignSelf: "center",
    marginTop: 34,
    fontSize: 21,
    maxWidth: 260,
    lineHeight: 28,
    fontWeight: "700",
    letterSpacing: -0.4,
    color: COLORS.ink,
    textAlign: "center",
  },
  action: {
    alignSelf: "stretch",
    marginTop: 30,
    height: 52,
    borderRadius: 10,
    backgroundColor: COLORS.action,
    alignItems: "center",
    justifyContent: "center",
    overflow: "hidden",
  },
  actionPressed: {
    backgroundColor: COLORS.actionPressed,
  },
  actionLabel: {
    fontSize: 16,
    fontWeight: "600",
    letterSpacing: -0.2,
    color: COLORS.actionLabel,
  },
});

export { AirBnbBottomSheetNotice };
export type { IAirBnbBottomSheetNotice, ICloseIcon } from "./types";
export default memo(AirBnbBottomSheetNotice);
export const DEFAULT_ARTWORK = require("./price-tag.webp");

export const DEFAULT_TITLE =
  "Now you'll see one price for your trip, all fees included.";

export const DEFAULT_ACTION_LABEL = "Got it";

export const SHEET_CORNER_RADIUS = 38;

export const BACKDROP_OPACITY = 0.4;

export const CONTENT_BOTTOM_PADDING = 20;

export const CONTENT_HORIZONTAL_PADDING = 22;

export const ARTWORK_SIZE = 144;

export const COLORS = {
  sheet: "#f7f4ef",
  ink: "#1c1b19",
  action: "#2b2a28",
  actionPressed: "#454340",
  actionLabel: "#ffffff",
  closePressed: "#e9e5dd",
} as const;
import type { ImageSourcePropType } from "react-native";

export interface ICloseIcon {
  size?: number;
  color?: string;
}

export interface IAirBnbBottomSheetNotice {
  title?: string;

  actionLabel?: string;

  artwork?: ImageSourcePropType;

  hideCloseButton?: boolean;

  openOnFocus?: boolean;

  onActionPress?: () => void;

  onClose?: () => void;
}

Ещё 1 файл с картинками или шрифтами. astra-rn add скачивает их сам, вручную положите их по этим путям:

  • src/shared/components/blocks/air-bnb-v2/price-tag.webp34 КБСкачать

ПРИСОЕДИНЯЙТЕСЬ

ПолучитезначокAstra Proужесегодня

ASTRAPRO
Дизайн и разработка — ©Astra