1 звезда на GitHub = 1 новый компонент.

Поставить звезду
Формыform

OTP Input

Поле ввода одноразового кода OTP с отдельными ячейками и автопереходом.

Живое превью

Enter your verification code

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

Скопируйте и вставьте этот код в ваш React-компонент:

tsx
import { OTPInput } from 'react-component-kit-ui/forms';

<OTPInput />

Ручная установка

Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/OTPInput.tsx + OTPInput.css:

OTPInput.tsx
'use client';

import { useEffect, useRef } from 'react';
import './OTPInput.css';

/**
 * OTPInput — A specialized input group for One-Time Password verification codes.
 * 
 * @param {string} label - Optional grouping label title.
 * @param {string} id - Base HTML ID for the inputs.
 * @param {number} length - Number of OTP digits. Default is 4.
 * @param {string} name - Base name for form submission (will be indexed).
 */
export interface OTPInputProps {
  label?: string;
  id: string;
  length?: number;
  name?: string;
}

export function OTPInput({ label, id, length = 4, name = "otp" }: OTPInputProps) {

  // Ported from the Astro <script>: runs once after mount (DOM logic kept as in the Astro kit).
  const mounted = useRef(false);
  useEffect(() => {
    if (mounted.current) return;
    mounted.current = true;
    document.querySelectorAll('[data-otp-group]').forEach(group => {
        const inputs = group.querySelectorAll('input') as NodeListOf<HTMLInputElement>;
        
        inputs.forEach((input, idx) => {
          // Auto-advance
          input.addEventListener('input', (e) => {
            const val = (e.target as HTMLInputElement).value;
            if (val && idx < inputs.length - 1) {
              inputs[idx + 1].focus();
            }
          });
    
          // Backspace logic
          input.addEventListener('keydown', (e) => {
            if (e.key === 'Backspace' && !input.value && idx > 0) {
              inputs[idx - 1].focus();
            }
          });
    
          // Paste logic
          input.addEventListener('paste', (e) => {
            e.preventDefault();
            const data = e.clipboardData?.getData('text').slice(0, inputs.length).split('') || [];
            data.forEach((val, i) => {
              if (inputs[i]) inputs[i].value = val;
            });
            if (inputs[data.length - 1]) inputs[data.length - 1].focus();
          });
        });
      });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <div className="otp-container" data-rk-a692f6="">
        {label && <label className="otp-label" data-rk-a692f6="">{label}</label>}
        <div className="otp-wrap" data-otp-group data-rk-a692f6="">
          {Array.from({ length }).map((_, i) => (
            <input key={i} type="text" inputMode="numeric" maxLength={1} pattern="[0-9]*" autoComplete="one-time-code" id={`${id}-${i}`} name={`${name}[]`} className="otp-field" data-otp-index={i} data-rk-a692f6="" />
          ))}
        </div>
      </div>
    </>
  );
}

export default OTPInput;
OTPInput.css
/* OTPInput — ported from astro-component-kit (forms/OTPInput.astro) */

.otp-container:where([data-rk-a692f6]) { display: flex; flex-direction: column; align-items: center; gap: var(--sp-4, 1rem); }
.otp-label:where([data-rk-a692f6]) { font-size: 0.9rem; font-weight: 600; color: var(--c-text-2, #94a3b8); }

.otp-wrap:where([data-rk-a692f6]) { display: flex; gap: 0.75rem; justify-content: center; }

.otp-field:where([data-rk-a692f6]) { 
  width: 50px; height: 60px; 
  background: var(--c-bg-elev, rgba(255,255,255,0.05)); 
  border: 1px solid var(--c-border, rgba(255,255,255,0.1)); 
  border-radius: var(--r-md, 12px); 
  text-align: center; color: var(--c-text-1, #fff); 
  font-size: 1.75rem; font-weight: 800; 
  outline: none; transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); 
}

.otp-field:focus:where([data-rk-a692f6]) { 
  border-color: var(--c-primary, #6366f1); 
  background: rgba(99, 102, 241, 0.1); 
  box-shadow: 0 0 15px rgba(99, 102, 241, 0.3), 0 0 0 3px rgba(99, 102, 241, 0.15); 
  transform: translateY(-2px);
}

Краткая информация

Категория
Формы
Имя файла
OTPInput.tsx
Зависимости
Нет — чистый React + CSS
Теги
form