Компоненты/Блик

Блик

31 января 2025

Заглушка загрузки с анимацией блика или пульса

Установка

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

Файлы

Shimmer.tsx
НазадШейдерный индексДалееУдарная волна

Вручную

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

npm install expo-linear-gradient react-native-reanimated
pnpm add expo-linear-gradient react-native-reanimated
yarn add expo-linear-gradient react-native-reanimated
bun add expo-linear-gradient react-native-reanimated

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

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

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

import { View, Text, StyleSheet, Pressable, ScrollView } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { useFonts } from "expo-font";
import { ShimmerGroup, Shimmer } from "@/shared/components/molecules/Shimmer";
import { useMemo, useState } from "react";
import { Showcase, useCustomize } from "~/showcase";
import type { ShimmerPreset } from "@/shared/components/molecules/Shimmer/Shimmer.types";

const PRESETS: ShimmerPreset[] = ["dark", "light", "twitter", "neutral"];

const CUSTOM_COLORS = [
  "rgba(88, 28, 135, 1)",
  "rgba(147, 51, 234, 1)",
  "rgba(232, 121, 249, 1)",
  "rgba(147, 51, 234, 1)",
  "rgba(88, 28, 135, 1)",
];

const CUSTOM_HEX = ["#581c87", "#9333ea", "#e879f9"];

const CUSTOMIZE = [
  { key: "preset", label: "Пресет", type: "select", options: ["dark", "light", "twitter", "neutral"], default: "dark" },
  { key: "variant", label: "Вид карточки", type: "select", options: ["shimmer", "pulse"], default: "shimmer" },
  { key: "duration", label: "Длительность", type: "slider", min: 300, max: 4000, step: 50, unit: "ms", default: 1200 },
  { key: "opacity", label: "Прозрачность", type: "slider", min: 0.1, max: 1, step: 0.05, default: 0.7 },
  { key: "customDuration", label: "Свои цвета: темп", type: "slider", min: 300, max: 4000, step: 50, unit: "ms", default: 1400 },
  { key: "colorEdge", label: "Цвет краёв", type: "color", default: CUSTOM_HEX[0] },
  { key: "colorMid", label: "Цвет середины", type: "color", default: CUSTOM_HEX[1] },
  { key: "colorPeak", label: "Цвет блика", type: "color", default: CUSTOM_HEX[2] },
  { key: "title", label: "Заголовок", type: "text", default: "Shimmer", maxLength: 40 },
  { key: "subtitle", label: "Подзаголовок", type: "text", default: "Placeholder skeletons while content loads.", maxLength: 80 },
] as const;

export default function App(_$_: Record<string, unknown>) {
  const [fontLoaded, fontError] = useFonts({
    SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),
    HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),
  });

  const custom = useCustomize(CUSTOMIZE);
  const [presetState, setPreset] = useState<ShimmerPreset>("dark");
  const [presetSource, setPresetSource] = useState<string>(custom.preset);
  if (presetSource !== custom.preset) {
    setPresetSource(custom.preset);
    setPreset(custom.preset as ShimmerPreset);
  }
  const preset = presetState;
  const { colorEdge, colorMid, colorPeak } = custom;
  const customColors = useMemo(
    () =>
      colorEdge === CUSTOM_HEX[0] &&
      colorMid === CUSTOM_HEX[1] &&
      colorPeak === CUSTOM_HEX[2]
        ? CUSTOM_COLORS
        : [colorEdge, colorMid, colorPeak, colorMid, colorEdge],
    [colorEdge, colorMid, colorPeak],
  );
  const customBase = colorEdge === CUSTOM_HEX[0] ? undefined : { backgroundColor: colorEdge };
  const display = fontLoaded ? "HelveticaNowDisplay" : undefined;
  const text = fontLoaded ? "SfProRounded" : undefined;

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

        <ScrollView
          style={{ opacity: fontLoaded || fontError ? 1 : 0 }}
          contentContainerStyle={styles.content}
          showsVerticalScrollIndicator={false}
        >
          <Text style={[styles.title, { fontFamily: display }]}>{custom.title}</Text>
          <Text style={[styles.subtitle, { fontFamily: text }]}>
            {custom.subtitle}
          </Text>

          <View style={styles.presetRow}>
            {PRESETS.map((item) => {
              const active = item === preset;
              return (
                <Pressable
                  key={item}
                  onPress={() => setPreset(item)}
                  style={[styles.presetChip, active && styles.presetChipActive]}
                >
                  <Text
                    style={[
                      styles.presetLabel,
                      active && styles.presetLabelActive,
                      { fontFamily: text },
                    ]}
                  >
                    {item}
                  </Text>
                </Pressable>
              );
            })}
          </View>

          <Text style={[styles.sectionLabel, { fontFamily: text }]}>Card</Text>
          <View style={styles.card}>
            <ShimmerGroup
              preset={preset}
              duration={custom.duration}
              opacity={custom.opacity}
              variant={custom.variant as "shimmer" | "pulse"}
            >
              <Shimmer style={styles.cover} />
              <View style={styles.cardBody}>
                <Shimmer style={styles.lineLg} />
                <Shimmer style={styles.lineMd} />
                <Shimmer style={styles.lineSm} />
              </View>
            </ShimmerGroup>
          </View>

          <Text style={[styles.sectionLabel, { fontFamily: text }]}>List</Text>
          <View style={styles.card}>
            <ShimmerGroup preset={preset} duration={custom.duration}>
              {[0, 1, 2].map((row) => (
                <View key={row} style={styles.listRow}>
                  <Shimmer style={styles.avatar} />
                  <View style={styles.listRowText}>
                    <Shimmer style={styles.lineMd} />
                    <Shimmer style={styles.lineSm} />
                  </View>
                  <Shimmer style={styles.trailing} />
                </View>
              ))}
            </ShimmerGroup>
          </View>

          <Text style={[styles.sectionLabel, { fontFamily: text }]}>
            Variants
          </Text>
          <View style={styles.card}>
            <View style={styles.variantRow}>
              <View style={styles.variantCell}>
                <Shimmer
                  preset={preset}
                  variant="shimmer"
                  duration={custom.duration}
                  style={styles.tile}
                />
                <Text style={[styles.caption, { fontFamily: text }]}>
                  shimmer
                </Text>
              </View>
              <View style={styles.variantCell}>
                <Shimmer
                  preset={preset}
                  variant="pulse"
                  duration={custom.duration}
                  style={styles.tile}
                />
                <Text style={[styles.caption, { fontFamily: text }]}>
                  pulse
                </Text>
              </View>
            </View>
          </View>

          <Text style={[styles.sectionLabel, { fontFamily: text }]}>
            Custom colors
          </Text>
          <View style={styles.card}>
            <ShimmerGroup
              preset="custom"
              shimmerColors={customColors}
              duration={custom.customDuration}
            >
              <Shimmer style={{ ...styles.customTile, ...customBase }} />
              <Shimmer style={{ ...styles.customLineLg, ...customBase }} />
              <Shimmer style={{ ...styles.customLineMd, ...customBase }} />
            </ShimmerGroup>
          </View>

          <Text style={[styles.sectionLabel, { fontFamily: text }]}>
            Directions
          </Text>
          <View style={styles.card}>
            <ShimmerGroup preset={preset} duration={custom.duration}>
              <Shimmer direction="leftToRight" style={styles.bar} />
              <Shimmer direction="rightToLeft" style={styles.bar} />
              <Shimmer direction="topToBottom" style={styles.bar} />
              <Shimmer direction="bottomToTop" style={styles.bar} />
            </ShimmerGroup>
          </View>
        </ScrollView>
      </GestureHandlerRootView>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#0a0a0a",
  },
  content: {
    paddingHorizontal: 24,
    paddingTop: 72,
    paddingBottom: 64,
  },
  title: {
    fontSize: 30,
    fontWeight: "600",
    color: "#fff",
  },
  subtitle: {
    fontSize: 14,
    color: "#777",
    marginTop: 6,
  },
  presetRow: {
    flexDirection: "row",
    flexWrap: "wrap",
    gap: 8,
    marginTop: 20,
  },
  presetChip: {
    paddingHorizontal: 14,
    paddingVertical: 7,
    borderRadius: 999,
    backgroundColor: "rgba(255,255,255,0.06)",
  },
  presetChipActive: {
    backgroundColor: "#fff",
  },
  presetLabel: {
    fontSize: 13,
    color: "#999",
  },
  presetLabelActive: {
    color: "#000",
  },
  sectionLabel: {
    fontSize: 12,
    color: "#555",
    textTransform: "uppercase",
    letterSpacing: 1,
    marginTop: 32,
    marginBottom: 12,
  },
  card: {
    backgroundColor: "#141414",
    borderRadius: 20,
    padding: 16,
    gap: 12,
  },
  cover: {
    width: "100%",
    height: 140,
    borderRadius: 14,
  },
  cardBody: {
    gap: 10,
  },
  lineLg: {
    width: "80%",
    height: 18,
    borderRadius: 6,
  },
  lineMd: {
    width: "60%",
    height: 13,
    borderRadius: 5,
  },
  lineSm: {
    width: "40%",
    height: 13,
    borderRadius: 5,
  },
  listRow: {
    flexDirection: "row",
    alignItems: "center",
    gap: 12,
  },
  listRowText: {
    flex: 1,
    gap: 8,
  },
  avatar: {
    width: 46,
    height: 46,
    borderRadius: 23,
  },
  trailing: {
    width: 26,
    height: 26,
    borderRadius: 8,
  },
  variantRow: {
    flexDirection: "row",
    gap: 12,
  },
  variantCell: {
    flex: 1,
    gap: 8,
    alignItems: "center",
  },
  tile: {
    width: "100%",
    height: 72,
    borderRadius: 14,
  },
  caption: {
    fontSize: 11,
    color: "#666",
  },
  bar: {
    width: "100%",
    height: 34,
    borderRadius: 10,
  },
  customTile: {
    width: "100%",
    height: 72,
    borderRadius: 14,
    backgroundColor: "rgba(88, 28, 135, 1)",
  },
  customLineLg: {
    width: "80%",
    height: 18,
    borderRadius: 6,
    backgroundColor: "rgba(88, 28, 135, 1)",
  },
  customLineMd: {
    width: "60%",
    height: 13,
    borderRadius: 5,
    backgroundColor: "rgba(88, 28, 135, 1)",
  },
});

Пропсы

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

Тип boolean

shimmerColors?

Тип string[]

duration?

Тип number

className?

@deprecated

Тип string

style?

Тип ViewStyle

variant?

Тип "shimmer" | "pulse"

direction?

Тип ShimmerDirection

preset?

Тип ShimmerPreset

opacity?

Тип number

children?

Тип ReactNode

IShimmerGroup

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

Тип boolean

children

Тип ReactNode

preset?

Тип ShimmerPreset

shimmerColors?

Тип string[]

duration?

Тип number

direction?

Тип ShimmerDirection

variant?

Тип "shimmer" | "pulse"

opacity?

Тип number