Поставьте зависимости:
npm install @expo/vector-icons react-native-reanimatedpnpm add @expo/vector-icons react-native-reanimatedyarn add @expo/vector-icons react-native-reanimatedbun add @expo/vector-icons react-native-reanimatedСкопируйте эти файлы в src/shared/components/micro-interactions/save-button/:
Он импортирует ещё один модуль — astra-rn add кладёт их рядом сам, вручную скопируйте и их:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React, { useRef } from "react";
import { StyleSheet, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import {
SaveButton,
type ISaveButtonHandle,
} from "@/shared/components/micro-interactions/save-button";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "label", label: "Текст", type: "text", default: "Save", maxLength: 12 },
{ key: "savedLabel", label: "Текст после", type: "text", default: "Saved", maxLength: 12 },
{ key: "fontSize", label: "Размер текста", type: "slider", min: 12, max: 24, step: 1, unit: "px", default: 18 },
{ key: "saveDuration", label: "Время сохранения", type: "slider", min: 300, max: 4000, step: 100, unit: "ms", default: 1200 },
{ key: "resetAfter", label: "Сброс через", type: "slider", min: 500, max: 5000, step: 100, unit: "ms", default: 2000 },
{ key: "lightBg", label: "Фон кнопки", type: "color", default: "#ECEAE4" },
{ key: "darkBg", label: "Фон загрузки", type: "color", default: "#2C2B29" },
{ key: "labelColor", label: "Цвет текста", type: "color", default: "#39382F" },
{ key: "savedLabelColor", label: "Цвет «Saved»", type: "color", default: "#4A4A46" },
{ key: "doneCheckBg", label: "Цвет галочки", type: "color", default: "#57564F" },
] as const;
import { useFonts } from "expo-font";
const wait = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
export default function SaveButtonScreen() {
const [fontLoaded] = useFonts({
SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),
SfProRoundedBold: require("~/assets/fonts/SF-Pro-Rounded-Bold.otf"),
SfProRoundedMedium: require("~/assets/fonts/SF-Pro-Rounded-Medium.otf"),
});
const custom = useCustomize(CUSTOMIZE);
const buttonRef = useRef<ISaveButtonHandle>(null);
const saveDuration = useRef(custom.saveDuration);
saveDuration.current = custom.saveDuration;
useAutoplay(async ({ wait }) => {
await wait(600);
buttonRef.current?.press();
await wait(4300);
});
return (
<Showcase>
<View style={styles.stage}>
<SaveButton.Root
ref={buttonRef}
onSave={() => wait(saveDuration.current)}
onSaved={() => console.log("saved")}
resetAfter={custom.resetAfter}
colors={{
lightBg: custom.lightBg,
darkBg: custom.darkBg,
label: custom.labelColor,
savedLabel: custom.savedLabelColor,
doneCheckBg: custom.doneCheckBg,
}}
>
<SaveButton.Label
style={{
fontFamily: fontLoaded ? "SfProRoundedBold" : undefined,
fontSize: custom.fontSize,
}}
>
{custom.label}
</SaveButton.Label>
<SaveButton.Saved
style={{
fontFamily: fontLoaded ? "SfProRoundedBold" : undefined,
fontSize: custom.fontSize,
}}
>
{custom.savedLabel}
</SaveButton.Saved>
</SaveButton.Root>
</View>
</Showcase>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: "#f6f5f2" },
stage: { flex: 1, alignItems: "center", justifyContent: "center" },
});Необязательный ref с типом ISaveButtonHandle даёт метод press() — запускает тот же цикл, что и касание (Save → спиннер → галочка → Saved), и getPhase() — текущая фаза (idle, loading, success, done). Пока кнопка не в фазе idle, press() ничего не делает. Без ref кнопка работает только от касаний, как и раньше.
children?Тип ReactNode
onSave?Тип () => void | Promise<void>
onSaved?Тип () => void
minLoading?Тип number
successPause?Тип number
resetAfter?Тип number
disabled?Тип boolean
colors?Тип Partial<typeof COLORS>
style?Тип StyleProp<ViewStyle>
ref?Тип Ref<ISaveButtonHandle>