Поставьте зависимости:
npm install @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-workletspnpm add @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-workletsyarn add @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-workletsbun add @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-workletsСкопируйте эти файлы в src/shared/components/organisms/dia-text/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React, { useCallback, useMemo } from "react";
import { LayoutChangeEvent, Platform, StyleSheet, Text, View } from "react-native";
import { DiaText } from "@/shared/components/organisms/dia-text";
import { Showcase, useCustomize } from "~/showcase";
import Animated, {
LinearTransition,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
const isWeb = Platform.OS === "web";
const CUSTOMIZE = [
{ key: "lead", label: "Начало фразы", type: "text", default: "Make every moment", maxLength: 40 },
{ key: "word1", label: "Слово 1", type: "text", default: "beautiful.", maxLength: 24 },
{ key: "word2", label: "Слово 2", type: "text", default: "effortless.", maxLength: 24 },
{ key: "word3", label: "Слово 3", type: "text", default: "memorable.", maxLength: 24 },
{ key: "baseColor", label: "Цвет текста", type: "color", default: "#f6f3ec" },
{ key: "fontSize", label: "Размер шрифта", type: "slider", min: 12, max: 40, step: 1, unit: "px", default: 20 },
{ key: "duration", label: "Длительность", type: "slider", min: 300, max: 4000, step: 50, unit: "ms", default: 1500 },
{ key: "loopDelay", label: "Пауза", type: "slider", min: 0, max: 3000, step: 50, unit: "ms", default: 900 },
{ key: "bandRatio", label: "Ширина блика", type: "slider", min: 0.1, max: 1, step: 0.05, default: 0.7 },
{ key: "sweep1", label: "Блик 1", type: "color", default: "#c679c4" },
{ key: "sweep2", label: "Блик 2", type: "color", default: "#fa3d1d" },
{ key: "sweep3", label: "Блик 3", type: "color", default: "#ffb005" },
{ key: "sweep4", label: "Блик 4", type: "color", default: "#e1e1fe" },
{ key: "sweep5", label: "Блик 5", type: "color", default: "#0358f7" },
{ key: "loop", label: "Повтор", type: "toggle", default: true },
] as const;
export default function DiaTextScreen() {
const custom = useCustomize(CUSTOMIZE);
const words = useMemo(
() => [custom.word1, custom.word2, custom.word3],
[custom.word1, custom.word2, custom.word3],
);
const sweepColors = useMemo(
() => [custom.sweep1, custom.sweep2, custom.sweep3, custom.sweep4, custom.sweep5],
[custom.sweep1, custom.sweep2, custom.sweep3, custom.sweep4, custom.sweep5],
);
const textStyle = useMemo(
() => [styles.word, { color: custom.baseColor, fontSize: custom.fontSize }],
[custom.baseColor, custom.fontSize],
);
const wordWidth = useSharedValue(0);
const onWordLayout = useCallback(
(e: LayoutChangeEvent) => {
const next = e.nativeEvent.layout.width;
if (Math.abs(next - wordWidth.value) < 0.5) return;
wordWidth.value =
wordWidth.value > 0 ? withTiming(next, { duration: 300 }) : next;
},
[wordWidth],
);
const wordSlotStyle = useAnimatedStyle(() =>
wordWidth.value > 0 ? { width: wordWidth.value } : {},
);
const word = (
<DiaText
loop={custom.loop}
loopDelay={custom.loopDelay}
duration={custom.duration}
text={words}
sweepColors={sweepColors}
baseColor={custom.baseColor}
bandRatio={custom.bandRatio}
textStyle={textStyle}
/>
);
return (
<Showcase>
<View style={styles.center}>
<Animated.View style={styles.headline} layout={LinearTransition}>
<Text style={textStyle}>{custom.lead}</Text>
{isWeb ? (
<Animated.View style={[styles.wordSlot, wordSlotStyle]}>
<View onLayout={onWordLayout}>{word}</View>
</Animated.View>
) : (
word
)}
</Animated.View>
</View>
</Showcase>
);
}
const styles = StyleSheet.create({
center: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 24,
},
headline: {
alignItems: "center",
gap: 6,
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center",
maxWidth: "100%",
},
wordSlot: {
flexDirection: "row",
},
word: {
color: "#f6f3ec",
fontSize: 20,
fontWeight: "300",
letterSpacing: -0.5,
},
});textТип string | readonly string[]
sweepColors?Тип readonly string[]
baseColor?Тип string
duration?Тип number
delay?Тип number
loop?Тип boolean
loopDelay?Тип number
bandRatio?Тип number
autoPlay?Тип boolean
textStyle?Тип StyleProp<TextStyle>
style?Тип StyleProp<ViewStyle>
onSweepEnd?Тип (index: number) => void