Blame view

天文台pc/tianwentai-ui/node_modules/input-otp/README.md 13.9 KB
bc518174   王天杨   提交两个项目文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
  # The only accessible & unstyled & full featured Input OTP component in the Web.
  
  ### OTP Input for React 🔐 by [@guilhermerodz](https://twitter.com/guilherme_rodz)
  
  https://github.com/guilhermerodz/input-otp/assets/10366880/753751f5-eda8-4145-a4b9-7ef51ca5e453
  
  ## Usage
  
  ```bash
  npm install input-otp
  ```
  
  Then import the component.
  
  ```diff
  +'use client'
  +import { OTPInput } from 'input-otp'
  
  function MyForm() {
    return <form>
  +   <OTPInput maxLength={6} render={({slots})  => (...)} />
    </form>
  }
  ```
  
  ## Default example
  
  The example below uses `tailwindcss` `@shadcn/ui` `tailwind-merge` `clsx`:
  
  ```tsx
  'use client'
  import { OTPInput, SlotProps } from 'input-otp'
  <OTPInput
    maxLength={6}
    containerClassName="group flex items-center has-[:disabled]:opacity-30"
    render={({ slots }) => (
      <>
        <div className="flex">
          {slots.slice(0, 3).map((slot, idx) => (
            <Slot key={idx} {...slot} />
          ))}
        </div>
  
        <FakeDash />
  
        <div className="flex">
          {slots.slice(3).map((slot, idx) => (
            <Slot key={idx} {...slot} />
          ))}
        </div>
      </>
    )}
  />
  
  // Feel free to copy. Uses @shadcn/ui tailwind colors.
  function Slot(props: SlotProps) {
    return (
      <div
        className={cn(
          'relative w-10 h-14 text-[2rem]',
          'flex items-center justify-center',
          'transition-all duration-300',
          'border-border border-y border-r first:border-l first:rounded-l-md last:rounded-r-md',
          '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': props.isActive },
        )}
      >
        <div className="group-has-[input[data-input-otp-placeholder-shown]]:opacity-20">
          {props.char ?? props.placeholderChar}
        </div>
        {props.hasFakeCaret && <FakeCaret />}
      </div>
    )
  }
  
  // You can emulate a fake textbox caret!
  function FakeCaret() {
    return (
      <div className="absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink">
        <div className="w-px h-8 bg-white" />
      </div>
    )
  }
  
  // Inspired by Stripe's MFA input.
  function FakeDash() {
    return (
      <div className="flex w-10 justify-center items-center">
        <div className="w-3 h-1 rounded-full bg-border" />
      </div>
    )
  }
  
  // tailwind.config.ts for the blinking caret animation.
  const config = {
    theme: {
      extend: {
        keyframes: {
          'caret-blink': {
            '0%,70%,100%': { opacity: '1' },
            '20%,50%': { opacity: '0' },
          },
        },
        animation: {
          'caret-blink': 'caret-blink 1.2s ease-out infinite',
        },
      },
    },
  }
  
  // Small utility to merge class names.
  import { clsx } from 'clsx'
  import { twMerge } from 'tailwind-merge'
  
  import type { ClassValue } from 'clsx'
  
  export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs))
  }
  ```
  
  ## 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 `relative`ly positioned parent (the container root called _OTPInput_).
  
  ## Features
  
  This is the most complete OTP input on the web. It's fully featured 
  
  <details>
  <summary>Supports iOS + Android copy-paste-cut</summary>
  
  https://github.com/guilhermerodz/input-otp/assets/10366880/bdbdc96a-23da-4e89-bff8-990e6a1c4c23
  
  </details>
  
  <details>
  <summary>Automatic OTP code retrieval from transport (e.g SMS)</summary>
  
  By default, this input uses `autocomplete='one-time-code'` and it works as it's a single input. 
  
  https://github.com/guilhermerodz/input-otp/assets/10366880/5705dac6-9159-443b-9c27-b52e93c60ea8
  
  </details>
  
  <details>
  <summary>Supports screen readers (a11y)</summary>
  
  Stripe was my first inspiration to build this library.
  
  Take a look at Stripe's input. The screen reader does not behave like it normally should on a normal single input.
  That's because Stripe's solution is to render a 1-digit input with "clone-divs" rendering a single char per div.
  
  https://github.com/guilhermerodz/input-otp/assets/10366880/3d127aef-147c-4f28-9f6c-57a357a802d0
  
  So we're rendering a single input with invisible/transparent colors instead.
  The screen reader now gets to read it, but there is no appearance. Feel free to build whatever UI you want:
  
  https://github.com/guilhermerodz/input-otp/assets/10366880/718710f0-2198-418c-8fa0-46c05ae5475d
  
  </details>
  
  <details>
  <summary>Supports all keybindings</summary>
  
  Should be able to support all keybindings of a common text input as it's an input.
  
  https://github.com/guilhermerodz/input-otp/assets/10366880/185985c0-af64-48eb-92f9-2e59be9eb78f
  
  </details>
  
  <details>
  <summary>Automatically optimizes for password managers</summary>
  
  
  For password managers such as LastPass, 1Password, Dashlane or Bitwarden, `input-otp` will automatically detect them in the page and increase input width by ~40px to trick the password manager's browser extension and prevent the badge from rendering to the last/right slot of the input.
  
  <img width="670" alt="image" src="https://github.com/guilhermerodz/input-otp/assets/10366880/9bb306ca-deff-4803-aa3d-148c594a540c">
  
  - **This feature is optional and it's enabled by default. You can disable this optimization by adding `pushPasswordManagerStrategy="none"`.**
  - **This feature does not cause visible layout shift.**
  
  ### Auto tracks if the input has space in the right side for the badge
  
  https://github.com/guilhermerodz/input-otp/assets/10366880/bf01af88-1f82-463e-adf4-54a737a92f59
  
  </details>
  
  ## API Reference
  
  ### OTPInput
  
  The root container. Define settings for the input via props. Then, use the `render` prop to create the slots.
  
  #### Props
  
  ```ts
  type OTPInputProps = {
    // The number of slots
    maxLength: number
  
    // Render function creating the slots
    render: (props: RenderProps) => React.ReactElement
    // PS: Render prop is mandatory, except in cases
    // you'd like to consume the original Context API.
    // (search for Context in this docs)
  
    // The class name for the root container
    containerClassName?: string
  
    // Value state controlling the input
    value?: string
    // Setter for the controlled value (or callback for uncontrolled value)
    onChange?: (newValue: string) => unknown
  
    // Callback when the input is complete
    onComplete?: (...args: any[]) => unknown
  
    // Where is the text located within the input
    // Affects click-holding or long-press behavior
    // Default: 'left'
    textAlign?: 'left' | 'center' | 'right'
  
    // Virtual keyboard appearance on mobile
    // Default: 'numeric'
    inputMode?: 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'
  
    // 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 'input-otp';
    // Then use it as: <OTPInput pattern={REGEXP_ONLY_DIGITS}>
    pattern?: string
  
    // While rendering the input slot, you can access both the char and the placeholder, if there's one and it's active.
    placeholder?: string
  
    // Transfomer function that allows pasting, for example, "XXX-XXX" even though the input's regex/pattern doesn't allow hyphen and its max length is 6.
    // Example: (pasted) => pasted.replaceAll('-', '')
    pasteTransformer?: (pastedText: string) => string
  
    // Enabled by default, it's an optional
    // strategy for detecting Password Managers
    // in the page and then shifting their
    // badges to the right side, outside the input.
    pushPasswordManagerStrategy?:
      | 'increase-width'
      | 'none'
  
    // Enabled by default, it's an optional
    // fallback for pages without JS.
    // This is a CSS string. Write your own
    // rules that will be applied as soon as
    // <noscript> is parsed for no-js pages.
    // Use `null` to disable any no-js fallback (not recommended).
    // Default: `
    // [data-input-otp] {
    //   --nojs-bg: white !important;
    //   --nojs-fg: black !important;
    // 
    //   background-color: var(--nojs-bg) !important;
    //   color: var(--nojs-fg) !important;
    //   caret-color: var(--nojs-fg) !important;
    //   letter-spacing: .25em !important;
    //   text-align: center !important;
    //   border: 1px solid var(--nojs-fg) !important;
    //   border-radius: 4px !important;
    //   width: 100% !important;
    // }
    // @media (prefers-color-scheme: dark) {
    //   [data-input-otp] {
    //     --nojs-bg: black !important;
    //     --nojs-fg: white !important;
    //   }
    // }`
    noScriptCSSFallback?: string | null
  }
  ```
  
  ## Examples
  
  <details>
  <summary>Automatic form submission on OTP completion</summary>
  
  ```tsx
  export default function Page() {
    const formRef = useRef<HTMLFormElement>(null)
    const buttonRef = useRef<HTMLButtonElement>(null)
  
    return (
      <form ref={formRef}>
        <OTPInput
          // ... automatically submit the form
          onComplete={() => formRef.current?.submit()}
          // ... or focus the button like as you wish
          onComplete={() => buttonRef.current?.focus()}
        />
  
        <button ref={buttonRef}>Submit</button>
      </form>
    )
  }
  ```
  </details>
  
  <details>
  <summary>Automatically focus the input when the page loads</summary>
  
  ```tsx
  export default function Page() {
    return (
      <form ref={formRef}>
        <OTPInput
          autoFocus
          // Pro tip: accepts all common HTML input props...
        />
      </form>
    )
  }
  ```
  </details>
  
  <details>
  <summary>Usage with react-hook-form</summary>
  Just use it as a regular text input:
  
  ```tsx
  const { register, handleSubmit } = useForm();
  // Then register it like a text input
  <InputOTP {...register("otp")} />
  ```
  
  You can also use react-hook-form's Controller if needed:
  ```tsx
  const { control } = useForm();
  // Then control it like a text input
  <Controller
    name="customOTP"
    control={control}
    defaultValue=""
    render={({ field }) => (
      <OTPInput
        {...field}
        label="Custom OTP"
      />
    )}
  />
  ```
  </details>
  
  ## Caveats
  
  <details>
  <summary>[Workaround] If you want to block specific password manager/badges:</summary>
  
  By default, `input-otp` handles password managers for you.
  The password manager badges should be automatically shifted to the right side.
  
  However, if you still want to block password managers, please disable the `pushPasswordManagerStrategy` and then manually block each PWM.
  
  ```diff
  <OTPInput
    // First, disable library's built-in strategy
    // for shifting badges automatically
  - pushPasswordManagerStrategy="increase-width"
  + pushPasswordManagerStrategy="none"
    // Then, manually add specifics attributes
    // your password manager docs
    // Example: block LastPass
  + data-lpignore="true" 
    // Example: block 1Password
  + data-1p-ignore="true"
  />
  ```
  </details>
  
  <details>
  <summary>[Setting] If you want to customize the `noscript` CSS fallback</summary>
  
  By default, `input-otp` handles cases where JS is not in the page by applying custom CSS styles.
  If you do not like the fallback design and want to apply it to your own, just pass a prop:
  
  ```diff
  // This is the default CSS fallback.
  // Feel free to change it entirely and apply to your design system.
  const NOSCRIPT_CSS_FALLBACK = `
  [data-input-otp] {
    --nojs-bg: white !important;
    --nojs-fg: black !important;
  
    background-color: var(--nojs-bg) !important;
    color: var(--nojs-fg) !important;
    caret-color: var(--nojs-fg) !important;
    letter-spacing: .25em !important;
    text-align: center !important;
    border: 1px solid var(--nojs-fg) !important;
    border-radius: 4px !important;
    width: 100% !important;
  }
  @media (prefers-color-scheme: dark) {
    [data-input-otp] {
      --nojs-bg: black !important;
      --nojs-fg: white !important;
    }
  }`
  
  <OTPInput
    // Pass your own custom styles for when JS is disabled
  + noScriptCSSFallback={NOSCRIPT_CSS_FALLBACK}
  />
  ```
  </details>
  
  <details>
  <summary>[Workaround] If you're experiencing an unwanted border on input focus:</summary>
  
  ```diff
  <OTPInput
    // Add class to the input itself
  + className="focus-visible:ring-0"
    // Not the container
    containerClassName="..."
  />
  ```
  </details>
  
  <details>
  <summary>[Not Recommended] If you want to centralize input text/selection, use the `textAlign` prop:</summary>
  
  ```diff
  <OTPInput
    // customizable but not recommended
  + textAlign="center"
  />
  ```
  
  NOTE: this also affects the selected caret position after a touch/click.
  
  `textAlign="left"`
  <img src="https://github.com/guilhermerodz/input-otp/assets/10366880/685a03df-2b69-4a36-b21c-e453f6098f79" width="300" />
  <br>
  
  `textAlign="center"`
  <img src="https://github.com/guilhermerodz/input-otp/assets/10366880/e0f15b97-ceb8-40c8-96b7-fa3a8896379f" width="300" />
  <br>
  
  `textAlign="right"`
  <img src="https://github.com/guilhermerodz/input-otp/assets/10366880/26697579-0e8b-4dad-8b85-3a036102e951" width="300" />
  <br>
  
  </details>
  
  <details>
  <summary>If you want to use Context props:</summary>
  
  ```diff
  +import { OTPInputContext } from 'input-otp'
  
  function MyForm() {
    return (
      <OTPInput
  -     // First remove the `render` prop
  -     render={...}
      >
        <OTPInputWrapper />
      </OTPInput>
    )
  }
  
  +function OTPInputWrapper() {
  + const inputContext = React.useContext(OTPInputContext)
  + return (
  +   <>
  +     {inputContext.slots.map((slot, idx) => (
  +       <Slot key={idx} {...slot} />
  +     ))}
  +   </>
  + )
  +}
  ```
  
  NOTE: this also affects the selected caret position after a touch/click.
  
  `textAlign="left"`
  <img src="https://github.com/guilhermerodz/input-otp/assets/10366880/685a03df-2b69-4a36-b21c-e453f6098f79" width="300" />
  <br>
  
  `textAlign="center"`
  <img src="https://github.com/guilhermerodz/input-otp/assets/10366880/e0f15b97-ceb8-40c8-96b7-fa3a8896379f" width="300" />
  <br>
  
  `textAlign="right"`
  <img src="https://github.com/guilhermerodz/input-otp/assets/10366880/26697579-0e8b-4dad-8b85-3a036102e951" width="300" />
  <br>
  
  </details>
  
  <details>
  <summary>[DX] Add Tailwind autocomplete for `containerClassname` attribute in VS Code.</summary>
  
  Add the following setting to your `.vscode/settings.json`:
  ```diff
  {
    "tailwindCSS.classAttributes": [
      "class",
      "className",
  +   ".*ClassName"
    ]
  }
  ```
  </details>