Компоненты/Кнопка сохранения

Кнопка сохранения

21 августа 2026

Кнопка сохранения переходит между состояниями по нажатию

Установка

npx astra-rn add save-button
pnpm dlx astra-rn add save-button
yarn dlx astra-rn add save-button
bunx --bun astra-rn add save-button

Файлы

index.tsx
НазадНажимаемоеДалееЗатухание при прокрутке

Вручную

Поставьте зависимости:

npm install @expo/vector-icons react-native-reanimated
pnpm add @expo/vector-icons react-native-reanimated
yarn add @expo/vector-icons react-native-reanimated
bun 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 кнопка работает только от касаний, как и раньше.

const buttonRef = useRef<ISaveButtonHandle>(null);

buttonRef.current?.press();

Пропсы

Пропс
Описание
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>