Люди передаются массивом people вида { id, name, image?, color? }; без картинки рисуются инициалы на цветном круге. Нажатие раскрывает стопку в ряд с пружинами по очереди и подписями под аватарами, повторное — собирает обратно.
На web аватар под курсором приподнимается. На iOS и Android наведения нет — работает только нажатие.
Поставьте зависимости:
npm install react-native-reanimated react-native-gesture-handler expo-status-barpnpm add react-native-reanimated react-native-gesture-handler expo-status-baryarn add react-native-reanimated react-native-gesture-handler expo-status-barbun 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>