Поставьте зависимости:
npm install react-native-reanimatedpnpm add react-native-reanimatedyarn add react-native-reanimatedbun add react-native-reanimatedСкопируйте эти файлы в src/shared/components/base/fade-component/:
Он импортирует ещё один модуль — astra-rn add кладёт их рядом сам, вручную скопируйте и их:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React, { useCallback, useMemo, useRef, useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { FadeComponent } from "@/shared/components/base/fade-component";
import type { TFadeState } from "@/shared/components/base/fade-component/types";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const PLANS = {
from: { label: "Monthly", price: "$12", cadence: "per month" },
to: { label: "Yearly", price: "$96", cadence: "per year" },
} as const;
const STATES: TFadeState[] = ["from", "to"];
const CUSTOMIZE = [
{ key: "fromLabel", label: "Вкладка 1", type: "text", default: PLANS.from.label, maxLength: 16 },
{ key: "fromPrice", label: "Цена 1", type: "text", default: PLANS.from.price, maxLength: 10 },
{ key: "fromCadence", label: "Период 1", type: "text", default: PLANS.from.cadence, maxLength: 20 },
{ key: "toLabel", label: "Вкладка 2", type: "text", default: PLANS.to.label, maxLength: 16 },
{ key: "toPrice", label: "Цена 2", type: "text", default: PLANS.to.price, maxLength: 10 },
{ key: "toCadence", label: "Период 2", type: "text", default: PLANS.to.cadence, maxLength: 20 },
{ key: "animation", label: "Анимация", type: "select", options: [{ value: "spring", label: "Пружина" }, { value: "timing", label: "Плавно" }], default: "spring" },
{ key: "duration", label: "Длительность", type: "slider", min: 100, max: 3000, step: 50, unit: "ms", default: 1000 },
{ key: "hiddenScale", label: "Масштаб скрытия", type: "slider", min: 0.5, max: 1, step: 0.01, default: 0.96 },
{ key: "priceColor", label: "Цвет цены", type: "color", default: "#FFFFFF" },
{ key: "accent", label: "Активная вкладка", type: "color", default: "#2A2A38" },
{ key: "holdMs", label: "Показ", type: "slider", min: 400, max: 5000, step: 50, unit: "ms", default: 1250 },
] as const;
type PlanView = { label: string; price: string; cadence: string };
const Plan: React.FC<{ plan: PlanView; priceColor: string }> = ({ plan, priceColor }) => (
<View style={styles.card}>
<Text style={[styles.price, { color: priceColor }]}>{plan.price}</Text>
<Text style={styles.cadence}>{plan.cadence}</Text>
</View>
);
const App: React.FC = (): React.ReactNode => {
const [state, setState] = useState<TFadeState>("from");
const custom = useCustomize(CUSTOMIZE);
const holdRef = useRef(custom.holdMs);
holdRef.current = custom.holdMs;
const plans = useMemo<Record<TFadeState, PlanView>>(
() => ({
from: { label: custom.fromLabel, price: custom.fromPrice, cadence: custom.fromCadence },
to: { label: custom.toLabel, price: custom.toPrice, cadence: custom.toCadence },
}),
[custom.fromLabel, custom.fromPrice, custom.fromCadence, custom.toLabel, custom.toPrice, custom.toCadence],
);
const onChange = useCallback((next: TFadeState): void => {
setState(next);
}, []);
useAutoplay(async ({ wait }) => {
await wait(400);
setState("to");
await wait(holdRef.current);
setState("from");
await wait(900);
});
return (
<Showcase>
<SafeAreaView style={styles.container}>
<FadeComponent
state={state}
animation={custom.animation as "spring" | "timing"}
duration={custom.duration}
onChange={onChange}
style={styles.fade}
>
<FadeComponent.From hiddenScale={custom.hiddenScale}>
<Plan plan={plans.from} priceColor={custom.priceColor} />
</FadeComponent.From>
<FadeComponent.To hiddenScale={custom.hiddenScale}>
<Plan plan={plans.to} priceColor={custom.priceColor} />
</FadeComponent.To>
</FadeComponent>
<View style={styles.segment}>
{STATES.map((value) => {
const active = state === value;
return (
<Pressable
key={value}
style={[styles.segmentItem, active && styles.segmentItemActive, active && { backgroundColor: custom.accent }]}
onPress={() => setState(value)}
>
<Text
style={[
styles.segmentText,
active && styles.segmentTextActive,
]}
>
{plans[value].label}
</Text>
</Pressable>
);
})}
</View>
</SafeAreaView>
</Showcase>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0a0a0f",
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 24,
},
fade: {
width: 260,
height: 160,
},
card: {
width: 260,
height: 160,
borderRadius: 20,
borderWidth: StyleSheet.hairlineWidth,
justifyContent: "center",
alignItems: "center",
},
price: {
fontSize: 40,
fontWeight: "700",
color: "#FFFFFF",
letterSpacing: -1,
},
cadence: {
marginTop: 6,
fontSize: 13,
color: "#7A7A8A",
},
segment: {
flexDirection: "row",
marginTop: 24,
padding: 4,
borderRadius: 999,
backgroundColor: "#16161F",
},
segmentItem: {
paddingHorizontal: 22,
paddingVertical: 10,
borderRadius: 999,
},
segmentItemActive: {
backgroundColor: "#2A2A38",
},
segmentText: {
fontSize: 13,
fontWeight: "600",
color: "#7A7A8A",
},
segmentTextActive: {
color: "#FFFFFF",
},
});
export default App;childrenТип ReactNode
duration?Duration in ms for the `timing` animation. @default 1000
Тип number
delay?Delay in ms before the animation starts. @default 0
Тип number
easing?Easing for the `timing` animation.
Тип EasingFunction | EasingFunctionFactory
animation?Drive the crossfade with timing or spring. @default "timing"
Тип TFadeAnimation
springConfig?Spring config used when `animation` is `"spring"`.
Тип WithSpringConfig
state?Controlled face — when provided the component mirrors this value.
Тип TFadeState
defaultState?Uncontrolled starting face. @default "from"
Тип TFadeState
onChange?Fired when a transition is requested.
Тип (state: TFadeState) => void
onAnimationEnd?Fired when a transition settles.
Тип (state: TFadeState) => void
disabled?Ignore imperative calls and controlled updates. @default false
Тип boolean
style?Style applied to the wrapping container.
Тип StyleProp<ViewStyle>
blockHiddenTouches?Prevent touches from reaching the face that is fading out. @default true
Тип boolean
testID?Тип string