
Что показывает список, почта или галерея, пока в них пусто.
npx astra-rn add empty-gallary-v1pnpm dlx astra-rn add empty-gallary-v1yarn dlx astra-rn add empty-gallary-v1bunx --bun astra-rn add empty-gallary-v1npm install expo-assetpnpm add expo-assetyarn add expo-assetbun add expo-assetimport 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 скачивает их сам, вручную положите их по этим путям: