Компоненты/Группа раскрытий

Группа раскрытий

31 января 2025

Группа раскрывающихся секций

Установка

npx astra-rn add disclosure-group
pnpm dlx astra-rn add disclosure-group
yarn dlx astra-rn add disclosure-group
bunx --bun astra-rn add disclosure-group

Файлы

index.tsx
НазадКарточка с переворотомДалееДизеринг фото

Вручную

Поставьте зависимости:

npm install expo-blur react-native-reanimated
pnpm add expo-blur react-native-reanimated
yarn add expo-blur react-native-reanimated
bun 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 группа сама хранит состояние, как раньше.

IDisclosureGroupItem

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

Тип React.ReactNode

onPress?

Тип () => void

style?

Тип StyleProp<ViewStyle>

disabled?

Тип boolean

pressScale?

Тип number

springConfig?

Тип IDisclosureGroupItemSpringConfig