Поставьте зависимости:
npm install @expo/vector-icons expo-blur react-native-reanimatedpnpm add @expo/vector-icons expo-blur react-native-reanimatedyarn add @expo/vector-icons expo-blur react-native-reanimatedbun add @expo/vector-icons expo-blur react-native-reanimatedСкопируйте эти файлы в src/shared/components/micro-interactions/flexi-button/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import { useRef, useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { useFonts } from "expo-font";
import { SymbolView } from "expo-symbols";
import { Ionicons } from "@expo/vector-icons";
import { FlexiButton } from "@/shared/components/micro-interactions/flexi-button";
import { Showcase, useAutoplay, useCustomize, hexToRgb } from "~/showcase";
const CUSTOMIZE = [
{ key: "text", label: "Текст", type: "text", default: "Clear All", maxLength: 20 },
{ key: "icon", label: "Иконка", type: "select", options: [{ value: "notifications", label: "Колокол" }, { value: "trash", label: "Корзина" }, { value: "mail", label: "Почта" }, { value: "heart", label: "Сердце" }, { value: "share-social", label: "Поделиться" }], default: "notifications" },
{ key: "collapsedWidth", label: "Ширина свёрнутой", type: "slider", min: 32, max: 80, step: 1, unit: "px", default: 40 },
{ key: "expandedWidth", label: "Ширина раскрытой", type: "slider", min: 80, max: 240, step: 2, unit: "px", default: 120 },
{ key: "tint", label: "Цвет фона", type: "color", default: "#ffffff" },
{ key: "tintOpacity", label: "Прозрачность фона", type: "slider", min: 0, max: 1, step: 0.05, default: 0.1 },
{ key: "holdMs", label: "Показ", type: "slider", min: 300, max: 4000, step: 100, unit: "ms", default: 900 },
] as const;
function tintOf(hex: string, alpha: number) {
if (hex.toLowerCase() === "#ffffff" && alpha === 0.1) return "rgba(255, 255, 255, 0.1)";
const { r, g, b } = hexToRgb(hex);
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${alpha})`;
}
export default function App() {
const [fontLoaded] = useFonts({
SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),
HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),
Coolvetica: require("@/assets/fonts/Coolvetica-Rg.otf"),
});
const launchDate = new Date("2026-07-20T14:30:00");
const [expanded, setExpanded] = useState<boolean>(false);
const custom = useCustomize(CUSTOMIZE);
const holdRef = useRef(custom.holdMs);
holdRef.current = custom.holdMs;
useAutoplay(
async ({ wait }) => {
await wait(300);
setExpanded(true);
await wait(holdRef.current);
setExpanded(false);
await wait(500);
},
);
return (
<Showcase>
<View style={styles.container}>
<StatusBar style="light" />
<View style={styles.content}>
<FlexiButton
expanded={expanded}
onExpandedChange={setExpanded}
text={custom.text}
icon={custom.icon as "notifications"}
collapsedWidth={custom.collapsedWidth}
expandedWidth={custom.expandedWidth}
backgroundColor={tintOf(custom.tint, custom.tintOpacity)}
/>
</View>
</View>
</Showcase>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0a0a0a",
},
content: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
iconBox: {
width: 64,
height: 64,
borderRadius: 20,
backgroundColor: "#1a1a1a",
justifyContent: "center",
alignItems: "center",
marginBottom: 8,
},
label: {
fontSize: 14,
color: "#555",
textTransform: "uppercase",
letterSpacing: 2,
},
date: {
fontSize: 15,
color: "#333",
marginTop: 8,
},
});onPress?Тип () => void
collapsedWidth?Тип number
expandedWidth?Тип number
text?Тип string
icon?Тип IconName | IconRenderFn
onDimensionsChange?Тип (dimensions: Dimensions) => void
backgroundColor?Тип string
expanded?Тип boolean
onExpandedChange?Тип (expanded: boolean) => void
Необязательные expanded и onExpandedChange делают кнопку управляемой: раскрытием можно управлять из кода, анимация та же пружина. Без expanded кнопка сама хранит состояние, как раньше.