Поставьте зависимости:
npm install react-native-reanimated react-native-safe-area-context react-native-workletspnpm add react-native-reanimated react-native-safe-area-context react-native-workletsyarn add react-native-reanimated react-native-safe-area-context react-native-workletsbun add react-native-reanimated react-native-safe-area-context react-native-workletsСкопируйте эти файлы в src/shared/components/organisms/unfold-menu/:
Он импортирует ещё один модуль — astra-rn add кладёт их рядом сам, вручную скопируйте и их:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React, { useMemo, useRef, type RefObject } from "react";
import { StyleSheet, View } from "react-native";
import { Feather } from "@expo/vector-icons";
import { UnfoldMenu } from "@/shared/components/organisms/unfold-menu";
import { useUnfoldMenu } from "@/shared/components/organisms/unfold-menu/context";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const ITEMS = [
{ value: "search", label: "Search", icon: "search" },
{ value: "share", label: "Share", icon: "share-2" },
{ value: "copy", label: "Copy", icon: "copy" },
{ value: "edit", label: "Edit", icon: "edit-2" },
{ value: "archive", label: "Archive", icon: "archive" },
{ value: "delete", label: "Delete", icon: "trash-2" },
] as const;
const CUSTOMIZE = [
{ key: "triggerLabel", label: "Кнопка", type: "text", default: "Add Task", maxLength: 24 },
{ key: "title", label: "Заголовок", type: "text", default: "Create", maxLength: 24 },
{ key: "theme", label: "Тема", type: "select", options: [{ value: "dark", label: "Тёмная" }, { value: "light", label: "Светлая" }], default: "dark" },
{ key: "surface", label: "Фон панели", type: "color", default: "#171716" },
{ key: "text", label: "Цвет текста", type: "color", default: "#f6f3ec" },
{ key: "columns", label: "Колонки", type: "slider", min: 2, max: 4, step: 1, default: 3 },
{ key: "items", label: "Пунктов", type: "slider", min: 2, max: 6, step: 1, default: 6 },
{ key: "radius", label: "Скругление", type: "slider", min: 0, max: 32, step: 1, unit: "px", default: 16 },
{ key: "iconSize", label: "Размер иконок", type: "slider", min: 14, max: 30, step: 1, unit: "px", default: 20 },
{ key: "hold", label: "Показ меню", type: "slider", min: 800, max: 5000, step: 100, unit: "ms", default: 1900 },
{ key: "closeOnSelect", label: "Закрывать выбором", type: "toggle", default: true },
] as const;
const DARK_DEFAULTS = { surface: "#171716", text: "#f6f3ec" };
type TMenuRemote = ReturnType<typeof useUnfoldMenu>;
function MenuRemote({ remote }: { remote: RefObject<TMenuRemote | null> }) {
const menu = useUnfoldMenu("MenuRemote");
remote.current = menu;
return null;
}
export default function UnfoldMenuScreen() {
const menu = useRef<TMenuRemote | null>(null);
const custom = useCustomize(CUSTOMIZE);
const holdRef = useRef(custom.hold);
holdRef.current = custom.hold;
const theme = custom.theme as "dark" | "light";
const { surface, text } = custom;
const palette = useMemo(
() => ({
...(surface !== DARK_DEFAULTS.surface && { surface }),
...(text !== DARK_DEFAULTS.text && { text }),
}),
[surface, text],
);
useAutoplay(async ({ wait }) => {
await wait(300);
menu.current?.requestOpen();
await wait(holdRef.current);
menu.current?.setOpen(false);
await wait(1200);
});
return (
<Showcase>
<View style={styles.center}>
<UnfoldMenu
theme={theme}
palette={palette}
radius={custom.radius}
iconSize={custom.iconSize}
closeOnSelect={custom.closeOnSelect}
onSelect={(value) => console.log(value)}
>
<MenuRemote remote={menu} />
<UnfoldMenu.Trigger>
<UnfoldMenu.Label>{custom.triggerLabel}</UnfoldMenu.Label>
<UnfoldMenu.Icon>
{({ color, size }) => (
<Feather name="plus" size={size} color={color} />
)}
</UnfoldMenu.Icon>
</UnfoldMenu.Trigger>
<UnfoldMenu.Content>
<UnfoldMenu.Header>
<UnfoldMenu.Title>{custom.title}</UnfoldMenu.Title>
<UnfoldMenu.Close>
{({ color, size }) => (
<Feather name="x" size={size} color={color} />
)}
</UnfoldMenu.Close>
</UnfoldMenu.Header>
<UnfoldMenu.Grid columns={custom.columns}>
{ITEMS.slice(0, custom.items).map((item) => (
<UnfoldMenu.Item key={item.value} value={item.value}>
<UnfoldMenu.Icon>
{({ color, size }) => (
<Feather name={item.icon} size={size} color={color} />
)}
</UnfoldMenu.Icon>
<UnfoldMenu.Label>{item.label}</UnfoldMenu.Label>
</UnfoldMenu.Item>
))}
</UnfoldMenu.Grid>
</UnfoldMenu.Content>
</UnfoldMenu>
</View>
</Showcase>
);
}
const styles = StyleSheet.create({
center: {
flex: 1,
justifyContent: "center",
alignItems: "center",
overflow: "hidden",
},
});childrenТип ReactNode
open?Тип boolean
defaultOpen?Тип boolean
onOpenChange?Тип (open: boolean) => void
onSelect?Тип (value?: string) => void
closeOnSelect?Тип boolean
panelWidth?Тип number
screenMargin?Тип number
radius?Тип number
iconSize?Тип number
theme?Тип TUnfoldMenuTheme
palette?Тип Partial<IUnfoldMenuPalette>
style?Тип StyleProp<ViewStyle>