Şablonlar
Tek kullanımlık kod
Tek sayısal alanla kod girişi, yapıştırma desteği, tekrar gönderme bekleme süresi ve kalan deneme hakkı.
Bu blok, girişten sonra istenen tek kullanımlık kodu AuthShell içinde alır. Kurallar Oturum süresi ve tek kullanımlık kod kalıbından gelir.
Önizleme
Bölüm başlığı “Önizleme”Örnek kod 123456’dır; başka altı rakam yanlış deneme sayılır. “Kodu tekrar gönder” 30 saniye sonra kullanılabilir. Sayfa başlığındaki marka ve mod seçimi önizlemeyi de değiştirir.
pnpm add @bigbang/ui| Veri | Tür | Not |
|---|---|---|
onVerify |
(code: string) => Promise<boolean> |
Doğru kodda true döner. |
attempts |
number |
Kalan deneme hakkı; yanlış kodda bir azalır. Varsayılan 3. |
initialError |
boolean |
Reddedilmiş bir denemeden sonra açılır. |
import { Button, Link, Stack, Text, TextInput } from '@bigbang/ui';import { useEffect, useState, type ClipboardEvent, type FormEvent } from 'react';import type { Lang } from '../../patterns/lang';import { AuthShell } from '../../templates/auth-shell/AuthShell';
const copy = { tr: { title: 'Kodu girin', description: 'Kod, ***** 12 ile biten telefona gönderildi.', label: 'Doğrulama kodu', hint: '6 haneli kodu girin.', verify: 'Doğrula', verifying: 'Doğrulanıyor', resend: 'Kodu tekrar gönder', resendWait: (seconds: number) => `Kodu tekrar gönder (${seconds} sn)`, resent: 'Yeni kod gönderildi.', other: 'Başka bir yöntem kullan', short: 'Kod 6 haneli olmalı.', wrong: (left: number) => `Kod hatalı. ${left} deneme hakkınız kaldı.`, }, en: { title: 'Enter the code', description: 'We sent the code to the phone ending in ***** 12.', label: 'Verification code', hint: 'Enter the 6-digit code.', verify: 'Verify', verifying: 'Verifying', resend: 'Resend code', resendWait: (seconds: number) => `Resend code (${seconds} s)`, resent: 'A new code was sent.', other: 'Use another method', short: 'The code must be 6 digits.', wrong: (left: number) => `Wrong code. You have ${left} ${left === 1 ? 'attempt' : 'attempts'} left.`, },};
export const OTP_COOLDOWN = 30;const digitsOf = (value: string) => value.replace(/\D/g, '').slice(0, 6);
export type OtpBlockProps = { lang?: Lang; /** Attempts left; the error after a wrong code counts down from here. */ attempts?: number; /** Starts with a wrong-code error, as after a rejected attempt. */ initialError?: boolean; /** Resolves `true` for a correct code. */ onVerify?: (code: string) => Promise<boolean>;};
const demoVerify = (code: string) => new Promise<boolean>((resolve) => setTimeout(() => resolve(code === '123456'), 800));
/** One-time code screen: a single numeric field (paste-friendly), a resend cooldown and a fallback link. */export function OtpBlock({ lang = 'tr', attempts = 3, initialError = false, onVerify = demoVerify }: OtpBlockProps) { const t = copy[lang]; const [code, setCode] = useState(''); const [left, setLeft] = useState(initialError ? attempts - 1 : attempts); const [error, setError] = useState(initialError ? t.wrong(attempts - 1) : ''); const [cooldown, setCooldown] = useState(OTP_COOLDOWN); const [status, setStatus] = useState(''); const [pending, setPending] = useState(false);
useEffect(() => { if (cooldown <= 0) return; const timer = setTimeout(() => setCooldown((value) => value - 1), 1000); return () => clearTimeout(timer); }, [cooldown]);
const onPaste = (event: ClipboardEvent<HTMLInputElement>) => { event.preventDefault(); setCode(digitsOf(event.clipboardData.getData('text'))); };
const onSubmit = async (event: FormEvent) => { event.preventDefault(); if (code.length !== 6) { setError(t.short); return; } setPending(true); const ok = await onVerify(code); setPending(false); if (ok) { setError(''); return; } const next = Math.max(left - 1, 0); setLeft(next); setError(t.wrong(next)); };
const resend = () => { if (cooldown > 0) return; setCooldown(OTP_COOLDOWN); setStatus(t.resent); };
return ( <AuthShell lang={lang} title={t.title} description={t.description}> <form noValidate onSubmit={onSubmit} aria-labelledby={`auth-title-${lang}`}> <Stack gap="lg"> <TextInput label={t.label} description={t.hint} name="code" inputMode="numeric" autoComplete="one-time-code" pattern="[0-9]*" maxLength={6} size="lg" className="[&_input]:tracking-[0.5em] [&_input]:tabular-nums" value={code} onValueChange={(value) => setCode(digitsOf(value))} onPaste={onPaste} {...(error ? { error } : {})} /> <Button type="submit" fullWidth loading={pending} loadingLabel={t.verifying}> {t.verify} </Button> <Stack gap="sm" align="start"> <Button variant="tertiary" size="sm" focusableWhenDisabled disabled={cooldown > 0} onClick={resend}> {cooldown > 0 ? t.resendWait(cooldown) : t.resend} </Button> <Link href="#" size="sm"> {t.other} </Link> </Stack> <Text size="sm" tone="subtle" role="status"> {status} </Text> </Stack> </form> </AuthShell> );}h1“Kodu girin” ve kodun gittiği maskeli numara.- Tek TextInput:
inputMode="numeric",autoComplete="one-time-code", en çok 6 rakam; yapıştırılan boşluk ve tireler silinir. - “Doğrula” birincil Button.
- “Kodu tekrar gönder” düğmesi: 30 saniye boyunca odaklanabilir ama devre dışı, kalan süre etikette.
- “Başka bir yöntem kullan” Link ve kibar durum bölgesi.
Erişilebilirlik
Bölüm başlığı “Erişilebilirlik”- Kod tek alandadır; ekran okuyucu altı ayrı kutu yerine tek alan okur, otomatik doldurma ve yapıştırma çalışır.
- Yanlış kodda alan
aria-invalidalır ve hata metni kalan hakla birlikte alana bağlı okunur. - Tekrar gönder düğmesi bekleme süresinde odaklanabilir kalır (
aria-disabled); kullanıcı nedenini etiketten öğrenir. - “Yeni kod gönderildi.” kibar durum bölgesinde bir kez okunur.
- Geri sayım her saniye duyurulmaz; yalnız etikette güncellenir.
Kullanılan bileşenler
Bölüm başlığı “Kullanılan bileşenler”TextInput, Button, Link, Text. Sıradaki bileşenler: OtpInput (C4) aynı tek alanın üstüne altı kutulu görünüm ekleyecek; bu blokta yalnız alan değişir.