Компоненты/Стопка аватаров

Стопка аватаров

27 сентября 2026

Аватары лежат внахлёст и веером разъезжаются по нажатию

Установка

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

Файлы

index.tsx
НазадКластер аватаровДалееИзогнутая лента

Люди передаются массивом people вида { id, name, image?, color? }; без картинки рисуются инициалы на цветном круге. Нажатие раскрывает стопку в ряд с пружинами по очереди и подписями под аватарами, повторное — собирает обратно.

На web аватар под курсором приподнимается. На iOS и Android наведения нет — работает только нажатие.

Вручную

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

npm install react-native-reanimated react-native-gesture-handler expo-status-bar
pnpm add react-native-reanimated react-native-gesture-handler expo-status-bar
yarn add react-native-reanimated react-native-gesture-handler expo-status-bar
bun add react-native-reanimated react-native-gesture-handler expo-status-bar

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

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

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

import { useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";

import { AvatarStack, type IAvatarStackPerson } from "@/shared/components/molecules/avatar-stack";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";

const CUSTOMIZE = [
  { key: "theme", label: "Тема", type: "select", options: [{ value: "dark", label: "Тёмная" }, { value: "light", label: "Светлая" }], default: "dark" },
  { key: "size", label: "Размер", type: "slider", min: 32, max: 64, step: 1, unit: "px", default: 46 },
  { key: "max", label: "Видно в стопке", type: "slider", min: 2, max: 8, step: 1, default: 4 },
  { key: "overlap", label: "Нахлёст", type: "slider", min: 0, max: 0.6, step: 0.02, default: 0.34 },
  { key: "showNames", label: "Имена", type: "toggle", default: true },
  { key: "background", label: "Фон", type: "color", default: "#0A0A0A" },
] as const;

const PEOPLE: IAvatarStackPerson[] = [
  { id: "p1", name: "Анна Ким" },
  { id: "p2", name: "Илья Соколов" },
  { id: "p3", name: "Мария Лебедева" },
  { id: "p4", name: "Олег Зуев" },
  { id: "p5", name: "Дина Орлова" },
  { id: "p6", name: "Павел Рудин" },
  { id: "p7", name: "Вера Нестерова" },
  { id: "p8", name: "Тимур Алиев" },
];

export default function App() {
  const custom = useCustomize(CUSTOMIZE);
  const [expanded, setExpanded] = useState(false);
  const theme = custom.theme === "light" ? "light" : "dark";
  const background = theme === "light" && custom.background === "#0A0A0A" ? "#F5F7FA" : custom.background;

  useAutoplay(async ({ wait }) => {
    setExpanded(false);
    await wait(1300);
    setExpanded(true);
    await wait(2600);
    setExpanded(false);
    await wait(900);
  });

  return (
    <Showcase>
      <GestureHandlerRootView style={[styles.container, { backgroundColor: background }]}>
        <StatusBar style={theme === "light" ? "dark" : "light"} />
        <View style={styles.content}>
          <Text style={[styles.caption, { color: theme === "light" ? "#6D7480" : "#9A958A" }]}>
            {expanded ? "Команда проекта" : "8 участников"}
          </Text>
          <AvatarStack
            people={PEOPLE}
            max={custom.max}
            size={custom.size}
            overlap={custom.overlap}
            showNames={custom.showNames}
            expanded={expanded}
            onExpandedChange={setExpanded}
            ringColor={background}
            theme={theme}
          />
        </View>
      </GestureHandlerRootView>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#0A0A0A" },
  content: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 20, gap: 18 },
  caption: { fontSize: 13, fontWeight: "500", letterSpacing: -0.1 },
});

Пропсы

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

Тип readonly IAvatarStackPerson[]

max?

Тип number

size?

Тип number

overlap?

Тип number

expanded?

Тип boolean

defaultExpanded?

Тип boolean

onExpandedChange?

Тип (expanded: boolean) => void

showNames?

Тип boolean

ringColor?

Тип string

theme?

Тип TAvatarStackTheme

style?

Тип StyleProp<ViewStyle>