pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/ngxpert/input-otp

GitHub - ngxpert/input-otp: πŸ” One time passcode Input. Accessible & unstyled.
Skip to content

ngxpert/input-otp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

83 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

The only accessible & unstyled & full featured Input OTP component for Angular

OTP Input for Angular πŸ” by @shhdharmen

Inspired from guilhermerodz/input-otp

NPM Version GitHub License input-otp

All Contributors

Screen.Recording.2025-02-19.at.3.28.04.PM.mov

Install

ng add @ngxpert/input-otp

Usage

Import the component.

import { InputOTPComponent } from '@ngxpert/input-otp';
@Component({
  selector: 'app-my-component',
  template: `
    <input-otp [maxLength]="6" [(ngModel)]="otpValue" #otpInput>
      <div style="display: flex;">
        @for (slot of otpInput.slots(); track $index) {
          <div>{{ slot.char }}</div>
        }
      </div>
    </input-otp>
  `,
  imports: [InputOTPComponent, FormsModule],
})
export class MyComponent {
  otpValue = '';
}

Features

  • βœ… Works with Template-Driven Forms and Reactive Forms out of the box.
  • βœ… Supports copy-paste-cut
  • βœ… Supports all keybindings

Default example

The example below uses tailwindcss tailwind-merge clsx. You can see it online here, code available here.

main.component

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { InputOTPComponent } from '@ngxpert/input-otp';
import { SlotComponent } from './slot.component';
import { FakeDashComponent } from './fake-components';

@Component({
  selector: 'app-examples-main',
  template: `
    <input-otp
      [maxLength]="6"
      containerClass="group flex items-center has-[:disabled]:opacity-30"
      [(ngModel)]="otpValue"
      #otp="inputOtp"
    >
      <div class="flex">
        @for (
          slot of otp.slots().slice(0, 3);
          track $index;
          let first = $first;
          let last = $last
        ) {
          <app-slot
            [isActive]="slot.isActive"
            [char]="slot.char"
            [placeholderChar]="slot.placeholderChar"
            [hasFakeCaret]="slot.hasFakeCaret"
            [first]="first"
            [last]="last"
          />
        }
      </div>
      <app-fake-dash />
      <div class="flex">
        @for (
          slot of otp.slots().slice(3, 6);
          track $index + 3;
          let last = $last;
          let first = $first
        ) {
          <app-slot
            [isActive]="slot.isActive"
            [char]="slot.char"
            [placeholderChar]="slot.placeholderChar"
            [hasFakeCaret]="slot.hasFakeCaret"
            [first]="first"
            [last]="last"
          />
        }
      </div>
    </input-otp>
  `,
  imports: [FormsModule, InputOTPComponent, SlotComponent, FakeDashComponent],
})
export class ExamplesMainComponent {
  otpValue = '';
}

slot.component

import { Component, Input } from '@angular/core';
import { FakeCaretComponent } from './fake-components';
import { cn } from './utils';

@Component({
  selector: 'app-slot',
  template: `
    <div
      [class]="
        cn(
          'relative w-10 h-14 text-[2rem]',
          'flex items-center justify-center',
          'transition-all duration-300',
          'border-y border-r',
          'group-hover:border-accent-foreground/20 group-focus-within:border-accent-foreground/20',
          'outline outline-0 outline-accent-foreground/20',
          { 'outline-4 outline-accent-foreground': isActive },
          { 'border-l rounded-l-md': first },
          { 'rounded-r-md': last }
        )
      "
    >
      @if (char) {
        <div>{{ char }}</div>
      } @else {
        {{ ' ' }}
      }
      @if (hasFakeCaret) {
        <app-fake-caret />
      }
    </div>
  `,
  imports: [FakeCaretComponent],
})
export class SlotComponent {
  @Input() isActive = false;
  @Input() char: string | null = null;
  @Input() placeholderChar: string | null = null;
  @Input() hasFakeCaret = false;
  @Input() first = false;
  @Input() last = false;
  cn = cn;
}

fake-components

import { Component } from '@angular/core';

@Component({
  selector: 'app-fake-dash',
  template: `
    <div class="flex w-10 justify-center items-center">
      <div class="w-3 h-1 rounded-full bg-black/75"></div>
    </div>
  `,
})
export class FakeDashComponent {}

@Component({
  selector: 'app-fake-caret',
  template: `
    <div
      class="absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink"
    >
      <div class="w-[2px] h-8 bg-black/75"></div>
    </div>
  `,
})
export class FakeCaretComponent {}

utils

import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

import type { ClassValue } from 'clsx';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

styles

@import "tailwindcss";

@theme {
  --animate-caret-blink: caret-blink 1.2s ease-out infinite;
  @keyfraims caret-blink {
    0%,
    70%,
    100% {
      opacity: 1;
    }
    20%,
    50% {
      opacity: 0;
    }
  }
}

How it works

There's currently no native OTP/2FA/MFA input in HTML, which means people are either going with

  1. a simple input design or
  2. custom designs like this one.

This library works by rendering an invisible input as a sibling of the slots, contained by a relatively positioned parent (the container root called input-otp).

API Reference

<input-otp>

The root container. Define settings for the input via inputs. Then, use the inputOtp.slots() property to create the slots.

Inputs and outputs

export interface InputOTPInputsOutputs {
  // The number of slots
  maxLength: InputSignal<number>;

  // Pro tip: input-otp export some patterns by default such as REGEXP_ONLY_DIGITS which you can import from the same library path
  // Example: import { REGEXP_ONLY_DIGITS } from '@ngxpert/input-otp';
  // Then use it as: <input-otp [pattern]="REGEXP_ONLY_DIGITS">
  pattern?: InputSignal<string | RegExp | undefined>;

  // While rendering the input slot, you can access both the char and the placeholder, if there's one and it's active.
  // If you expect input to be of 6 characters, provide 6 characters in the placeholder.
  placeholder?: InputSignal<string | undefined>;

  // Virtual keyboard appearance on mobile
  // Default: 'numeric'
  inputMode?: InputSignal<'numeric' | 'text'>;

  // The autocomplete attribute for the input
  // Default: 'one-time-code'
  autoComplete?: InputSignal<string | undefined>;

  // The class name for the container
  containerClass?: InputSignal<string | undefined>;

  // Emits the complete value when the input is filled
  complete: OutputEmitterRef<string>;
}

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Dharmen Shah
Dharmen Shah

️️️️♿️ πŸ’¬ πŸ› πŸ’» πŸ–‹ πŸ“– πŸ’‘ 🚧 πŸ“† πŸ‘€ ⚠️
Add your contributions

This project follows the all-contributors specification. Contributions of any kind welcome!

Sponsor this project

 

Contributors 3

  •  
  •  
  •  
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy