Компоненты/Живой чип

Живой чип

21 августа 2026

Чип, который анимирует иконку и подпись при выборе

Установка

npx astra-rn add animated-chip
pnpm dlx astra-rn add animated-chip
yarn dlx astra-rn add animated-chip
bunx --bun astra-rn add animated-chip

Файлы

index.tsx
НазадМеню с перекатом буквДалееАнимированные иконки наведения

Вручную

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

npm install expo-haptics react-native-reanimated
pnpm add expo-haptics react-native-reanimated
yarn add expo-haptics react-native-reanimated
bun add expo-haptics react-native-reanimated

Скопируйте эти файлы в src/shared/components/molecules/animated-chip/:

Он импортирует ещё один модуль — astra-rn add кладёт их рядом сам, вручную скопируйте и их:

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

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

import { StatusBar } from "expo-status-bar";
import { SymbolView, type SymbolViewProps } from "expo-symbols";
import { useMemo, useState } from "react";
import { StyleSheet, View } from "react-native";

import { AnimatedChip } from "@/shared/components/molecules/animated-chip";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";

const FILTERS: {
  value: string;
  label: string;
  icon: SymbolViewProps["name"];
  color: string;
  tint: string;
}[] = [
  {
    value: "all",
    label: "All",
    icon: "square.grid.2x2.fill",
    color: "#ffffff",
    tint: "#000000",
  },
  {
    value: "music",
    label: "Music",
    icon: "music.note",
    color: "#ff375f",
    tint: "#ffffff",
  },
  {
    value: "videos",
    label: "Videos",
    icon: "play.fill",
    color: "#5e5ce6",
    tint: "#ffffff",
  },
];

const CUSTOMIZE = [
  { key: "label1", label: "Подпись 1", type: "text", default: "All", maxLength: 14 },
  { key: "label2", label: "Подпись 2", type: "text", default: "Music", maxLength: 14 },
  { key: "label3", label: "Подпись 3", type: "text", default: "Videos", maxLength: 14 },
  { key: "color1", label: "Цвет 1", type: "color", default: "#ffffff" },
  { key: "color2", label: "Цвет 2", type: "color", default: "#ff375f" },
  { key: "color3", label: "Цвет 3", type: "color", default: "#5e5ce6" },
  { key: "inactive", label: "Фон неактивных", type: "color", default: "#1a1a1a" },
  { key: "iconTint", label: "Иконки неактивных", type: "color", default: "#5c5c60" },
  { key: "gap", label: "Зазор", type: "slider", min: 0, max: 24, step: 1, unit: "px", default: 8 },
  { key: "damping", label: "Затухание", type: "slider", min: 5, max: 40, step: 1, default: 13 },
  { key: "stiffness", label: "Жёсткость", type: "slider", min: 50, max: 500, step: 10, default: 180 },
] as const;

export default function App() {
  const [filter, setFilter] = useState<string | number>("all");
  const custom = useCustomize(CUSTOMIZE);
  const labels = [custom.label1, custom.label2, custom.label3];
  const colors = [custom.color1, custom.color2, custom.color3];
  const { damping, stiffness } = custom;
  const springConfig = useMemo(
    () => ({ damping, stiffness, mass: 0.5, overshootClamping: false }),
    [damping, stiffness],
  );

  useAutoplay(async ({ wait }) => {
    setFilter("all");
    await wait(1000);
    setFilter("music");
    await wait(900);
    setFilter("videos");
    await wait(1300);
    setFilter("music");
    await wait(900);
    setFilter("all");
    await wait(1000);
  });

  return (
    <Showcase>
      <View style={styles.container}>
        <StatusBar style="light" />

        <AnimatedChip.Group
          value={filter}
          onValueChange={setFilter}
          gap={custom.gap}
          springConfig={springConfig}
        >
          {FILTERS.map(({ value, icon, tint }, index) => (
            <AnimatedChip.Item
              key={value}
              value={value}
              activeColor={colors[index]}
              inactiveColor={custom.inactive}
            >
              <AnimatedChip.Icon>
                {({ selected }) => (
                  <SymbolView
                    name={icon}
                    size={18}
                    tintColor={selected ? tint : custom.iconTint}
                    resizeMode="scaleAspectFit"
                  />
                )}
              </AnimatedChip.Icon>
              <AnimatedChip.Label color={tint}>{labels[index]}</AnimatedChip.Label>
            </AnimatedChip.Item>
          ))}
        </AnimatedChip.Group>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: "#0a0a0a",
  },
});

Пропсы

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

Тип ReactNode

value?

Selected value — pass with `onValueChange` to control the group.

Тип TChipValue

defaultValue?

Selected value on first render when the group is uncontrolled.

Тип TChipValue

onValueChange?

Тип (value: TChipValue) => void

gap?

Space between chips.

Тип number

springConfig?

Spring driving every chip's expansion.

Тип WithSpringConfig

haptics?

Fire a light haptic tap on selection. Native only.

Тип boolean

reserveWidth?

Hold the row at the width of its widest arrangement so the group never resizes as the selection moves. Turn off for a row that should shrink to whatever is currently selected.

Тип boolean

style?

Тип StyleProp<ViewStyle>