Компоненты/Веер карточек

Веер карточек

28 сентября 2026

Дуга из перекрывающихся карточек: при касании карточка выпрыгивает вперёд, а по нажатию раскрывается крупно; на вебе выбор — наведением.

Установка

npx astra-rn add hero-card-stack
pnpm dlx astra-rn add hero-card-stack
yarn dlx astra-rn add hero-card-stack
bunx --bun astra-rn add hero-card-stack

Файлы

index.tsx
НазадГрадиентный аватарДалееВыезд медиа в герое

Карточки с фото выстроены веером по дуге: крайние наклонены и чуть опущены. При наведении или касании карточка приподнимается и увеличивается, а соседние расступаются. По нажатию выбранная карточка пружинно вырастает в крупный просмотр с подписью, а остальные собираются в ряд миниатюр под ней; повторное нажатие или нажатие мимо возвращают веер.

Над любой карточкой можно повесить парящий бейдж. Уже mobileBreakpoint (по умолчанию 640) вместо веера показывается колода, которую листают свайпом. Анимация идёт на UI-потоке Reanimated, при «Уменьшении движения» бейджи не парят, а переходы становятся мягкими. Работает на iOS, Android и в вебе.

Вручную

Поставьте зависимости:

npm install expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets
pnpm add expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets
yarn add expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets
bun 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>