• Экраны
  • Доки
1Online
ПодпискаКаталог
  • Экраны
  • Доки
1Online
ПодпискаКаталог
  • Экраны
  • Доки
1Online
ПодпискаКаталог
Пустая галерея — экран из раздела «Пустой экран»
Пустой экран

Пустая галерея

Что показывает список, почта или галерея, пока в них пусто.

Установка

src/shared/components/blocks/empty-gallary-v1
npx astra-rn add empty-gallary-v1
pnpm dlx astra-rn add empty-gallary-v1
yarn dlx astra-rn add empty-gallary-v1
bunx --bun astra-rn add empty-gallary-v1

Зависимости

1 пакет
npm install expo-asset
pnpm add expo-asset
yarn add expo-asset
bun add expo-asset

Исходник

4 файла
import React, { memo, useCallback, useMemo, useState } from "react";
import {
  Image,
  Pressable,
  StyleSheet,
  Text,
  View,
  type ImageSourcePropType,
} from "react-native";
import type { IEmptyGalleryState } from "./types";
import {
  ACTION_HEIGHT,
  ACTION_HORIZONTAL_PADDING,
  CARD_BORDER_WIDTH,
  CARD_HEIGHT,
  CARD_LAYOUTS,
  CARD_RADIUS,
  CARD_WIDTH,
  COLORS,
  DEFAULT_ACTION_LABEL,
  DEFAULT_DESCRIPTION,
  DEFAULT_PHOTOS,
  DEFAULT_TITLE,
  STACK_HEIGHT,
  STACK_WIDTH,
} from "./const";

const EmptyGalleryState: React.FC<IEmptyGalleryState> = ({
  title = DEFAULT_TITLE,
  description = DEFAULT_DESCRIPTION,
  actionLabel = DEFAULT_ACTION_LABEL,
  photos,
  hideAction = false,
  style,
  onActionPress,
}: IEmptyGalleryState): React.ReactNode & React.ReactElement => {
  const [isActionPressed, setIsActionPressed] = useState<boolean>(false);

  const handlePressIn = useCallback((): void => setIsActionPressed(true), []);
  const handlePressOut = useCallback((): void => setIsActionPressed(false), []);

  const cards = useMemo<(ImageSourcePropType | null)[]>(() => {
    const sources: ImageSourcePropType[] =
      photos ?? DEFAULT_PHOTOS.map((uri) => ({ uri }));
    return CARD_LAYOUTS.map((_, index) => sources[index] ?? null);
  }, [photos]);

  return (
    <View style={[styles.container, style]}>
      <View style={styles.stack}>
        {CARD_LAYOUTS.map((layout, index) => {
          const source = cards[index];
          return (
            <View
              key={`${layout.rotate}-${layout.translateX}`}
              style={[
                styles.card,
                {
                  transform: [
                    { translateX: layout.translateX },
                    { translateY: layout.translateY },
                    { rotate: layout.rotate },
                  ],
                },
              ]}
            >
              {source ? (
                <Image
                  source={source}
                  style={styles.photo}
                  resizeMode="cover"
                  accessibilityIgnoresInvertColors
                />
              ) : (
                <View style={[styles.photo, styles.placeholder]} />
              )}
            </View>
          );
        })}
      </View>

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

      {hideAction ? null : (
        <Pressable
          accessibilityRole="button"
          accessibilityLabel={actionLabel}
          onPress={onActionPress}
          onPressIn={handlePressIn}
          onPressOut={handlePressOut}
          style={[styles.action, isActionPressed ? styles.actionPressed : null]}
        >
          <Text style={styles.actionLabel} numberOfLines={1}>
            {actionLabel}
          </Text>
        </Pressable>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    paddingHorizontal: 24,
    backgroundColor: COLORS.screen,
  },
  stack: {
    width: STACK_WIDTH,
    height: STACK_HEIGHT,
    alignItems: "center",
    justifyContent: "center",
  },
  card: {
    position: "absolute",
    width: CARD_WIDTH,
    height: CARD_HEIGHT,
    borderRadius: CARD_RADIUS,
    borderWidth: CARD_BORDER_WIDTH,
    borderColor: COLORS.card,
    backgroundColor: COLORS.card,
    shadowColor: "#000000",
    shadowOpacity: 0.16,
    shadowRadius: 12,
    shadowOffset: { width: 0, height: 5 },
    elevation: 6,
  },
  photo: {
    flex: 1,
    borderRadius: CARD_RADIUS - CARD_BORDER_WIDTH,
  },
  placeholder: {
    backgroundColor: COLORS.placeholder,
  },
  title: {
    alignSelf: "stretch",
    marginTop: 40,
    fontSize: 22,
    lineHeight: 33,
    fontWeight: "500",
    letterSpacing: -0.5,
    color: COLORS.ink,
    textAlign: "center",
  },
  description: {
    alignSelf: "stretch",
    marginTop: 12,
    fontSize: 16,
    fontWeight: "500",
    lineHeight: 24,
    letterSpacing: -0.3,
    color: COLORS.body,
    textAlign: "center",
  },
  action: {
    marginTop: 34,
    height: ACTION_HEIGHT,
    paddingHorizontal: ACTION_HORIZONTAL_PADDING,
    borderRadius: ACTION_HEIGHT / 2,
    backgroundColor: COLORS.action,
    alignItems: "center",
    justifyContent: "center",
    overflow: "hidden",
  },
  actionPressed: {
    backgroundColor: COLORS.actionPressed,
  },
  actionLabel: {
    fontSize: 18,
    fontWeight: "700",
    letterSpacing: -0.3,
    color: COLORS.actionLabel,
  },
});

export { EmptyGalleryState };
export type { ICardLayout, IEmptyGalleryState } from "./types";
export default memo(EmptyGalleryState);
import type { ICardLayout } from "./types";
import { PHOTO_SOURCES } from "./photos";

export const DEFAULT_TITLE = "Add Photos to your walk";
export const DEFAULT_DESCRIPTION =
  "A space for your sunset, tree and flower photos.";
export const DEFAULT_ACTION_LABEL = "Add Photos";
export const DEFAULT_PHOTOS = [
  PHOTO_SOURCES["empty-gallary-v1-1"],
  PHOTO_SOURCES["empty-gallary-v1-2"],
  PHOTO_SOURCES["empty-gallary-v1-3"],
  PHOTO_SOURCES["empty-gallary-v1-4"],
] as const;

export const STACK_WIDTH = 268;
export const STACK_HEIGHT = 212;
export const CARD_WIDTH = 132;
export const CARD_HEIGHT = 132;
export const CARD_RADIUS = 25;
export const CARD_BORDER_WIDTH = 4;
export const CARD_LAYOUTS: ICardLayout[] = [
  { rotate: "-16deg", translateX: -52, translateY: 6 },
  { rotate: "-5deg", translateX: -16, translateY: -35 },
  { rotate: "13deg", translateX: 66, translateY: 0 },
  { rotate: "-1deg", translateX: 0, translateY: 34 },
];
export const ACTION_HEIGHT = 50;
export const ACTION_HORIZONTAL_PADDING = 22;
export const COLORS = {
  screen: "#fbfbfb",
  card: "#ffffff",
  placeholder: "#ececee",
  ink: "#0b0b0c",
  body: "#111113",
  action: "#0b0b0c",
  actionPressed: "#333336",
  actionLabel: "#ffffff",
} as const;
import type { ImageSourcePropType, StyleProp, ViewStyle } from "react-native";

export interface ICardLayout {
  rotate: string;
  translateX: number;
  translateY: number;
}

export interface IEmptyGalleryState {
  title?: string;
  description?: string;
  actionLabel?: string;
  photos?: ImageSourcePropType[];
  hideAction?: boolean;
  style?: StyleProp<ViewStyle>;
  onActionPress?: () => void;
}
import { Asset } from "expo-asset";

const uri = (id: number): string => Asset.fromModule(id).uri;

export const PHOTO_SOURCES = {
  "empty-gallary-v1-1": uri(require("./empty-gallary-v1-1.webp")),
  "empty-gallary-v1-2": uri(require("./empty-gallary-v1-2.webp")),
  "empty-gallary-v1-3": uri(require("./empty-gallary-v1-3.webp")),
  "empty-gallary-v1-4": uri(require("./empty-gallary-v1-4.webp")),
};

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

  • src/shared/components/blocks/empty-gallary-v1/photos/empty-gallary-v1-1.webp71 КБСкачать
  • src/shared/components/blocks/empty-gallary-v1/photos/empty-gallary-v1-2.webp128 КБСкачать
  • src/shared/components/blocks/empty-gallary-v1/photos/empty-gallary-v1-3.webp70 КБСкачать
  • src/shared/components/blocks/empty-gallary-v1/photos/empty-gallary-v1-4.webp31 КБСкачать

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

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

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