Поставьте зависимости:
npm install expo-linear-gradient react-native-reanimatedpnpm add expo-linear-gradient react-native-reanimatedyarn add expo-linear-gradient react-native-reanimatedbun add expo-linear-gradient react-native-reanimatedСкопируйте эти файлы в src/shared/components/base/button/:
Он импортирует ещё один модуль — astra-rn add кладёт их рядом сам, вручную скопируйте и их:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import { Platform, View, StyleSheet, Text } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { useFonts } from "expo-font";
import { Ionicons } from "@expo/vector-icons";
import { useCallback, useMemo, useState } from "react";
import { Button } from "@/shared/components/base/button";
import { CircularLoader } from "@/shared/components/molecules/circular-loader";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "label", label: "Надпись", type: "text", default: "Click Me!", maxLength: 30 },
{ key: "loadingLabel", label: "Надпись загрузки", type: "text", default: "Fetching…", maxLength: 30 },
{ key: "fill", label: "Цвет кнопки", type: "color", default: "#ffffff" },
{ key: "ink", label: "Цвет текста", type: "color", default: "#000000" },
{ key: "loadingFill", label: "Цвет загрузки", type: "color", default: "#cacaca" },
{ key: "width", label: "Ширина", type: "slider", min: 120, max: 320, step: 2, unit: "px", default: 200 },
{ key: "height", label: "Высота", type: "slider", min: 32, max: 80, step: 1, unit: "px", default: 48 },
{ key: "radius", label: "Скругление", type: "slider", min: 0, max: 40, step: 1, unit: "px", default: 16 },
{ key: "fontSize", label: "Размер шрифта", type: "slider", min: 12, max: 26, step: 1, unit: "px", default: 17 },
{ key: "loadingMs", label: "Загрузка", type: "slider", min: 500, max: 8000, step: 100, unit: "ms", default: 3000 },
{ key: "showIcon", label: "Иконка", type: "toggle", default: true },
{ key: "background", label: "Фон", type: "color", default: "#0a0a0a" },
] as const;
export default function App() {
const [fontLoaded] = useFonts({
SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),
});
const custom = useCustomize(CUSTOMIZE);
const [loading, setLoading] = useState<boolean>(false);
const onPress = useCallback(() => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, custom.loadingMs);
}, [custom.loadingMs]);
const [pressed, setPressed] = useState<boolean>(false);
useAutoplay(async ({ wait }) => {
await wait(400);
try {
setPressed(true);
await wait(120);
setPressed(false);
setLoading(true);
await wait(Math.max(0, custom.loadingMs - 100));
} finally {
setPressed(false);
setLoading(false);
}
await wait(300);
});
const fontReady = fontLoaded || Platform.OS !== "web";
const layers = useMemo(
() => (
<>
<Button.Content>
<View
style={[
styles.btn,
{ backgroundColor: custom.fill, borderRadius: custom.radius },
]}
>
{custom.showIcon ? (
<Ionicons
name="arrow-forward"
size={18}
color={custom.ink === "#000000" ? "black" : custom.ink}
/>
) : null}
<Text
style={[
styles.btnText,
{
fontFamily: fontLoaded ? "SfProRounded" : undefined,
color: custom.ink,
fontSize: custom.fontSize,
},
]}
>
{custom.label}
</Text>
</View>
</Button.Content>
<Button.Loading>
<Button.Indicator>
<CircularLoader
activeColor={custom.ink}
size={18}
strokeWidth={2.5}
enableBlur
gradientLength={50}
duration={500}
/>
</Button.Indicator>
<Button.Label
color={custom.ink === "#000000" ? "#000" : custom.ink}
style={{
fontFamily: fontLoaded ? "SfProRounded" : undefined,
}}
>
{custom.loadingLabel}
</Button.Label>
</Button.Loading>
</>
),
[
fontLoaded,
custom.fill,
custom.ink,
custom.radius,
custom.fontSize,
custom.showIcon,
custom.label,
custom.loadingLabel,
],
);
return (
<Showcase>
<View style={[styles.container, { backgroundColor: custom.background }]}>
<StatusBar style="light" />
<View style={fontReady ? undefined : styles.pending}>
<Button.Root
isLoading={loading}
isPressed={pressed}
onPress={onPress}
width={custom.width}
height={custom.height}
backgroundColor={custom.fill === "#ffffff" ? "#fff" : custom.fill}
loadingBackgroundColor={custom.loadingFill}
borderRadius={custom.radius === 16 ? undefined : custom.radius}
>
{layers}
</Button.Root>
</View>
</View>
</Showcase>
);
}
const styles = StyleSheet.create({
pending: {
opacity: 0,
},
container: {
flex: 1,
backgroundColor: "#0a0a0a",
alignItems: "center",
justifyContent: "center",
},
btn: {
flexDirection: "row",
alignItems: "center",
gap: 10,
backgroundColor: "#fff",
paddingVertical: 16,
paddingHorizontal: 32,
borderRadius: 16,
},
btnText: {
fontSize: 17,
fontWeight: "600",
color: "#000",
},
});children?Тип ReactNode
isLoading?Тип boolean
isPressed?Тип boolean
onPress?Тип () => void
width?Тип number
height?Тип number
backgroundColor?Тип string
loadingBackgroundColor?Тип string
borderRadius?Тип number
gradientColors?Тип string[]
withPressAnimation?Тип boolean
animationDuration?Тип number
disabled?Тип boolean
accessibilityLabel?Тип string
style?Тип StyleProp<ViewStyle>