Поставьте зависимости:
npm install expo-blur react-native-reanimatedpnpm add expo-blur react-native-reanimatedyarn add expo-blur react-native-reanimatedbun add expo-blur react-native-reanimatedСкопируйте эти файлы в src/shared/components/organisms/radial-intro/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import { StyleSheet, Text, View } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { useMemo, useRef, useState } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { RadialIntro, OrbitItem } from "@/shared/components/organisms/radial-intro";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
import { PHOTO_SOURCES } from "~/assets/photos";
const ORBIT_ITEMS: OrbitItem[] = [
{
id: 1,
src: PHOTO_SOURCES["radial-intro-1"],
},
{
id: 2,
src: PHOTO_SOURCES["radial-intro-2"],
},
{
id: 3,
src: PHOTO_SOURCES["radial-intro-3"],
},
{
id: 4,
src: PHOTO_SOURCES["radial-intro-4"],
},
{
id: 5,
src: PHOTO_SOURCES["radial-intro-5"],
},
];
const CUSTOMIZE = [
{ key: "photos", label: "Изображения", type: "images", default: ORBIT_ITEMS.map((item) => item.src) },
{ key: "stageSize", label: "Размер сцены", type: "slider", min: 260, max: 420, step: 2, unit: "px", default: 400 },
{ key: "imageSize", label: "Размер фото", type: "slider", min: 50, max: 140, step: 1, unit: "px", default: 90 },
{ key: "spinDuration", label: "Период вращения", type: "slider", min: 2, max: 40, step: 1, unit: "s", default: 12 },
{ key: "revealOnFanOut", label: "Показ при раскрытии", type: "toggle", default: false },
{ key: "background", label: "Фон", type: "color", default: "#0a0a0a" },
] as const;
export default function App() {
const custom = useCustomize(CUSTOMIZE);
const orbitItems = useMemo(
() => ORBIT_ITEMS.map((item, index) => ({ ...item, src: custom.photos[index] ?? item.src })),
[custom.photos],
);
const insets = useSafeAreaInsets();
const [expanded, setExpanded] = useState(false);
const expandedRef = useRef(expanded);
expandedRef.current = expanded;
useAutoplay(async ({ wait }) => {
if (!expandedRef.current) {
await wait(550);
setExpanded(true);
}
await wait(5900);
setExpanded(false);
await wait(450);
});
return (
<Showcase>
<GestureHandlerRootView
style={[styles.container, { paddingBottom: insets.top, backgroundColor: custom.background }]}
>
<StatusBar style="light" />
<View style={styles.card}>
<RadialIntro
orbitItems={orbitItems}
expanded={expanded}
stageSize={custom.stageSize}
revealOnFanOut={custom.revealOnFanOut}
imageSize={custom.imageSize}
spinDuration={custom.spinDuration}
onCenterPress={() => setExpanded((v) => !v)}
/>
</View>
</GestureHandlerRootView>
</Showcase>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0a0a0a",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
},
card: {},
caption: {
marginTop: 18,
fontSize: 13,
color: "rgba(255,255,255,0.55)",
letterSpacing: -0.2,
},
});orbitItemsТип readonly OrbitItem[]
stageSize?Тип number
imageSize?Тип number
spinDuration?Тип number
expanded?Тип boolean
onCenterPress?Тип () => void
revealOnFanOut?Тип boolean
style?Тип StyleProp<ViewStyle>