Компоненты/Луч по рамке

Луч по рамке

21 августа 2026

Луч света непрерывно бежит по границе контейнера

Установка

npx astra-rn add border-beam
pnpm dlx astra-rn add border-beam
yarn dlx astra-rn add border-beam
bunx --bun astra-rn add border-beam

Файлы

index.tsx
НазадКарточка подтвержденияДалееЧат-сфера

Вручную

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

npm install @shopify/react-native-skia react-native-reanimated
pnpm add @shopify/react-native-skia react-native-reanimated
yarn add @shopify/react-native-skia react-native-reanimated
bun add @shopify/react-native-skia react-native-reanimated

Скопируйте эти файлы в src/shared/components/base/border-beam/:

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

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

import React, { useMemo } from "react";
import { Dimensions, StyleSheet, Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Feather } from "@expo/vector-icons";
import { BorderBeam } from "@/shared/components/base/border-beam";
import { Showcase, useCustomize } from "~/showcase";
import { SymbolView } from "expo-symbols";
import { useFonts } from "expo-font";
import sfProRounded from "@/assets/fonts/sf-pro-rounded.ttf";
import sfProRoundedBold from "~/assets/fonts/SF-Pro-Rounded-Bold.otf";
import sfProRoundedMedium from "~/assets/fonts/SF-Pro-Rounded-Medium.otf";

const RADIUS = 23;
const WIDTH = Dimensions.get("window").width;
const RAF_SIZE = 19;
const _ICON_SIZE = RAF_SIZE * 0.89;

const CUSTOMIZE = [
  { key: "color1", label: "Цвет луча 1", type: "color", default: "#c15f3c" },
  { key: "color2", label: "Цвет луча 2", type: "color", default: "#c15f3c" },
  { key: "color3", label: "Цвет луча 3", type: "color", default: "#c15f3c" },
  { key: "duration", label: "Круг", type: "slider", min: 0.5, max: 12, step: 0.5, unit: "s", default: 3 },
  { key: "beamLength", label: "Длина луча", type: "slider", min: 0.05, max: 1, step: 0.01, default: 0.36 },
  { key: "intensity", label: "Яркость", type: "slider", min: 0, max: 2, step: 0.05, default: 0.9 },
  { key: "glow", label: "Свечение", type: "slider", min: 0, max: 40, step: 1, unit: "px", default: 8 },
  { key: "ambient", label: "Фоновый свет", type: "slider", min: 0, max: 1, step: 0.01, default: 0 },
  { key: "borderWidth", label: "Толщина рамки", type: "slider", min: 0.5, max: 6, step: 0.5, unit: "px", default: 0.5 },
  { key: "radius", label: "Скругление", type: "slider", min: 0, max: 48, step: 1, unit: "px", default: RADIUS },
  { key: "placeholder", label: "Подсказка", type: "text", default: "Chat with Claude", maxLength: 40 },
  { key: "model", label: "Модель", type: "text", default: "Opus 4.8 Medium", maxLength: 30 },
  { key: "cardColor", label: "Фон карточки", type: "color", default: "#121212" },
] as const;

export default function BorderBeamScreen() {
  const [fontLoaded] = useFonts({
    SfProRounded: sfProRounded,
    SfProRoundedBold: sfProRoundedBold,
    SfProRoundedMedium: sfProRoundedMedium,
  });
  const insets = useSafeAreaInsets();
  const custom = useCustomize(CUSTOMIZE);
  const colors = useMemo(
    () => [custom.color1, custom.color2, custom.color3],
    [custom.color1, custom.color2, custom.color3],
  );
  return (
    <Showcase>
      <View
        style={[
          styles.stage,
          {
            justifyContent: "flex-end",
            alignItems: "center",
            flex: 1,
            paddingBottom: Math.max(insets.bottom, 12) + 12,
          },
        ]}
      >
        <BorderBeam
          style={styles.beam}
          borderRadius={custom.radius}
          borderWidth={custom.borderWidth}
          colors={colors}
          ambient={custom.ambient}
          duration={custom.duration}
          beamLength={custom.beamLength}
          intensity={custom.intensity}
          glow={custom.glow}
        >
          <View
            style={[
              styles.card,
              { borderRadius: custom.radius, backgroundColor: custom.cardColor },
            ]}
          >
            <Text
              style={[
                styles.placeholder,
                {
                  fontFamily: fontLoaded ? "SfProRounded" : undefined,
                },
              ]}
            >
              {custom.placeholder}
            </Text>

            <View style={styles.row}>
              <Feather name="plus" size={_ICON_SIZE} color="#e9e9ee" />
              <View style={styles.model}>
                <Text
                  style={[
                    styles.modelText,
                    {
                      fontFamily: fontLoaded ? "SfProRoundedMedium" : undefined,
                    },
                  ]}
                >
                  {custom.model}
                </Text>
              </View>
              <View style={{ flex: 1 }} />
              <View style={styles.actions}>
                <Feather name="mic" size={_ICON_SIZE} color="#e9e9ee" />
                <View style={styles.send}>
                  <SymbolView
                    name="waveform"
                    size={_ICON_SIZE}
                    tintColor="#000"
                  />
                </View>
              </View>
            </View>
          </View>
        </BorderBeam>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  beam: {
    width: WIDTH * 0.9,
  },
  card: {
    width: "100%",
    borderRadius: RADIUS,
    minHeight: 115,
    backgroundColor: "#121212",
    padding: 20,
  },
  placeholder: {
    color: "#6b6b73",
    fontSize: _ICON_SIZE * 0.87,
    fontWeight: "400",
    paddingBottom: _ICON_SIZE * 1.7,
    top: _ICON_SIZE / 2,
  },
  row: {
    flexDirection: "row",
    alignItems: "center",
    gap: 12,
  },
  model: {
    backgroundColor: "#1f1f1f",
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 22,
  },
  modelText: {
    color: "#c9c9d1",
    fontSize: 12,
    fontWeight: "500",
  },
  actions: {
    flexDirection: "row",
    alignItems: "center",
    gap: 14,
    // flex: 1,
  },
  send: {
    width: 28,
    height: 28,
    borderRadius: 19,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },

  stage: {
    padding: 12,
  },
});

Пропсы

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

Тип React.ReactNode

borderRadius?

Тип number

borderWidth?

Тип number

glow?

Тип number

duration?

Тип number

beamLength?

Тип number

colors?

Тип string[]

intensity?

Тип number

ambient?

Тип number

style?

Тип StyleProp<ViewStyle>