इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

एलिमेंट

OtpInput

वन-टाइम-कोड इनपुट निश्चित संख्या में अंक लेता है (टेक्स्ट संदेश से आया वेरिफ़िकेशन कोड, या ऑथेंटिकेटर का कोड) और उन्हें सेल में दिखाता है। व्यवहार इसी का है: सेल के ऊपर रखा एक पारदर्शी नेटिव टेक्स्ट फ़ील्ड कीबोर्ड लेता है, इसलिए टाइपिंग, इनपुट मेथड, पेस्ट, नंबर पैड और सिस्टम का कोड ऑटोफ़िल (iOS का “From Messages”, Android के SMS सुझाव) प्लैटफ़ॉर्म के अपने ही रहते हैं, और स्क्रीन रीडर को केवल एक टेक्स्ट फ़ील्ड मिलता है। केवल अंक ही अंदर जाते हैं, length तक; Backspace आख़िरी अंक हटाता है, और onComplete बताता है कि कोड पूरा भर गया है। रूप-रंग ऐप का अपना है: OtpInput के अपने कोई रंग, आकार, बॉर्डर या लेबल नहीं होते। इसके भाग हैं सेल, वे समूह जिनमें सेल बँटे होते हैं, और एक कैरेट, और ऐप हर एक को स्टाइल करता है।

el-otp-input.tsx
el-otp-input.tsxLive
import { Clipboard, cleanup, mount, signal, Text, View } from "nsh";
import { Button, OtpInput, Switch } from "nsh/base";
import { button, buttonText, desktop, focusRing, hoverFill, Log, Panel, size, Split, ui } from "./palette";

// The parts: geometry and motion in classes, colors from the palette. The
// cell that takes the next digit gets the keyboard's focus ring (`focus:`,
// the palette's `info` ring) while the code has the focus, and the caret
// blinks in it (steady with reduced motion: the input's own). A cell that
// holds a digit, a bullet too, lifts out of its well: a card's fill under a
// firmer edge (`data-filled:`), faded in (`transition-colors`). Cells are
// 44 points on a touch screen, 36 with a pointer; a pressed or hovered
// button and the switches' rows take the palette's states.
const CODE = "246810";
const cell = desktop ? "w-9 h-11 text-lg" : "w-11 h-13 text-xl";
const cellClass = `${cell} items-center justify-center rounded-lg border border-line bg-inset font-semibold text-foreground transition-colors motion-reduce:transition-none data-filled:border-input data-filled:bg-card focus:outline-2 focus:outline-info focus:-outline-offset-2 disabled:opacity-40`;
const row = `-mx-2 flex-row-reverse items-center justify-between gap-3 rounded-lg px-2 ${desktop ? "py-1" : "py-2"} active:opacity-70`;
const track = `${desktop ? "w-9" : "w-12"} rounded-full p-0.5 transition-colors motion-reduce:transition-none`;
const thumb = desktop ? "size-4 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-4" : "size-5.5 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-5";

function Option(props: { label: string; checked: boolean; onChange: (next: boolean) => void }) {
  return (
    <Switch
      checked={props.checked}
      onChange={({ checked }) => props.onChange(checked)}
      class={row}
      hoverStyle={hoverFill()}
      focusStyle={focusRing()}
      trackClass={track}
      trackStyle={props.checked ? ui().accentFill : ui().inset}
      thumbClass={thumb}
      thumbStyle={props.checked ? { backgroundColor: ui().onAccent } : ui().raised}
    >
      <Text style={{ flex: 1, fontSize: size.label, color: ui().text }}>{props.label}</Text>
    </Switch>
  );
}

function Action(props: { label: string; disabled?: boolean; onPress: () => void }) {
  return (
    <Button onPress={props.onPress} disabled={props.disabled} {...button("secondary", { style: desktop ? {} : { flex: 1 } })}>
      <Text style={buttonText()}>{props.label}</Text>
    </Button>
  );
}

function App() {
  const code = signal("");
  const masked = signal(false);
  const disabled = signal(false);
  const status = signal<"typing" | "checking" | "right" | "wrong">("typing");
  const events = signal<string[]>([]);
  const log = (line: string) => events.set([line, ...events()].slice(0, 6));
  // Checking the code takes a moment, as a server's answer does.
  let check: ReturnType<typeof setTimeout> | undefined;
  cleanup(() => clearTimeout(check));
  const verify = (value: string) => {
    status.set("checking");
    clearTimeout(check);
    check = setTimeout(() => status.set(value === CODE ? "right" : "wrong"), 600);
  };
  const message = () =>
    ({
      typing: { text: `${code().length} of 6 digits`, color: ui().muted },
      checking: { text: "Checking…", color: ui().muted },
      right: { text: "Verified", color: ui().up },
      wrong: { text: "That code is not right", color: ui().down },
    })[status()];

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>otp input</Text>
      <Text style={{ color: ui().muted }}>
        {desktop ? "Click the code and type it, or paste it; Tab reaches it too" : "Tap the code to type it, or paste it"}
      </Text>

      <Split
        gap={14}
        inspector={
          <>
            <Panel title="value · masked · disabled">
              <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>
                {code() ? `value “${masked() ? "•".repeat(code().length) : code()}”` : "value “” (empty)"}
              </Text>
              <Option
                label="Masked"
                checked={masked()}
                onChange={(next) => {
                  masked.set(next);
                  log(next ? "masked: bullets, a secure field" : "masked off");
                }}
              />
              <Option
                label="Disabled"
                checked={disabled()}
                onChange={(next) => {
                  disabled.set(next);
                  log(next ? "disabled: no focus" : "enabled");
                }}
              />
            </Panel>
            <Log title="onChange · onComplete · onFocus · onBlur" lines={events()} minHeight={120} />
          </>
        }
      >
        <View style={{ ...ui().card, borderRadius: 16, padding: desktop ? 20 : 16, gap: 16, alignItems: "center" }}>
          <View style={{ gap: 4, alignItems: "center" }}>
            <Text style={{ fontSize: size.label, fontWeight: "600", color: ui().text }}>Enter the code</Text>
            <Text style={{ fontSize: size.detail, color: ui().muted }}>{`We sent it to •••• 4821 (it is ${CODE})`}</Text>
          </View>
          <OtpInput
            length={6}
            groups={2}
            value={code()}
            masked={masked()}
            disabled={disabled()}
            onChange={({ value: next }) => {
              code.set(next);
              status.set("typing");
              log(`onChange “${next}”`);
            }}
            onComplete={({ value }) => {
              log(`onComplete “${value}”`);
              verify(value);
            }}
            onFocus={() => log("onFocus")}
            onBlur={() => log("onBlur")}
            accessibilityLabel="Verification code, 6 digits"
            class="flex-row items-center gap-3"
            groupClass="flex-row gap-1.5"
            cellClass={cellClass}
            cellStyle={{ boxShadow: ui().inset.boxShadow }}
            caretClass="w-0.5 h-6 rounded-full"
            caretStyle={{ backgroundColor: ui().text }}
          />
          <Text style={{ fontSize: size.detail, color: message().color }}>{message().text}</Text>
          <View class={desktop ? "flex-row gap-2" : "flex-row gap-2 self-stretch"}>
            <Action
              label="Copy the code"
              onPress={() => {
                Clipboard.writeText(CODE);
                log("copied: paste it into the code");
              }}
            />
            <Action
              label="Clear"
              disabled={code() === ""}
              onPress={() => {
                code.set("");
                status.set("typing");
                log("value set to “”");
              }}
            />
          </View>
        </View>
      </Split>
    </View>
  );
}

mount(App);
use nsh_base::{OtpInputCompleteEvent, otp_input, theme};
use nsh_core::prelude::*;

fn verification(verify: impl Fn(&str) + 'static) -> impl IntoElement {
    let code = signal(SharedString::default());
    otp_input()
        .length(6)
        .groups(2)
        .value(move || Some(code.get()))
        .on_change(move |next: &SharedString| code.set(next.clone()))
        .on_complete(move |e: &OtpInputCompleteEvent| verify(&e.value))
        .accessibility_label("Verification code, 6 digits")
        .flex_row()
        .gap_3()
        .group_style(|s| s.flex_row().gap_2())
        .cell_style(|s| {
            s.w(px(40.0))
                .h(px(48.0))
                .items_center()
                .justify_center()
                .rounded(px(8.0))
                .border_1()
                .border_color(move || theme().input)
                .text_xl()
        })
        // The cell that takes the next digit, while the code has the focus,
        // and the cells that hold a digit.
        .cell_class("focus:border-ring data-filled:bg-muted")
        .caret_style(|s| s.w(px(2.0)).h(px(24.0)).bg(move || theme().foreground))
}

OtpInput nsh/base से आता है, जो Rust में nsh-base क्रेट है। Rust में otp_input() हर प्रॉप के लिए एक सेटर (.length(..), .cell_style(..)), हर इवेंट के लिए एक लिस्नर मेथड (.on_complete(..)) और इनपुट के अपने लिए टाइप्ड स्टाइल मेथड के साथ एक इनपुट बनाता है।

#कोड

  • Controlled: value दें और उसे onChange में सेट करें। सेल वही value दिखाते हैं जो ऐप देता है, जैसा है वैसा; ऐप द्वारा सेट किया गया मान (ग़लत कोड के बाद कोड साफ़ करना) कभी रिपोर्ट नहीं किया जाता।
  • Uncontrolled: defaultValue दें (या कुछ नहीं), और कोड को onChange और onComplete से पढ़ें।

उपयोगकर्ता जो करता है वह फ़ील्ड से पढ़ा जाता है, कैरेट हमेशा उसके अंत में रहता है:

उपयोगकर्ताकोड
कोई अंक टाइप करता हैजब तक कोड length से छोटा है, वह अंत में जुड़ जाता है; भर जाने के बाद कुछ नहीं बदलता।
फ़ुल-विड्थ अंक टाइप करता है (0–9, पूर्वी एशियाई इनपुट मेथड का)ASCII अंक जुड़ता है।
कुछ और टाइप करता हैकुछ नहीं बदलता; फ़ील्ड उसे छोड़ देता है।
Backspace दबाता हैआख़िरी अंक हट जाता है।
पेस्ट करता है, या सिस्टम कोड ऑटोफ़िल करता हैउसके अंक उसी तरह जुड़ते हैं; जिस टेक्स्ट में length या अधिक अंक हों (123-456, किसी संदेश का कोड), वह कोड को अपने पहले length अंकों से बदल देता है।

onChange हर बदलाव को नए कोड के साथ रिपोर्ट करता है, और जब बदलाव से आख़िरी सेल भरती है तो onComplete उसके बाद आता है, इसलिए ऐप कोड का सत्यापन वहीं करे।

#Props

PropRustटाइपडिफ़ॉल्टविवरण
length.length(..)numberआवश्यककोड में कितने अंक हैं: हर अंक के लिए एक सेल।
value.value(..)stringकोई नहींऐप द्वारा नियंत्रित कोड (जैसा है वैसा दिखाया जाता है)। इसके बिना इनपुट अपना कोड स्वयं रखता है।
defaultValue.default_value(..)stringकोई नहींशुरुआत में कोड, जब value सेट न हो।
masked.masked(..)booleanfalseभरी हुई सेल अपने अंक की जगह एक बुलेट दिखाती हैं, और फ़ील्ड सुरक्षित (secure) होता है: स्क्रीन रीडर कोड को नहीं पढ़ते, और सिस्टम उसे अपने सुझावों से बाहर रखता है।
disabled.disabled(..)booleanfalseन फ़ोकस, न संपादन; disabled: स्टाइल इनपुट और उसके भागों पर लागू होते हैं, और स्क्रीन रीडर बताते हैं कि वह धूसर (dimmed) है।
groups.groups(..)number1सेल कितने समूहों में बँटी हैं, जितना हो सके बराबर (छह अंकों को दो में बाँटने पर 3 + 3); कम से कम एक, अधिकतम हर सेल पर एक।
accessibilityLabel.accessibility_label(..)stringकोई नहींस्क्रीन रीडर फ़ील्ड के लिए क्या कहते हैं: कोड किसलिए है और उसमें कितने अंक हैं।
cellClass, cellStyle.cell_class(..), .cell_style(|s| ..)string, StyleProp<TextStyle>कोई नहींहर सेल: उसका आकार, बॉर्डर और टेक्स्ट (color, fontSize, जिन्हें उसका अंक विरासत में लेता है); focus: और data-filled: उसकी अवस्थाओं को स्टाइल करते हैं (Parts देखें)।
groupClass, groupStyle.group_class(..), .group_style(|s| ..)string, StyleProp<ViewStyle>कोई नहींसेल का हर समूह (flex-row gap-2)।
caretClass, caretStyle.caret_class(..), .caret_style(|s| ..)string, StyleProp<ViewStyle>कोई नहींकैरेट: उसकी चौड़ाई, ऊँचाई और रंग।
class, styleस्टाइल मेथडstring, StyleProp<ViewStyle>कोई नहींस्वयं इनपुट: उसके समूहों के चारों ओर की पंक्ति (या स्तंभ)।

#Events

EventRustPayloadकब
onChange.on_change(|value: &SharedString| ..)(e: { value: string })उपयोगकर्ता ने कोड बदला: टाइप किया, मिटाया, पेस्ट किया या ऑटोफ़िल किया।
onComplete.on_complete(|e: &OtpInputCompleteEvent| ..){ value }उपयोगकर्ता के बदलाव से आख़िरी सेल भर गई, उसके onChange के बाद।
onFocus / onBlur.on_focus / .on_blurकोई नहींइनपुट को कीबोर्ड फ़ोकस मिला / उसने खोया।

#Ref मेथड

मेथडRustविवरण
focus()focus() (OtpInputApi)कीबोर्ड फ़ोकस लेता है और सॉफ़्टवेयर कीबोर्ड दिखाता है, जैसा इनपुट पर प्रेस करने से होता है।

#Parts

भागभीतरस्टाइल करने के लिए
इनपुटclass, style
एक समूहइनपुट, हर समूह के लिए एकgroupClass, groupStyle; disabled: इनपुट का अनुसरण करता है
एक सेलउसका समूह, हर अंक के लिए एकcellClass, cellStyle; focus: उस सेल पर जो अगला अंक लेती है जब इनपुट के पास फ़ोकस हो, data-filled: हर उस सेल पर जिसमें अंक है, disabled: इनपुट का अनुसरण करता है
अंकभरी हुई सेलसेल के टेक्स्ट स्टाइल (text-xl, font-semibold); • जब masked
कैरेटवह खाली सेल जो अगला अंक लेती है, जब इनपुट के पास फ़ोकस होcaretClass, caretStyle

अगला अंक लेने वाली सेल आख़िरी अंक के बाद वाली सेल होती है, या कोड पूरा भर जाने पर आख़िरी सेल। कैरेट टेक्स्ट फ़ील्ड की तरह ब्लिंक करता है (आधा सेकंड चालू, आधा बंद, और हर संपादन के बाद कुछ देर स्थिर), अपनी opacity के द्वारा, नेटिव रूप से, जबकि वह लेआउट में अपनी जगह बनाए रखता है; reduced motion होने पर वह स्थिर रहता है। इनपुट पर कहीं भी प्रेस करने से वह फ़ोकस हो जाता है: फ़ील्ड सेल के ऊपर रहता है।

जिस सेल में अंक है, मास्क किया हुआ हो या नहीं, उसमें एट्रिब्यूट data-filled होता है, इसलिए data-filled: क्लासेस उन सेल को स्टाइल करती हैं जिन्हें उपयोगकर्ता ने भरा है (गहरा बॉर्डर, एक फ़िल: data-filled:border-foreground data-filled:bg-muted), जैसा shadcn/ui अपनी अवस्थाएँ लिखता है। cellClass में group के साथ, group-data-filled: क्लासेस भरी हुई सेल के भीतर की चीज़, यानी उसके अंक, को स्टाइल करती हैं। सेल अपनी क्लासेस बदलती हैं, अपनी सामग्री कभी नहीं, इसलिए एक कुंजी एक ही सेल को फिर से स्टाइल करती है और कुछ दोबारा ड्रॉ नहीं करती।

#Accessibility

  • स्क्रीन रीडर को एक टेक्स्ट फ़ील्ड मिलता है, जिसका नाम accessibilityLabel से तय होता है (बताएँ कि कोड किसलिए है और उसकी लंबाई कितनी है: “Verification code, 6 digits”), और कोड उसका मान होता है; सेल उनसे छिपी रहती हैं। जब masked हो, फ़ील्ड सुरक्षित (secure) होता है, इसलिए कोड ज़ोर से नहीं पढ़ा जाता।
  • कीबोर्ड प्लैटफ़ॉर्म का अपना है: फ़ोन पर नंबर पैड, डेस्कटॉप पर कोई भी कीबोर्ड (केवल अंक अंदर जाते हैं), और Backspace। Tab किसी भी टेक्स्ट फ़ील्ड की तरह इनपुट तक पहुँचता है।
  • जब disabled हो, उसे फ़ोकस नहीं मिलता, और स्क्रीन रीडर बताते हैं कि वह धूसर (dimmed) है।

सामान्य रूप से लेबल, रोल और घोषणाओं के लिए Accessibility देखें।

#प्लैटफ़ॉर्म नोट्स

  • iOS: फ़ील्ड एक कोड फ़ील्ड है (textContentType oneTimeCode), इसलिए QuickType बार Messages या Mail से कोई कोड सुझाता है; नंबर पैड खुलता है। masked फ़ील्ड फ़ोकस खोने के बाद दोबारा टाइप करने पर नए सिरे से शुरू होता है, जैसा iOS के secure फ़ील्ड करते हैं।
  • Android: autofillHints smsOTPCode कीबोर्ड और ऑटोफ़िल सेवा को टेक्स्ट संदेश का कोड सुझाने देता है; नंबर कीबोर्ड खुलता है।
  • macOS, Windows, Linux और वेब: टाइपिंग और पेस्ट फ़ील्ड में जाते हैं (ब्राउज़र में, फ़ोन के नंबर कीबोर्ड के लिए inputmode="numeric" के साथ), और सेल के ऊपर I-beam पॉइंटर दिखता है।