Компоненты/Стена логотипов

Стена логотипов

28 сентября 2026

Сетка логотипов в рамке, где каждая ячейка сменяется сама по себе, а смена прокатывается волной по колонкам.

Установка

npx astra-rn add logo-wall
pnpm dlx astra-rn add logo-wall
yarn dlx astra-rn add logo-wall
bunx --bun astra-rn add logo-wall

Файлы

index.tsx
НазадБлик по логотипуДалееБегущая строка

Логотипы стоят в сетке с тонкими разделителями. Если логотипов больше, чем ячеек, они по очереди сменяются: старый уходит вверх с размытием, новый проявляется снизу.

Смена идёт волной — каждая следующая колонка и ряд запаздывают на stagger, а направление волны можно развернуть. Анимации идут на Reanimated; размытие работает в вебе и на Android, на iOS логотипы сменяются сдвигом и прозрачностью. При «Уменьшении движения» логотипы сменяются без движения. Работает на iOS, Android и в вебе.

Вручную

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

npm install react-native-reanimated react-native-worklets
pnpm add react-native-reanimated react-native-worklets
yarn add react-native-reanimated react-native-worklets
bun add react-native-reanimated react-native-worklets

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

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

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

import React from "react";
import { StyleSheet, Text, View } from "react-native";
import Svg, { Circle, Line, Path, Polygon, Polyline, Rect } from "react-native-svg";
import { LogoWall } from "@/shared/components/molecules/logo-wall";
import { Showcase, useCustomize } from "~/showcase";

type IconName =
  | "GitHub"
  | "Figma"
  | "Slack"
  | "Framer"
  | "Apple"
  | "Chrome"
  | "GitLab"
  | "Trello"
  | "Dribbble"
  | "CodePen";

const INK = "#f4f4f5";

const GLYPHS: Record<IconName, React.ReactNode> = {
  GitHub: (
    <>
      <Path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
      <Path d="M9 18c-4.51 2-5-2-7-2" />
    </>
  ),
  Figma: (
    <>
      <Path d="M5 5.5A3.5 3.5 0 0 1 8.5 2H12v7H8.5A3.5 3.5 0 0 1 5 5.5z" />
      <Path d="M12 2h3.5a3.5 3.5 0 1 1 0 7H12V2z" />
      <Path d="M12 12.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 1 1-7 0z" />
      <Path d="M5 19.5A3.5 3.5 0 0 1 8.5 16H12v3.5a3.5 3.5 0 1 1-7 0z" />
      <Path d="M5 12.5A3.5 3.5 0 0 1 8.5 9H12v7H8.5A3.5 3.5 0 0 1 5 12.5z" />
    </>
  ),
  Slack: (
    <>
      <Rect width="3" height="8" x="13" y="2" rx="1.5" />
      <Path d="M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5" />
      <Rect width="3" height="8" x="8" y="14" rx="1.5" />
      <Path d="M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5" />
      <Rect width="8" height="3" x="14" y="13" rx="1.5" />
      <Path d="M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5" />
      <Rect width="8" height="3" x="2" y="8" rx="1.5" />
      <Path d="M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5" />
    </>
  ),
  Framer: <Path d="M5 16V9h14V2H5l14 14h-7m-7 0 7 7v-7m-7 0h7" />,
  Apple: (
    <>
      <Path d="M12 6.528V3a1 1 0 0 1 1-1h0" />
      <Path d="M18.237 21A15 15 0 0 0 22 11a6 6 0 0 0-10-4.472A6 6 0 0 0 2 11a15.1 15.1 0 0 0 3.763 10 3 3 0 0 0 3.648.648 5.5 5.5 0 0 1 5.178 0A3 3 0 0 0 18.237 21" />
    </>
  ),
  Chrome: (
    <>
      <Path d="M10.88 21.94 15.46 14" />
      <Path d="M21.17 8H12" />
      <Path d="M3.95 6.06 8.54 14" />
      <Circle cx="12" cy="12" r="10" />
      <Circle cx="12" cy="12" r="4" />
    </>
  ),
  GitLab: (
    <Path d="m22 13.29-3.33-10a.42.42 0 0 0-.14-.18.38.38 0 0 0-.22-.11.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18l-2.26 6.67H8.32L6.1 3.26a.42.42 0 0 0-.1-.18.38.38 0 0 0-.26-.08.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18L2 13.29a.74.74 0 0 0 .27.83L12 21l9.69-6.88a.71.71 0 0 0 .31-.83Z" />
  ),
  Trello: (
    <>
      <Rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
      <Rect width="3" height="9" x="7" y="7" />
      <Rect width="3" height="5" x="14" y="7" />
    </>
  ),
  Dribbble: (
    <>
      <Circle cx="12" cy="12" r="10" />
      <Path d="M19.13 5.09C15.22 9.14 10 10.44 2.25 10.94" />
      <Path d="M21.75 12.84c-6.62-1.41-12.14 1-16.38 6.32" />
      <Path d="M8.56 2.75c4.37 6 6 9.42 8 17.72" />
    </>
  ),
  CodePen: (
    <>
      <Polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2" />
      <Line x1="12" x2="12" y1="22" y2="15.5" />
      <Polyline points="22 8.5 12 15.5 2 8.5" />
      <Polyline points="2 15.5 12 8.5 22 15.5" />
      <Line x1="12" x2="12" y1="2" y2="8.5" />
    </>
  ),
};

const LOGOS: IconName[] = [
  "GitHub",
  "Figma",
  "Slack",
  "Framer",
  "Apple",
  "Chrome",
  "GitLab",
  "Trello",
  "Dribbble",
  "CodePen",
];

const CUSTOMIZE = [
  { key: "columnCount", label: "Колонки", type: "slider", min: 2, max: 6, step: 1, default: 5 },
  { key: "rowCount", label: "Ряды", type: "slider", min: 1, max: 3, step: 1, default: 1 },
  {
    key: "interval",
    label: "Интервал смены",
    type: "slider",
    min: 1000,
    max: 8000,
    step: 100,
    unit: "мс",
    default: 3000,
  },
  {
    key: "stagger",
    label: "Задержка волны",
    type: "slider",
    min: 0,
    max: 0.5,
    step: 0.01,
    unit: "с",
    default: 0.12,
  },
  {
    key: "direction",
    label: "Направление волны",
    type: "select",
    options: [
      { value: "ltr", label: "Слева направо" },
      { value: "rtl", label: "Справа налево" },
    ],
    default: "ltr",
  },
  { key: "showNames", label: "Названия", type: "toggle", default: true },
] as const;

function LogoIcon({ name }: { name: IconName }) {
  return (
    <Svg
      width={14}
      height={14}
      viewBox="0 0 24 24"
      fill="none"
      stroke={INK}
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
    >
      {GLYPHS[name]}
    </Svg>
  );
}

export default function LogoWallScreen() {
  const custom = useCustomize(CUSTOMIZE);

  return (
    <Showcase>
      <View style={styles.page}>
        <View style={styles.stage}>
          <LogoWall
            columnCount={custom.columnCount}
            rowCount={custom.rowCount}
            interval={custom.interval}
            stagger={custom.stagger}
            direction={custom.direction as "ltr" | "rtl"}
            style={styles.wall}
          >
            {LOGOS.map((name) => (
              <View key={name} style={styles.logo}>
                <LogoIcon name={name} />
                {custom.showNames ? (
                  <Text numberOfLines={1} style={styles.name}>
                    {name}
                  </Text>
                ) : null}
              </View>
            ))}
          </LogoWall>
        </View>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  page: {
    ...StyleSheet.absoluteFillObject,
    alignItems: "center",
    justifyContent: "center",
    paddingVertical: 48,
  },
  stage: {
    alignSelf: "stretch",
    alignItems: "center",
    justifyContent: "center",
    paddingHorizontal: 16,
    paddingVertical: 48,
  },
  wall: {
    width: "100%",
    maxWidth: 768,
    backgroundColor: "#09090b",
  },
  logo: {
    minWidth: 0,
    maxWidth: "100%",
    alignItems: "center",
    gap: 4,
  },
  name: {
    maxWidth: "100%",
    color: INK,
    fontSize: 9,
    lineHeight: 11,
    fontWeight: "600",
    letterSpacing: -0.225,
  },
});

Пропсы

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

Тип ReactNode

columnCount?

Тип number

rowCount?

Тип number

direction?

Тип "ltr" | "rtl"

interval?

Тип number

stagger?

Тип number

dividerColor?

Тип string

style?

Тип StyleProp<ViewStyle>