Поставьте зависимости:
npm install @sbaiahmed1/react-native-blur expo-haptics react-native-reanimatedpnpm add @sbaiahmed1/react-native-blur expo-haptics react-native-reanimatedyarn add @sbaiahmed1/react-native-blur expo-haptics react-native-reanimatedbun add @sbaiahmed1/react-native-blur expo-haptics react-native-reanimatedСкопируйте эти файлы в src/shared/components/base/flip-card/:
Он импортирует ещё один модуль — astra-rn add кладёт их рядом сам, вручную скопируйте и их:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React, { useMemo, useRef, useState, type FC } from "react";
import { FlipCard } from "@/shared/components/base/flip-card";
import { LinearGradient } from "expo-linear-gradient";
import { StyleSheet, View, Text, StatusBar } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { useFonts } from "expo-font";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "name", label: "Название", type: "text", default: "Beachfront Villa", maxLength: 28 },
{ key: "location", label: "Место", type: "text", default: "Malibu, California", maxLength: 32 },
{ key: "price", label: "Цена", type: "text", default: "$385", maxLength: 10 },
{ key: "rating", label: "Рейтинг", type: "text", default: "4.92", maxLength: 6 },
{ key: "gradient1", label: "Градиент 1", type: "color", default: "#FF385C" },
{ key: "gradient2", label: "Градиент 2", type: "color", default: "#E31C5F" },
{ key: "gradient3", label: "Градиент 3", type: "color", default: "#BD1E59" },
{ key: "accent", label: "Кнопка брони", type: "color", default: "#FF385C" },
{ key: "animation", label: "Переворот", type: "select", options: [{ value: "horizontal", label: "Гориз." }, { value: "vertical", label: "Верт." }, { value: "depth", label: "Глубина" }], default: "horizontal" },
{ key: "size", label: "Размер", type: "slider", min: 240, max: 360, step: 5, unit: "px", default: 320 },
{ key: "radius", label: "Скругление", type: "slider", min: 0, max: 48, step: 1, unit: "px", default: 16 },
{ key: "stiffness", label: "Жёсткость", type: "slider", min: 40, max: 400, step: 5, default: 120 },
{ key: "damping", label: "Затухание", type: "slider", min: 4, max: 40, step: 1, default: 12 },
{ key: "holdMs", label: "Показ", type: "slider", min: 500, max: 5000, step: 50, unit: "ms", default: 1350 },
] as const;
export default function App<T extends FC<{}>>():
| (React.ReactNode & React.JSX.Element & React.ReactElement)
| null {
const [fontLoaded] = useFonts({
SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),
});
const [flipped, setFlipped] = useState<boolean>(false);
const custom = useCustomize(CUSTOMIZE);
const holdRef = useRef(custom.holdMs);
holdRef.current = custom.holdMs;
const springConfig = useMemo(
() => ({ mass: 0.8, stiffness: custom.stiffness, damping: custom.damping }),
[custom.stiffness, custom.damping],
);
const frontColors = useMemo(
() => [custom.gradient1, custom.gradient2, custom.gradient3] as const,
[custom.gradient1, custom.gradient2, custom.gradient3],
);
useAutoplay(async ({ wait }) => {
await wait(450);
setFlipped(true);
await wait(holdRef.current);
setFlipped(false);
await wait(700);
});
if (!fontLoaded) return null;
return (
<Showcase>
<SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="light-content" />
<FlipCard
width={custom.size}
height={custom.size}
animationDuration={700}
animation={custom.animation as "horizontal" | "vertical" | "depth"}
borderRadius={custom.radius}
enableHaptics
scaleOnPress={Boolean(true)}
blurTint="dark"
blurIntensity={10}
isFlipped={flipped}
onFlip={setFlipped}
springConfig={springConfig}
>
<FlipCard.Front>
<LinearGradient
style={styles.gradient}
colors={frontColors}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
<View style={styles.frontContent}>
<View style={styles.topRow}>
<View style={styles.guestFavorite}>
<Text style={styles.guestFavoriteText}>Guest favorite</Text>
</View>
<View style={styles.rating}>
<Text style={styles.star}>★</Text>
<Text style={styles.ratingText}>{custom.rating}</Text>
</View>
</View>
<View style={styles.mainContent}>
<Text style={styles.propertyType}>Entire home</Text>
<Text style={styles.propertyName}>{custom.name}</Text>
<Text style={styles.location}>{custom.location}</Text>
</View>
<View style={styles.bottomRow}>
<View style={styles.priceBlock}>
<Text style={styles.price}>{custom.price}</Text>
<Text style={styles.perNight}>night</Text>
</View>
<Text style={styles.tapHint}>Tap for details</Text>
</View>
</View>
</FlipCard.Front>
<FlipCard.Back>
<LinearGradient
style={styles.gradient}
colors={["#222222", "#1a1a1a"]}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
/>
<View style={styles.backContent}>
<Text style={styles.backTitle}>What this place offers</Text>
<View style={styles.amenitiesGrid}>
<View style={styles.amenityItem}>
<Text style={styles.amenityText}>Ocean view</Text>
</View>
<View style={styles.amenityItem}>
<Text style={styles.amenityText}>Pool</Text>
</View>
<View style={styles.amenityItem}>
<Text style={styles.amenityText}>Fast wifi</Text>
</View>
<View style={styles.amenityItem}>
<Text style={styles.amenityText}>Free parking</Text>
</View>
</View>
<View style={styles.divider} />
<View style={styles.statsRow}>
<View style={styles.stat}>
<Text style={styles.statNumber}>4</Text>
<Text style={styles.statLabel}>guests</Text>
</View>
<View style={styles.stat}>
<Text style={styles.statNumber}>2</Text>
<Text style={styles.statLabel}>bedrooms</Text>
</View>
<View style={styles.stat}>
<Text style={styles.statNumber}>2</Text>
<Text style={styles.statLabel}>baths</Text>
</View>
</View>
<View style={[styles.reserveButton, { backgroundColor: custom.accent }]}>
<Text style={styles.reserveText}>Reserve</Text>
</View>
</View>
</FlipCard.Back>
<FlipCard.Trigger asChild={false} />
</FlipCard>
</SafeAreaView>
</Showcase>
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "#0a0a0a",
alignItems: "center",
justifyContent: "center",
},
gradient: {
...StyleSheet.absoluteFillObject,
},
frontContent: {
flex: 1,
justifyContent: "space-between",
padding: 28,
},
topRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
guestFavorite: {
backgroundColor: "rgba(255, 255, 255, 0.95)",
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 20,
},
guestFavoriteText: {
fontFamily: "SfProRounded",
fontSize: 12,
fontWeight: "600",
color: "#222222",
},
rating: {
flexDirection: "row",
alignItems: "center",
gap: 4,
},
star: {
fontSize: 14,
color: "#ffffff",
},
ratingText: {
fontFamily: "SfProRounded",
fontSize: 15,
fontWeight: "600",
color: "#ffffff",
},
mainContent: {
gap: 6,
},
propertyType: {
fontFamily: "SfProRounded",
fontSize: 14,
color: "rgba(255, 255, 255, 0.7)",
textTransform: "uppercase",
letterSpacing: 1,
},
propertyName: {
fontFamily: "SfProRounded",
fontSize: 36,
fontWeight: "700",
color: "#ffffff",
letterSpacing: -0.5,
},
location: {
fontFamily: "SfProRounded",
fontSize: 17,
color: "rgba(255, 255, 255, 0.85)",
},
bottomRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-end",
},
priceBlock: {
flexDirection: "row",
alignItems: "baseline",
gap: 5,
},
price: {
fontFamily: "SfProRounded",
fontSize: 32,
fontWeight: "700",
color: "#ffffff",
},
perNight: {
fontFamily: "SfProRounded",
fontSize: 16,
color: "rgba(255, 255, 255, 0.8)",
},
tapHint: {
fontFamily: "SfProRounded",
fontSize: 13,
color: "rgba(255, 255, 255, 0.5)",
},
backContent: {
flex: 1,
padding: 28,
justifyContent: "space-between",
},
backTitle: {
fontFamily: "SfProRounded",
fontSize: 20,
fontWeight: "600",
color: "#ffffff",
},
amenitiesGrid: {
flexDirection: "row",
flexWrap: "wrap",
gap: 12,
marginTop: 10,
},
amenityItem: {
width: "47%",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 12,
backgroundColor: "rgba(255, 255, 255, 0.08)",
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 12,
},
amenityIcon: {
fontSize: 20,
},
amenityText: {
fontFamily: "SfProRounded",
fontSize: 12,
color: "#ffffff",
},
divider: {
height: 0.5,
backgroundColor: "rgba(255, 255, 255, 0.1)",
marginTop: 10,
},
statsRow: {
flexDirection: "row",
justifyContent: "space-around",
},
stat: {
alignItems: "center",
gap: 4,
},
statNumber: {
fontFamily: "SfProRounded",
fontSize: 16,
fontWeight: "600",
color: "#ffffff",
},
statLabel: {
fontFamily: "SfProRounded",
fontSize: 11.5,
color: "rgba(255, 255, 255, 0.6)",
},
reserveButton: {
backgroundColor: "#FF385C",
paddingVertical: 12,
borderRadius: 12,
marginTop: 12,
alignItems: "center",
},
reserveText: {
fontFamily: "SfProRounded",
fontSize: 12,
fontWeight: "600",
color: "#ffffff",
},
});width?Тип number
height?Тип number
borderRadius?Тип number
blurIntensity?Тип number
containerStyle?Тип StyleProp<ViewStyle>
blurTint?Тип BlurType
animationDuration?Approximate settle time of the flip spring, in ms.
Тип number
springConfig?Тип WithSpringConfig
animation?Тип TFlipCardAnimation
enableHaptics?Тип boolean
onFlip?Тип (isFlipped: boolean) => void
scaleOnPress?Тип boolean
isFlipped?Controlled mode: when provided, the card mirrors this value.
Тип boolean
defaultFlipped?Тип boolean
isFlippedТип boolean
flipТип () => void
flipToТип (flipped: boolean) => void
widthТип number
heightТип number
borderRadiusТип number
blurIntensityТип number
animationТип TFlipCardAnimation
progress0 = front facing, 1 = back facing.
Тип SharedValue<number>
scaleТип SharedValue<number>
tintТип BlurType
scaleEnabledТип boolean