Поставьте зависимости:
npm install expo-haptics react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-svg react-native-workletspnpm add expo-haptics react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-svg react-native-workletsyarn add expo-haptics react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-svg react-native-workletsbun add expo-haptics react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-svg react-native-workletsСкопируйте эти файлы в src/shared/components/molecules/Toast/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import { Fragment, useMemo, useRef } from "react";
import { Dimensions, StyleSheet, Button } from "react-native";
import { SymbolView } from "expo-symbols";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { toast, Toaster } from "@/shared/components/molecules/Toast";
import type { IToasterHandle, IToastSwipeAction, TToastPosition } from "@/shared/components/molecules/Toast";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
import { View } from "react-native";
const _width = Dimensions.get("window").width;
const BACK_PILL_CLEARANCE = 28;
const SWIPE_ACTION: IToastSwipeAction = {
label: "Delete",
color: "#FF3B30",
direction: "left" as const,
icon: () => <SymbolView name="trash.fill" size={18} tintColor={"#fff"} />,
commitOffset: _width * 0.8,
onCommit: () => {},
};
const CUSTOMIZE = [
{ key: "position", label: "Позиция", type: "select", options: [{ value: "top", label: "Сверху" }, { value: "bottom", label: "Снизу" }], default: "top" },
{ key: "theme", label: "Тема", type: "select", options: [{ value: "dark", label: "Тёмная" }, { value: "light", label: "Светлая" }], default: "dark" },
{ key: "successTitle", label: "Заголовок", type: "text", default: "Success", maxLength: 32 },
{ key: "successText", label: "Описание", type: "text", default: "Your changes have been saved.", maxLength: 60 },
{ key: "offset", label: "Отступ", type: "slider", min: 0, max: 120, step: 1, unit: "px", default: 30 },
{ key: "gap", label: "Зазор", type: "slider", min: 0, max: 40, step: 1, unit: "px", default: 14 },
{ key: "visibleToasts", label: "Видимых", type: "slider", min: 1, max: 6, step: 1, default: 3 },
{ key: "duration", label: "Время показа", type: "slider", min: 1000, max: 15000, step: 500, unit: "ms", default: 4000 },
{ key: "closeButton", label: "Крестик", type: "toggle", default: false },
{ key: "swipeLabel", label: "Текст свайпа", type: "text", default: "Delete", maxLength: 16 },
{ key: "swipeColor", label: "Цвет свайпа", type: "color", default: "#FF3B30" },
{ key: "interval", label: "Пауза автопоказа", type: "slider", min: 150, max: 2000, step: 50, unit: "ms", default: 500 },
] as const;
export default function ToastScreen() {
const custom = useCustomize(CUSTOMIZE);
const position = custom.position as TToastPosition;
const insets = useSafeAreaInsets();
const toasterRef = useRef<IToasterHandle>(null);
const customRef = useRef(custom);
customRef.current = custom;
const { swipeLabel, swipeColor } = custom;
const swipeAction = useMemo<IToastSwipeAction>(
() =>
swipeLabel === SWIPE_ACTION.label && swipeColor === SWIPE_ACTION.color
? SWIPE_ACTION
: { ...SWIPE_ACTION, label: swipeLabel, color: swipeColor },
[swipeLabel, swipeColor],
);
const handleSuccess = () =>
toast.success(customRef.current.successTitle, {
description: customRef.current.successText,
});
const handleError = () =>
toast.error("Connection lost", {
description: "Something went wrong. Please try again.",
});
const handleInfo = () =>
toast.info("New version available", {
description: "Restart the app to update.",
});
const handleWarning = () =>
toast.warning("Low storage", {
description: "Less than 200 MB left on this device.",
});
useAutoplay(async ({ wait }) => {
const ids: (string | number)[] = [];
try {
await wait(customRef.current.interval);
ids.push(handleSuccess());
await wait(customRef.current.interval);
ids.push(handleError());
await wait(customRef.current.interval);
ids.push(handleInfo());
await wait(customRef.current.interval);
ids.push(handleWarning());
await wait(customRef.current.interval);
ids.push(handleSuccess());
await wait(customRef.current.interval);
toasterRef.current?.expand();
await wait(900);
for (const id of [ids[2], ids[3], ids[1], ids[4], ids[0]]) {
toasterRef.current?.swipeAway(id);
await wait(1300);
}
await wait(400);
} finally {
toasterRef.current?.collapse();
}
});
return (
<Fragment>
<Showcase disableBackButton={true}>
<View style={styles.stage}>
<Button title="Success" onPress={handleSuccess} />
<Button title="Error" onPress={handleError} />
<Button title="Info" onPress={handleInfo} />
<Button title="Warning" onPress={handleWarning} />
</View>
<Toaster
ref={toasterRef}
position={position}
swipeAction={swipeAction}
offset={custom.offset}
style={
position === "top"
? { top: insets.top + custom.offset + BACK_PILL_CLEARANCE }
: undefined
}
gap={custom.gap}
visibleToasts={custom.visibleToasts}
duration={custom.duration}
closeButton={custom.closeButton}
theme={custom.theme as "dark" | "light"}
/>
</Showcase>
</Fragment>
);
}
const styles = StyleSheet.create({
stage: {
justifyContent: "center",
alignItems: "center",
flex: 1,
},
});Необязательный ref у Toaster с типом IToasterHandle даёт методы expand(), collapse() и toggle() — раскрывают и сворачивают стопку так же, как тап по ней. Без ref стопка раскрывается только касанием, как и раньше.
idТип TToastId
type?Тип TToastType
title?Тип TToastContent
description?Тип TToastContent
icon?Тип ReactNode
jsx?Тип ReactNode
duration?Тип number
dismissible?Тип boolean
position?Тип TToastPosition
action?Тип IToastAction
cancel?Тип IToastAction
onDismiss?Тип TOnDismiss
onAutoClose?Тип TOnDismiss
promise?Тип TPromise
swipeAction?Тип IToastSwipeAction | null
style?Тип StyleProp<ViewStyle>
delete?Тип boolean
position?Тип TToastPosition
duration?Тип number
gap?Тип number
visibleToasts?Тип number
expand?Тип boolean
offset?Тип number
closeButton?Тип boolean
swipeToDismiss?Тип boolean
swipeDirection?Тип TToastSwipeDirection
swipeAction?Тип IToastSwipeAction
haptics?Тип boolean
theme?Тип TToastTheme
colors?Тип Partial<TToastColors>
icons?Тип IToastIcons
style?Тип StyleProp<ViewStyle>
toastStyle?Тип StyleProp<ViewStyle>
titleStyle?Тип StyleProp<TextStyle>
descriptionStyle?Тип StyleProp<TextStyle>
ref?Тип Ref<IToasterHandle>
labelТип ReactNode
onPressТип (id: TToastId) => void