Карточки с фото выстроены веером по дуге: крайние наклонены и чуть опущены. При наведении или касании карточка приподнимается и увеличивается, а соседние расступаются. По нажатию выбранная карточка пружинно вырастает в крупный просмотр с подписью, а остальные собираются в ряд миниатюр под ней; повторное нажатие или нажатие мимо возвращают веер.
Над любой карточкой можно повесить парящий бейдж. Уже mobileBreakpoint (по умолчанию 640) вместо веера показывается колода, которую листают свайпом. Анимация идёт на UI-потоке Reanimated, при «Уменьшении движения» бейджи не парят, а переходы становятся мягкими. Работает на iOS, Android и в вебе.
Поставьте зависимости:
npm install expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletspnpm add expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsyarn add expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsbun add expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsСкопируйте эти файлы в src/shared/components/organisms/hero-card-stack/: Для узких экранов компонент использует «Колоду карточек» — скопируйте и её в src/shared/components/organisms/swipe-card-deck/.
Он импортирует ещё один модуль — astra-rn add кладёт их рядом сам, вручную скопируйте и их:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React from "react";
import { StyleSheet, View } from "react-native";
import { HeroCardStack } from "@/shared/components/organisms/hero-card-stack";
import { PHOTO_SOURCES } from "~/assets/photos";
import { Showcase, useCustomize } from "~/showcase";
const ALTS = [
"Бег по цветущему лугу",
"Огни вечернего мегаполиса",
"Крепость на острове",
"Горный хребет",
"Домики у подножия гор",
"Храм на Красной площади",
"Закат на морском берегу",
];
const CUSTOMIZE = [
{ key: "cardWidth", label: "Ширина карточки", type: "slider", min: 120, max: 240, step: 4, unit: "px", default: 176 },
{ key: "cardHeight", label: "Высота карточки", type: "slider", min: 120, max: 280, step: 4, unit: "px", default: 176 },
{ key: "overlap", label: "Перекрытие", type: "slider", min: 0, max: 120, step: 2, unit: "px", default: 48 },
{ key: "maxRotation", label: "Наклон крайних", type: "slider", min: 0, max: 30, step: 1, unit: "°", default: 14 },
{ key: "hint", label: "Подсказка", type: "text", default: "Нажмите на карточку", maxLength: 40 },
{ key: "badgeLabel", label: "Бейдж над центральной", type: "text", default: "", maxLength: 20 },
{ key: "badgeColor", label: "Цвет бейджа", type: "color", default: "#66d9e8" },
{
key: "photos",
label: "Фото",
type: "images",
count: 7,
default: [
PHOTO_SOURCES["circular-list-2"],
PHOTO_SOURCES["cinematic-carousel-1"],
PHOTO_SOURCES["cinematic-carousel-3"],
PHOTO_SOURCES["vertical-flow-carousel-1"],
PHOTO_SOURCES["cinematic-carousel-2"],
PHOTO_SOURCES["cinematic-carousel-4"],
PHOTO_SOURCES["photo-stack-1"],
],
},
] as const;
export default function HeroCardStackScreen() {
const custom = useCustomize(CUSTOMIZE);
const items = custom.photos.map((src, index) => ({ src, alt: ALTS[index] }));
const badges = custom.badgeLabel.trim()
? [{ cardIndex: Math.floor(items.length / 2), label: custom.badgeLabel, color: custom.badgeColor }]
: undefined;
return (
<Showcase>
<View style={styles.stage}>
<HeroCardStack
items={items}
badges={badges}
cardWidth={custom.cardWidth}
cardHeight={custom.cardHeight}
overlap={custom.overlap}
maxRotation={custom.maxRotation}
hint={custom.hint}
/>
</View>
</Showcase>
);
}
const styles = StyleSheet.create({
stage: {
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#0a0a0a",
},
});itemsТип readonly IHeroCardStackItem[]
badges?Тип readonly IHeroCardStackBadge[]
cardWidth?Тип number
cardHeight?Тип number
overlap?Тип number
maxRotation?Тип number
hint?Тип string | null
mobileBreakpoint?Тип number
style?Тип StyleProp<ViewStyle>