Компоненты/Пагинация

Пагинация

31 января 2025

Индикатор страниц с прокруткой

Установка

npx astra-rn add pagination
pnpm dlx astra-rn add pagination
yarn dlx astra-rn add pagination
bunx --bun astra-rn add pagination

Файлы

Pagination.tsx
НазадВвод OTPДалееПараллакс-лента

Вручную

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

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

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

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

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

import { useRef, useState } from "react";
import { StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";

import { Pagination } from "@/shared/components/molecules/Pagination";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";

const AUTOPLAY_SEQUENCE: Array<[number, number]> = [
  [1, 380],
  [2, 280],
  [3, 640],
  [2, 630],
  [1, 250],
  [0, 200],
];

const CUSTOMIZE = [
  { key: "total", label: "Количество", type: "slider", min: 2, max: 8, step: 1, default: 4 },
  { key: "dotSize", label: "Размер точки", type: "slider", min: 4, max: 16, step: 1, unit: "px", default: 12 },
  { key: "radius", label: "Скругление", type: "slider", min: 0, max: 8, step: 0.5, unit: "px", default: 4 },
  { key: "gap", label: "Отступ точек", type: "slider", min: 0, max: 16, step: 1, unit: "px", default: 8 },
  { key: "activeColor", label: "Активный цвет", type: "color", default: "#f4f4f5" },
  { key: "inactiveColor", label: "Неактивный цвет", type: "color", default: "#3f3f46" },
  { key: "background", label: "Фон", type: "color", default: "#171717" },
  { key: "tempo", label: "Темп автопоказа", type: "slider", min: 0.5, max: 3, step: 0.1, default: 1 },
] as const;

function buildSequence(total: number): Array<[number, number]> {
  if (total === 4) return AUTOPLAY_SEQUENCE;
  const forward = Array.from({ length: total - 1 }, (_, i): [number, number] => [i + 1, 320]);
  const back = Array.from({ length: total - 1 }, (_, i): [number, number] => [total - 2 - i, 260]);
  return [...forward, ...back];
}

export default function App() {
  const [activeIndex, setActiveIndex] = useState<number>(0);
  const custom = useCustomize(CUSTOMIZE);
  const live = useRef({ total: custom.total, tempo: custom.tempo });
  live.current = { total: custom.total, tempo: custom.tempo };

  useAutoplay(async ({ wait }) => {
    for (const [index, delay] of buildSequence(live.current.total)) {
      await wait(delay / live.current.tempo);
      setActiveIndex(Math.min(index, live.current.total - 1));
    }
    await wait(300 / live.current.tempo);
  });

  return (
    <Showcase>
      <GestureHandlerRootView style={styles.container}>
        <StatusBar style="inverted" />

        <Pagination
          activeIndex={Math.min(activeIndex, custom.total - 1)}
          onIndexChange={setActiveIndex}
          dotSize={custom.dotSize}
          inactiveColor={custom.inactiveColor}
          activeColor={custom.activeColor}
          currentColor={custom.activeColor}
          borderRadius={custom.radius}
          dotContainer={custom.gap}
          containerStyle={{
            backgroundColor: custom.background,
            padding: 12,
            borderRadius: 12,
          }}
          totalItems={custom.total}
        />
      </GestureHandlerRootView>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#0a0a0a",
  },
  header: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
    paddingTop: 70,
    paddingBottom: 32,
  },
  title: {
    fontSize: 28,
    color: "#fff",
    letterSpacing: 2,
  },
  subtitle: {
    fontSize: 12,
    color: "#aaa",
  },
  headerRight: {
    width: 40,
    height: 40,
    borderRadius: 20,
    backgroundColor: "#1a1a1a",
    justifyContent: "center",
    alignItems: "center",
  },
  card: {
    width: "100%",
    height: 340,
    borderRadius: 24,
    overflow: "hidden",
  },
  cardImage: {
    width: 300,
    height: "100%",
    resizeMode: "cover",
  },
  cardGradient: {
    ...StyleSheet.absoluteFillObject,
  },
  cardContent: {
    position: "absolute",
    bottom: 0,
    left: 0,
    right: 0,
    padding: 20,
    gap: 8,
  },
  albumName: {
    fontSize: 16,
    color: "#fff",
    letterSpacing: 1,
  },
  artistRow: {
    flexDirection: "row",
    alignItems: "center",
    gap: 6,
  },
  artistText: {
    fontSize: 13,
    color: "rgba(255,255,255,0.7)",
  },
  dot: {
    width: 3,
    height: 3,
    borderRadius: 1.5,
    backgroundColor: "rgba(255,255,255,0.4)",
  },
  yearText: {
    fontSize: 13,
    color: "rgba(255,255,255,0.5)",
  },
  footer: {
    paddingHorizontal: 24,
    marginTop: 32,
    gap: 20,
  },
  nowPlaying: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    backgroundColor: "#141414",
    padding: 12,
    borderRadius: 16,
  },
  nowPlayingLeft: {
    flexDirection: "row",
    alignItems: "center",
    gap: 12,
    flex: 1,
  },
  nowPlayingImage: {
    width: 48,
    height: 48,
    borderRadius: 10,
  },
  nowPlayingInfo: {
    flex: 1,
    gap: 2,
  },
  nowPlayingTitle: {
    fontSize: 14,
    fontWeight: "600",
    color: "#fff",
  },
  nowPlayingArtist: {
    fontSize: 12,
    color: "#666",
  },
  nowPlayingControls: {
    width: 44,
    height: 44,
    borderRadius: 22,
    backgroundColor: "#fff",
    justifyContent: "center",
    alignItems: "center",
  },
  dots: {
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
    gap: 6,
  },
  dotIndicator: {
    width: 6,
    height: 6,
    borderRadius: 3,
    backgroundColor: "#333",
  },
  dotIndicatorActive: {
    width: 20,
    backgroundColor: "#fff",
  },
});

Пропсы

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

Тип number

totalItems

Тип number

inactiveColor?

Тип string

activeColor?

Тип string

currentColor?

Тип string

containerStyle?

Тип StyleProp<ViewStyle>

dotSize?

Тип number

borderRadius?

Тип number

dotContainer?

Тип number

onIndexChange?

Тип (index: number) => void