Компоненты/Упругий аккордеон

Упругий аккордеон

21 августа 2026

Аккордеон раскрывается и закрывается с пружинным перелётом

Установка

npx astra-rn add bouncy-accordion
pnpm dlx astra-rn add bouncy-accordion
yarn dlx astra-rn add bouncy-accordion
bunx --bun astra-rn add bouncy-accordion

Файлы

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/molecules/bouncy-accordion/:

Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.

Использование

import React, { useState } from "react";
import { ScrollView, StyleSheet, Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Feather } from "@expo/vector-icons";
import { BouncyAccordion } from "@/shared/components/molecules/bouncy-accordion";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
import { SFSymbol, SymbolView } from "expo-symbols";
import { useFonts } from "expo-font";

const _DS_TOKENS = {
  bg: "#121212",
  label: "#ebebeb",
};

interface AccordionContent {
  value: string;
  label: string;
  content: string;
  icon: SFSymbol;
}

const _ACCORDION_CONTENT: AccordionContent[] = [
  {
    value: "what-is-Astra",
    label: "What is Astra?",
    content:
      "Astra is a collection of beautifully crafted React Native components, animations, and utilities designed to help you build polished mobile apps faster.",
    icon: "sparkles",
  },
  {
    value: "is-Astra-free",
    label: "Is Astra free?",
    content:
      "Most components, all logos and agent skills are free to use in personal and commercial apps. Components marked Pro come with an Astra Pro subscription.",
    icon: "gift",
  },
  {
    value: "expo-support",
    label: "Does it support Expo?",
    content:
      "Absolutely. Astra is built with Expo and React Native in mind, while also supporting bare React Native projects whenever possible.",
    icon: "iphone",
  },
  {
    value: "customization",
    label: "Can I customize the components?",
    content:
      "Every component is designed to be customizable. You can adjust colors, spacing, animations, and behavior to match your app's design system.",
    icon: "paintbrush",
  },
];

const CUSTOMIZE = [
  { key: "title", label: "Заголовок", type: "text", default: "FAQ", maxLength: 30 },
  { key: "subtitle", label: "Подзаголовок", type: "text", default: "Common questions", maxLength: 40 },
  { key: "itemColor", label: "Фон пунктов", type: "color", default: _DS_TOKENS.bg },
  { key: "labelColor", label: "Цвет вопросов", type: "color", default: _DS_TOKENS.label },
  { key: "titleColor", label: "Цвет заголовка", type: "color", default: "#ffffff" },
  { key: "gap", label: "Промежуток", type: "slider", min: 0, max: 32, step: 1, unit: "px", default: 12 },
  { key: "radius", label: "Скругление", type: "slider", min: 0, max: 40, step: 1, unit: "px", default: 28 },
  { key: "step", label: "Шаг автопоказа", type: "slider", min: 300, max: 2000, step: 10, unit: "ms", default: 720 },
] as const;

export default function BouncyAccordionScreen() {
  const custom = useCustomize(CUSTOMIZE);
  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 [openValue, setOpenValue] = useState<string | null>(null);

  useAutoplay(async ({ wait }) => {
    await wait(300);
    for (const item of _ACCORDION_CONTENT) {
      setOpenValue(item.value);
      await wait(custom.step);
    }
    setOpenValue(_ACCORDION_CONTENT[0]!.value);
    await wait(850);
    setOpenValue(null);
    await wait(500);
  });

  return (
    <Showcase>
      <View style={styles.content}>
        <View style={styles.stage}>
          <Text
            style={[
              styles.headingTitle,
              { color: custom.titleColor },
              fontLoaded && { fontFamily: "SfProRoundedBold" },
            ]}
          >
            {custom.title}
          </Text>
          <Text
            style={[
              styles.headingSubtitle,
              fontLoaded && { fontFamily: "SfProRounded" },
            ]}
          >
            {custom.subtitle}
          </Text>
        </View>

        <BouncyAccordion.Root
          value={openValue}
          onValueChange={setOpenValue}
          gap={custom.gap}
          radius={custom.radius}
        >
          {_ACCORDION_CONTENT.map((item, index) => (
            <BouncyAccordion.Item
              value={item.value}
              style={[styles.item, { backgroundColor: custom.itemColor }]}
              key={index.toString()}
            >
              <BouncyAccordion.Trigger>
                <BouncyAccordion.Trigger.Icon>
                  <SymbolView
                    name={item.icon}
                    size={18}
                    tintColor={custom.labelColor}
                  />
                </BouncyAccordion.Trigger.Icon>
                <BouncyAccordion.Trigger.Label
                  style={[styles.title, { color: custom.labelColor }]}
                >
                  {item.label}
                </BouncyAccordion.Trigger.Label>
              </BouncyAccordion.Trigger>
              <BouncyAccordion.Content>{item.content}</BouncyAccordion.Content>
            </BouncyAccordion.Item>
          ))}
        </BouncyAccordion.Root>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#f2f2f7" },
  content: { flex: 1, justifyContent: "center", padding: 20 },
  stage: {
    paddingHorizontal: 12,
  },
  title: {
    color: _DS_TOKENS.label,
  },
  item: {
    backgroundColor: _DS_TOKENS.bg,
  },
  headingTitle: {
    fontSize: 32,
    color: "#fff",
  },
  headingSubtitle: {
    fontSize: 15,
    color: "#555",
    marginBottom: 32,
  },
});

Пропсы

Пропс
Описание
children

Тип ReactNode

value?

Controlled open value.

Тип TBouncyAccordionValue

defaultValue?

Uncontrolled initial open value.

Тип TBouncyAccordionValue

onValueChange?

Тип (value: TBouncyAccordionValue) => void

collapsible?

Allow closing the open item by tapping it again.

Тип boolean

gap?

Gap that opens up between connected groups.

Тип number

radius?

Corner radius applied to the outer edges of each group.

Тип number

style?

Тип StyleProp<ViewStyle>