Компоненты/Лента событий

Лента событий

28 сентября 2026

Живая лента событий, в которой новые карточки появляются сверху и сдвигают остальные вниз.

Установка

npx astra-rn add activity-feed
pnpm dlx astra-rn add activity-feed
yarn dlx astra-rn add activity-feed
bunx --bun astra-rn add activity-feed

Файлы

index.tsx
НазадРельс действийДалееОбложка альбома

Список последних событий, который обновляется сам: через заданный интервал сверху выезжает новая карточка с лёгким масштабом, остальные пружинно сдвигаются вниз, а нижняя растворяется. Низ списка мягко уходит в прозрачность через градиентную маску.

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

Вручную

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

npm install react-native-reanimated expo-linear-gradient @react-native-masked-view/masked-view
pnpm add react-native-reanimated expo-linear-gradient @react-native-masked-view/masked-view
yarn add react-native-reanimated expo-linear-gradient @react-native-masked-view/masked-view
bun add react-native-reanimated expo-linear-gradient @react-native-masked-view/masked-view

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

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

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

import React, { useMemo } from "react";
import { StyleSheet, View } from "react-native";
import Svg, { Circle, Line, Path, Rect } from "react-native-svg";

import { ActivityFeed, type IActivityFeedItem } from "@/shared/components/molecules/activity-feed";
import { Showcase, useCustomize } from "~/showcase";

const ICON = { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" } as const;

const CreditCard = ({ color }: { color: string }) => (
  <Svg {...ICON} stroke={color}>
    <Rect width={20} height={14} x={2} y={5} rx={2} />
    <Line x1={2} x2={22} y1={10} y2={10} />
  </Svg>
);

const UserPlus = ({ color }: { color: string }) => (
  <Svg {...ICON} stroke={color}>
    <Path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
    <Circle cx={9} cy={7} r={4} />
    <Line x1={19} x2={19} y1={8} y2={14} />
    <Line x1={22} x2={16} y1={11} y2={11} />
  </Svg>
);

const Rocket = ({ color }: { color: string }) => (
  <Svg {...ICON} stroke={color}>
    <Path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" />
    <Path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09" />
    <Path d="M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z" />
    <Path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05" />
  </Svg>
);

const ShieldCheck = ({ color }: { color: string }) => (
  <Svg {...ICON} stroke={color}>
    <Path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
    <Path d="m9 12 2 2 4-4" />
  </Svg>
);

const MessageSquare = ({ color }: { color: string }) => (
  <Svg {...ICON} stroke={color}>
    <Path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" />
  </Svg>
);

const Download = ({ color }: { color: string }) => (
  <Svg {...ICON} stroke={color}>
    <Path d="M12 15V3" />
    <Path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
    <Path d="m7 10 5 5 5-5" />
  </Svg>
);

const ITEMS: IActivityFeedItem[] = [
  {
    icon: <CreditCard color="#34d399" />,
    iconBackground: "rgba(16,185,129,0.15)",
    title: "Платёж получен",
    description: "4 900 ₽ от ООО «Вектор»",
    time: "сейчас",
  },
  {
    icon: <UserPlus color="#66d9e8" />,
    iconBackground: "rgba(102,217,232,0.15)",
    title: "Новый участник",
    description: "Анна Смирнова в команде",
    time: "1 мин",
  },
  {
    icon: <Rocket color="#66d9e8" />,
    iconBackground: "rgba(102,217,232,0.15)",
    title: "Деплой завершён",
    description: "astra-web · продакшен",
    time: "2 мин",
  },
  {
    icon: <ShieldCheck color="#fbbf24" />,
    iconBackground: "rgba(245,158,11,0.15)",
    title: "Проверка безопасности",
    description: "Уязвимостей: 0",
    time: "4 мин",
  },
  {
    icon: <MessageSquare color="#fb7185" />,
    iconBackground: "rgba(244,63,94,0.15)",
    title: "Новое упоминание",
    description: "#общий · «выкатываем 🚀»",
    time: "6 мин",
  },
  {
    icon: <Download color="#d4d4d8" />,
    iconBackground: "rgba(113,113,122,0.15)",
    title: "Отчёт выгружен",
    description: "выручка-Q3.csv · 2,4 МБ",
    time: "9 мин",
  },
];

const CUSTOMIZE = [
  { key: "interval", label: "Интервал событий", type: "slider", min: 600, max: 5000, step: 100, unit: "мс", default: 1800 },
  { key: "visible", label: "Карточек на экране", type: "slider", min: 2, max: 6, step: 1, default: 5 },
  { key: "gap", label: "Отступ между карточками", type: "slider", min: 0, max: 28, step: 1, unit: "px", default: 12 },
  { key: "radius", label: "Скругление карточек", type: "slider", min: 0, max: 28, step: 1, unit: "px", default: 16 },
  { key: "title", label: "Заголовок первого события", type: "text", default: "Платёж получен", maxLength: 40 },
  { key: "description", label: "Описание первого события", type: "text", default: "4 900 ₽ от ООО «Вектор»", maxLength: 50 },
  { key: "showTime", label: "Показывать время", type: "toggle", default: true },
] as const;

export default function ActivityFeedScreen() {
  const custom = useCustomize(CUSTOMIZE);
  const items = useMemo(
    () =>
      ITEMS.map((item, index) => ({
        ...item,
        title: index === 0 ? custom.title : item.title,
        description: index === 0 ? custom.description : item.description,
        time: custom.showTime ? item.time : undefined,
      })),
    [custom.title, custom.description, custom.showTime],
  );

  return (
    <Showcase>
      <View style={styles.page}>
        <View style={styles.frame}>
          <ActivityFeed
            key={custom.visible}
            items={items}
            visible={custom.visible}
            interval={custom.interval}
            gap={custom.gap}
            radius={custom.radius}
            style={styles.feed}
          />
        </View>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  page: { flex: 1, backgroundColor: "#0a0a0a" },
  frame: { flex: 1, alignItems: "center", justifyContent: "center", paddingVertical: 24, paddingHorizontal: 16 },
  feed: { maxWidth: 576 },
});

Иконку события передайте в icon любым элементом, а цвет подложки — в iconBackground.

Пропсы

Пропс
Описание
items

Тип readonly IActivityFeedItem[]

visible?

Тип number

interval?

Тип number

gap?

Тип number

radius?

Тип number

style?

Тип StyleProp<ViewStyle>