Поставьте зависимости:
npm install @expo/vector-icons expo-blur expo-haptics react-native-reanimatedpnpm add @expo/vector-icons expo-blur expo-haptics react-native-reanimatedyarn add @expo/vector-icons expo-blur expo-haptics react-native-reanimatedbun add @expo/vector-icons expo-blur expo-haptics react-native-reanimatedСкопируйте эти файлы в src/shared/components/molecules/accordion/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
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 { Accordion } from "@/shared/components/molecules/accordion";
import { Fragment, useRef, useState } from "react";
import { Stack } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useAutoplay, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "title", label: "Заголовок", type: "text", default: "FAQ", maxLength: 30 },
{ key: "subtitle", label: "Подзаголовок", type: "text", default: "Common questions", maxLength: 40 },
{ key: "cardColor", label: "Фон карточки", type: "color", default: "#141414" },
{ key: "borderColor", label: "Цвет рамки", type: "color", default: "#222222" },
{ key: "questionColor", label: "Цвет вопроса", type: "color", default: "#ffffff" },
{ key: "answerColor", label: "Цвет ответа", type: "color", default: "#888888" },
{ key: "chevronColor", label: "Цвет стрелки", type: "color", default: "#666666" },
{ key: "symbolColor", label: "Цвет иконок", type: "color", default: "#888888" },
{ key: "icon", label: "Индикатор", type: "select", options: [{ value: "chevron", label: "Стрелка" }, { value: "cross", label: "Крестик" }], default: "chevron" },
{ key: "spacing", label: "Отступ", type: "slider", min: 0, max: 24, step: 1, unit: "px", default: 8 },
{ key: "questionSize", label: "Размер вопроса", type: "slider", min: 12, max: 22, step: 1, unit: "px", default: 15 },
{ key: "pop", label: "Пружинка", type: "toggle", default: false },
{ key: "step", label: "Шаг автопоказа", type: "slider", min: 400, max: 3000, step: 50, unit: "ms", default: 900 },
] as const;
const FAQS = [
{
id: "1",
question: "How do I get started?",
answer:
"Simply create an account and follow the onboarding steps. It only takes a minute.",
icon: "questionmark.circle.fill",
},
{
id: "2",
question: "Is my data secure?",
answer:
"Yes, we use end-to-end encryption and never share your data with third parties.",
icon: "lock.fill",
},
{
id: "3",
question: "Can I cancel anytime?",
answer:
"Absolutely. No contracts, no hidden fees. Cancel whenever you want.",
icon: "xmark.circle.fill",
},
];
export default function App() {
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 stepRef = useRef<number>(custom.step);
stepRef.current = custom.step;
const darkTheme = {
backgroundColor: custom.cardColor,
borderColor: custom.borderColor,
textColor: custom.questionColor,
iconColor: custom.chevronColor,
};
const insets = useSafeAreaInsets();
const [openItems, setOpenItems] = useState<string[]>([]);
useAutoplay(async ({ wait }) => {
setOpenItems([]);
await wait(500);
for (const faq of FAQS) {
setOpenItems([faq.id]);
await wait(stepRef.current);
}
await wait(400);
});
return (
<Fragment>
<Stack.Screen
options={{
headerBackVisible: true,
headerShown: true,
headerTitle: "",
headerTransparent: true,
}}
/>
<GestureHandlerRootView
style={[
styles.container,
{
paddingTop: insets.top,
},
]}
>
<StatusBar style="light" />
<View style={styles.content}>
<Text
style={[
styles.title,
fontLoaded && { fontFamily: "SfProRoundedBold" },
]}
>
{custom.title}
</Text>
<Text
style={[
styles.subtitle,
fontLoaded && { fontFamily: "SfProRounded" },
]}
>
{custom.subtitle}
</Text>
<View style={styles.accordionWrapper}>
<Accordion
theme={{
backgroundColor: darkTheme.backgroundColor,
borderColor: darkTheme.borderColor,
iconColor: darkTheme.iconColor,
headlineColor: darkTheme.textColor,
subtitleColor: darkTheme.textColor,
}}
spacing={custom.spacing}
value={openItems}
onValueChange={setOpenItems}
>
{FAQS.map((faq) => (
<Accordion.Item
key={faq.id}
value={faq.id}
icon={custom.icon as "chevron" | "cross"}
pop={custom.pop}
>
<Accordion.Trigger>
<View style={styles.triggerContent}>
<SymbolView
name={faq.icon as any}
size={18}
tintColor={custom.symbolColor}
/>
<Text
style={[
styles.question,
{
color: custom.questionColor,
fontSize: custom.questionSize,
},
fontLoaded && { fontFamily: "SfProRoundedMedium" },
]}
>
{faq.question}
</Text>
</View>
</Accordion.Trigger>
<Accordion.Content>
<Text
style={[
styles.answer,
{ color: custom.answerColor },
fontLoaded && { fontFamily: "SfProRounded" },
]}
>
{faq.answer}
</Text>
</Accordion.Content>
</Accordion.Item>
))}
</Accordion>
</View>
</View>
</GestureHandlerRootView>
</Fragment>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0a0a0a",
},
content: {
flex: 1,
paddingHorizontal: 20,
justifyContent: "center",
},
title: {
fontSize: 28,
fontWeight: "700",
color: "#fff",
marginBottom: 4,
},
subtitle: {
fontSize: 15,
color: "#555",
marginBottom: 32,
},
accordionWrapper: {
gap: 8,
},
triggerContent: {
flexDirection: "row",
alignItems: "center",
gap: 12,
flex: 1,
},
question: {
fontSize: 15,
fontWeight: "600",
color: "#fff",
flex: 1,
},
answer: {
fontSize: 14,
color: "#888",
lineHeight: 22,
},
});childrenТип React.ReactNode
type?Тип AccordionType
theme?Тип AccordionTheme
spacing?Тип number
value?Тип string[]
onValueChange?Тип (value: string[]) => void
Необязательные value (массив открытых value пунктов) и onValueChange делают аккордеон управляемым — открытием можно управлять из кода. Без value аккордеон сам хранит состояние, как раньше.
childrenТип React.ReactNode
valueТип string
pop?Тип boolean
icon?Тип AccordionIcons
popScale?Тип number
isLast?Тип boolean