Логотипы стоят в сетке с тонкими разделителями. Если логотипов больше, чем ячеек, они по очереди сменяются: старый уходит вверх с размытием, новый проявляется снизу.
Смена идёт волной — каждая следующая колонка и ряд запаздывают на stagger, а направление волны можно развернуть. Анимации идут на Reanimated; размытие работает в вебе и на Android, на iOS логотипы сменяются сдвигом и прозрачностью. При «Уменьшении движения» логотипы сменяются без движения. Работает на iOS, Android и в вебе.
Поставьте зависимости:
npm install react-native-reanimated react-native-workletspnpm add react-native-reanimated react-native-workletsyarn add react-native-reanimated react-native-workletsbun 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>