100 lines
3.2 KiB
Plaintext
100 lines
3.2 KiB
Plaintext
@*
|
|
EN: 6-digit OTP input with auto-focus, auto-advance, and backspace support.
|
|
VI: Input OTP 6 chữ số với auto-focus, tự chuyển ô, và hỗ trợ backspace.
|
|
*@
|
|
|
|
@inject IJSRuntime JS
|
|
|
|
<div class="auth-otp-group"
|
|
role="group"
|
|
aria-label="@GroupAriaLabel">
|
|
@for (int i = 0; i < DigitCount; i++)
|
|
{
|
|
var index = i;
|
|
<input id="otp-@index"
|
|
type="text"
|
|
inputmode="numeric"
|
|
maxlength="1"
|
|
autocomplete="one-time-code"
|
|
aria-label="@($"Digit {index + 1} of {DigitCount}")"
|
|
class="auth-otp-input @(UseBlueTheme ? "auth-otp-input--blue" : "") @(!string.IsNullOrEmpty(_digits[index]) ? "auth-otp-input--filled" : "")"
|
|
value="@_digits[index]"
|
|
@oninput="(e) => HandleInput(e, index)"
|
|
@onkeydown="(e) => HandleKeyDown(e, index)" />
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
private string[] _digits = new string[6];
|
|
|
|
/// <summary>
|
|
/// EN: Number of OTP digits.
|
|
/// VI: Số chữ số OTP.
|
|
/// </summary>
|
|
[Parameter] public int DigitCount { get; set; } = 6;
|
|
|
|
/// <summary>
|
|
/// EN: Use blue theme (for 2FA authenticator).
|
|
/// VI: Dùng theme xanh dương (cho 2FA authenticator).
|
|
/// </summary>
|
|
[Parameter] public bool UseBlueTheme { get; set; }
|
|
|
|
/// <summary>
|
|
/// EN: Accessible group label for screen readers.
|
|
/// VI: Nhãn nhóm accessible cho screen reader.
|
|
/// </summary>
|
|
[Parameter] public string GroupAriaLabel { get; set; } = "Enter OTP code";
|
|
|
|
/// <summary>
|
|
/// EN: Callback when all digits are entered.
|
|
/// VI: Callback khi tất cả chữ số được nhập.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<string> OnComplete { get; set; }
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
_digits = new string[DigitCount];
|
|
}
|
|
|
|
private async Task HandleInput(ChangeEventArgs e, int index)
|
|
{
|
|
var value = e.Value?.ToString() ?? "";
|
|
|
|
// EN: Only allow numeric input
|
|
// VI: Chỉ cho phép nhập số
|
|
if (!string.IsNullOrEmpty(value) && !char.IsDigit(value[0]))
|
|
{
|
|
_digits[index] = "";
|
|
return;
|
|
}
|
|
|
|
_digits[index] = value;
|
|
|
|
if (!string.IsNullOrEmpty(value) && index < DigitCount - 1)
|
|
{
|
|
// EN: Auto-advance to next input
|
|
// VI: Tự động chuyển sang ô tiếp theo
|
|
await JS.InvokeVoidAsync("focusOtpInput", index + 1);
|
|
}
|
|
|
|
// EN: Check if all digits are filled
|
|
// VI: Kiểm tra tất cả ô đã nhập xong chưa
|
|
if (_digits.All(d => !string.IsNullOrEmpty(d)))
|
|
{
|
|
var code = string.Join("", _digits);
|
|
await OnComplete.InvokeAsync(code);
|
|
}
|
|
}
|
|
|
|
private async Task HandleKeyDown(KeyboardEventArgs e, int index)
|
|
{
|
|
if (e.Key == "Backspace" && string.IsNullOrEmpty(_digits[index]) && index > 0)
|
|
{
|
|
// EN: Move to previous input on backspace
|
|
// VI: Chuyển về ô trước khi nhấn backspace
|
|
_digits[index - 1] = "";
|
|
await JS.InvokeVoidAsync("focusOtpInput", index - 1);
|
|
}
|
|
}
|
|
}
|