Компоненты/Стопка уведомлений

Стопка уведомлений

28 сентября 2026

Стопка карточек-уведомлений, которая раскрывается в полный список и снова сворачивается в компактную стопку.

Установка

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

Файлы

index.tsx
НазадВидеоплеер с субтитрамиДалееКарусель-веер

Уведомления сложены в компактную стопку: видна верхняя карточка, а из-под неё выглядывают края следующих. Касание верхней карточки или кнопки «Показать все» пружинно раскладывает стопку в список, карточки разъезжаются с небольшой задержкой друг за другом. Кнопка «Скрыть» собирает их обратно.

Высота карточек измеряется через onLayout, поэтому в стопку можно положить любое содержимое. Карточка со ссылкой href открывает её через Linking. Анимации идут на UI-потоке Reanimated, а при «Уменьшении движения» стопка раскрывается без пружин. Работает на iOS, Android и в вебе.

Вручную

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

npm install react-native-reanimated react-native-svg
pnpm add react-native-reanimated react-native-svg
yarn add react-native-reanimated react-native-svg
bun add react-native-reanimated react-native-svg

Скопируйте эти файлы в src/shared/components/molecules/card-stack/:

Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.

Использование

import React, { useMemo } from "react";
import { StyleSheet, View } from "react-native";
import { CardStack, DEFAULT_CARDS } from "@/shared/components/molecules/card-stack";
import { Showcase, useCustomize } from "~/showcase";

const CUSTOMIZE = [
  { key: "width", label: "Ширина", type: "slider", min: 260, max: 560, step: 4, unit: "px", default: 384 },
  { key: "count", label: "Количество карточек", type: "slider", min: 1, max: DEFAULT_CARDS.length, step: 1, default: DEFAULT_CARDS.length },
  { key: "peekOffset", label: "Выступ в стопке", type: "slider", min: 0, max: 24, step: 1, unit: "px", default: 10 },
  { key: "peekScale", label: "Уменьшение в стопке", type: "slider", min: 0, max: 0.15, step: 0.01, default: 0.05 },
  { key: "gap", label: "Отступ в списке", type: "slider", min: 0, max: 24, step: 1, unit: "px", default: 8 },
  { key: "firstTitle", label: "Заголовок первой", type: "text", default: DEFAULT_CARDS[0]!.title, maxLength: 60 },
  { key: "showLabel", label: "Кнопка раскрытия", type: "text", default: "Показать все", maxLength: 24 },
  { key: "hideLabel", label: "Кнопка сворачивания", type: "text", default: "Скрыть", maxLength: 24 },
] as const;

export default function CardStackScreen() {
  const custom = useCustomize(CUSTOMIZE);
  const cards = useMemo(
    () =>
      DEFAULT_CARDS.slice(0, custom.count).map((card, index) =>
        index === 0 ? { ...card, title: custom.firstTitle } : card,
      ),
    [custom.count, custom.firstTitle],
  );

  return (
    <Showcase>
      <View style={styles.container}>
        <View style={styles.stage}>
          <CardStack
            cards={cards}
            width={custom.width}
            peekOffset={custom.peekOffset}
            peekScale={custom.peekScale}
            gap={custom.gap}
            showLabel={custom.showLabel}
            hideLabel={custom.hideLabel}
          />
        </View>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#0a0a0a",
  },
  stage: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    paddingHorizontal: 16,
  },
});

Пропсы

Пропс
Описание
cards?

Тип readonly ICardItem[]

width?

Тип number

peekOffset?

Тип number

peekScale?

Тип number

gap?

Тип number

showLabel?

Тип string

hideLabel?

Тип string

style?

Тип StyleProp<ViewStyle>