Компоненты/Клавиатура

Клавиатура

28 сентября 2026

Клавиатура ноутбука с настоящей геометрией клавиш, наклоном в перспективе и подсветкой нажатий.

Установка

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

Файлы

index.tsx
НазадОбратный кавер-флоуДалееТурнирная сетка плей-офф

Клавиатура ноутбука с реальными пропорциями клавиш, стрелками половинной высоты и наклоном в перспективе с растворением вдаль. Клавиши нажимаются касанием: клавиша проседает и вспыхивает цветом подсветки, а onKey сообщает о нажатии и отпускании. Отдельные клавиши можно подсветить заранее через lit.

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

Вручную

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

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/organisms/keyboard/:

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

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

import React, { useCallback, useMemo, useState } from "react";
import { Platform, Pressable, StyleSheet, Text, View } from "react-native";

import { Keyboard, type KeyboardCaps } from "@/shared/components/organisms/keyboard";
import { Showcase, useCustomize } from "~/showcase";

const ACCENTS = [
  { name: "Бирюзовый", value: "#66d9e8" },
  { name: "Янтарь", value: "#f59e0b" },
  { name: "Мята", value: "#10b981" },
  { name: "Роза", value: "#f43f5e" },
];

const CUSTOMIZE = [
  { key: "tiltAngle", label: "Наклон", type: "slider", min: 0, max: 60, step: 1, unit: "°", default: 34 },
  { key: "width", label: "Ширина", type: "slider", min: 600, max: 1408, step: 8, unit: "px", default: 1408 },
  { key: "accent", label: "Цвет подсветки", type: "color", default: "#66d9e8" },
  { key: "lit", label: "Подсвеченные клавиши", type: "text", default: "", maxLength: 24 },
  {
    key: "caps",
    label: "Клавиши",
    type: "select",
    options: [
      { value: "auto", label: "По теме" },
      { value: "dark", label: "Тёмные" },
      { value: "light", label: "Светлые" },
    ],
    default: "auto",
  },
  { key: "tilt", label: "Перспектива", type: "toggle", default: true },
] as const;

const RU = "йцукенгшщзфывапролдячсмить";
const EN = "qwertyuiopasdfghjklzxcvbnm";

function toCodes(text: string) {
  const latin = Array.from(text.toLowerCase(), (ch) => (RU.includes(ch) ? EN[RU.indexOf(ch)] : ch)).join("");
  return Array.from(new Set(latin.toUpperCase().replace(/[^A-Z0-9]/g, "").split("")))
    .filter(Boolean)
    .map((ch) => (/\d/.test(ch) ? `Digit${ch}` : `Key${ch}`));
}

const MONO = Platform.select({ ios: "Menlo", android: "monospace", default: "ui-monospace, SFMono-Regular, Menlo, monospace" });

export default function KeyboardScreen() {
  const custom = useCustomize(CUSTOMIZE);
  const [accent, setAccent] = useState(ACCENTS[0]!.value);
  const [prevAccent, setPrevAccent] = useState(custom.accent);
  if (prevAccent !== custom.accent) {
    setPrevAccent(custom.accent);
    setAccent(custom.accent);
  }
  const lit = useMemo(() => toCodes(custom.lit), [custom.lit]);
  const [last, setLast] = useState<string | null>(null);
  const handleKey = useCallback((code: string, down: boolean) => {
    if (down) setLast(code);
  }, []);

  return (
    <Showcase>
      <View style={styles.container}>
        <View style={styles.stack}>
          <View style={[styles.board, { maxWidth: custom.width }]}>
            <Keyboard
              accent={accent}
              listen
              onKey={handleKey}
              lit={lit}
              caps={custom.caps as KeyboardCaps}
              tilt={custom.tilt}
              tiltAngle={custom.tiltAngle}
            />
          </View>
          <View style={styles.footer}>
            <Text style={styles.hint}>{last ? `нажата ${last}` : "нажмите любую клавишу"}</Text>
            <View style={styles.swatches}>
              {ACCENTS.map((item) => {
                const selected = accent === item.value;
                return (
                  <Pressable
                    key={item.value}
                    accessibilityLabel={item.name}
                    onPress={() => setAccent(item.value)}
                    style={[
                      styles.swatch,
                      {
                        backgroundColor: item.value,
                        borderColor: selected ? "#fafafa" : "transparent",
                        transform: [{ scale: selected ? 1.1 : 1 }],
                      },
                    ]}
                  />
                );
              })}
            </View>
          </View>
        </View>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    paddingVertical: 48,
    backgroundColor: "#0a0a0a",
  },
  stack: {
    width: "100%",
    alignItems: "center",
    gap: 32,
    paddingHorizontal: 12,
    paddingVertical: 32,
  },
  board: {
    width: "100%",
  },
  footer: {
    alignItems: "center",
    gap: 16,
  },
  hint: {
    fontFamily: MONO,
    fontSize: 12,
    lineHeight: 16,
    color: "rgba(250,250,250,0.5)",
  },
  swatches: {
    flexDirection: "row",
    gap: 12,
  },
  swatch: {
    width: 24,
    height: 24,
    borderRadius: 12,
    borderWidth: 2,
  },
});

Пропсы

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

Тип readonly string[]

accent?

Тип string

listen?

Тип boolean

caps?

Тип KeyboardCaps

tilt?

Тип boolean

tiltAngle?

Тип number

fadeColor?

Тип string

onKey?

Тип (code: string, down: boolean) => void

style?

Тип StyleProp<ViewStyle>