Поставьте зависимости:
npm install expo-blur react-native-reanimatedpnpm add expo-blur react-native-reanimatedyarn add expo-blur react-native-reanimatedbun add expo-blur react-native-reanimatedСкопируйте эти файлы в src/shared/components/molecules/disclosure-group/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import { useRef, useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { useFonts } from "expo-font";
import { SymbolView } from "expo-symbols";
import { DisclosureGroup } from "@/shared/components/molecules/disclosure-group";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "trigger", label: "Заголовок", type: "text", default: "More Options", maxLength: 30 },
{ key: "item1", label: "Пункт 1", type: "text", default: "Edit", maxLength: 24 },
{ key: "item2", label: "Пункт 2", type: "text", default: "Duplicate", maxLength: 24 },
{ key: "item3", label: "Пункт 3", type: "text", default: "Share", maxLength: 24 },
{ key: "item4", label: "Пункт 4", type: "text", default: "Delete", maxLength: 24 },
{ key: "cardColor", label: "Фон карточки", type: "color", default: "#141414" },
{ key: "itemColor", label: "Фон пунктов", type: "color", default: "#1a1a1a" },
{ key: "textColor", label: "Цвет текста", type: "color", default: "#ffffff" },
{ key: "dangerColor", label: "Цвет удаления", type: "color", default: "#ff453a" },
{ key: "radius", label: "Скругление", type: "slider", min: 0, max: 32, step: 1, unit: "px", default: 16 },
{ key: "holdMs", label: "Показ", type: "slider", min: 600, max: 5000, step: 100, unit: "ms", default: 1800 },
] as const;
export default function App() {
const [fontLoaded] = useFonts({
SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),
HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),
});
const [open, setOpen] = useState<boolean>(false);
const custom = useCustomize(CUSTOMIZE);
const holdRef = useRef(custom.holdMs);
holdRef.current = custom.holdMs;
useAutoplay(async ({ wait }) => {
await wait(200);
setOpen(true);
await wait(holdRef.current);
setOpen(false);
await wait(900);
});
const OPTIONS = [
{ label: custom.item1, icon: "pencil" },
{ label: custom.item2, icon: "doc.on.doc" },
{ label: custom.item3, icon: "square.and.arrow.up" },
{ label: custom.item4, icon: "trash", destructive: true },
];
return (
<Showcase>
<GestureHandlerRootView style={styles.container}>
<StatusBar style="light" />
<View style={[styles.card, { backgroundColor: custom.cardColor, borderRadius: custom.radius }]}>
<DisclosureGroup open={open} onOpenChange={setOpen}>
<DisclosureGroup.Trigger
showChevron={false}
contentContainerStyle={styles.triggerContent}
>
<View style={styles.triggerLeft}>
<Text
style={[
styles.triggerText,
{ color: custom.textColor },
fontLoaded && { fontFamily: "SfProRounded" },
]}
>
{custom.trigger}
</Text>
</View>
<SymbolView name="chevron.down" size={14} tintColor="#555" />
</DisclosureGroup.Trigger>
<DisclosureGroup.Items maxHeight={300} scrollable={false} useBlur>
{OPTIONS.map((option, index) => (
<DisclosureGroup.Item
key={index}
onPress={() => console.log(option.label)}
style={[styles.item, { backgroundColor: custom.itemColor }]}
>
<SymbolView
name={option.icon as any}
size={18}
tintColor={option.destructive ? custom.dangerColor : custom.textColor}
/>
<Text
style={[
styles.itemText,
{ color: option.destructive ? custom.dangerColor : custom.textColor },
fontLoaded && { fontFamily: "SfProRounded" },
]}
>
{option.label}
</Text>
</DisclosureGroup.Item>
))}
</DisclosureGroup.Items>
</DisclosureGroup>
</View>
</GestureHandlerRootView>
</Showcase>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0a0a0a",
justifyContent: "center",
padding: 12,
},
content: {
paddingHorizontal: 20,
paddingTop: 100,
gap: 0,
},
title: {
fontSize: 28,
fontWeight: "700",
color: "#fff",
},
subtitle: {
fontSize: 15,
color: "#555",
},
card: {
backgroundColor: "#141414",
borderRadius: 16,
overflow: "hidden",
marginTop: 20,
},
triggerContent: {
padding: 16,
},
triggerLeft: {
flexDirection: "row",
alignItems: "center",
gap: 12,
},
triggerText: {
fontSize: 16,
fontWeight: "500",
color: "#fff",
},
item: {
flexDirection: "row",
alignItems: "center",
gap: 12,
padding: 14,
backgroundColor: "#1a1a1a",
borderRadius: 12,
marginBottom: 6,
},
itemText: {
fontSize: 15,
color: "#fff",
},
destructiveText: {
color: "#ff453a",
},
});childrenТип React.ReactNode
maxHeight?Тип number
scrollable?Тип boolean
blurTint?Тип BlurTint
useBlur?Тип boolean
style?Тип StyleProp<ViewStyle>
У DisclosureGroup есть необязательные open и onOpenChange: с ними раскрытием можно управлять из кода, анимация та же. Без open группа сама хранит состояние, как раньше.
children?Тип React.ReactNode
onPress?Тип () => void
style?Тип StyleProp<ViewStyle>
disabled?Тип boolean
pressScale?Тип number
springConfig?Тип IDisclosureGroupItemSpringConfig