@coreui/react-pro 5.22.0 → 5.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/components/autocomplete/CAutocomplete.js +2 -2
- package/dist/cjs/components/autocomplete/CAutocomplete.js.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/one-time-password-input/COneTimePassword.d.ts +73 -0
- package/dist/cjs/components/one-time-password-input/COneTimePassword.js +178 -0
- package/dist/cjs/components/one-time-password-input/COneTimePassword.js.map +1 -0
- package/dist/cjs/components/one-time-password-input/COneTimePasswordInput.d.ts +8 -0
- package/dist/cjs/components/one-time-password-input/COneTimePasswordInput.js +14 -0
- package/dist/cjs/components/one-time-password-input/COneTimePasswordInput.js.map +1 -0
- package/dist/cjs/components/one-time-password-input/index.d.ts +5 -0
- package/dist/cjs/components/one-time-password-input/utils.d.ts +2 -0
- package/dist/cjs/components/one-time-password-input/utils.js +28 -0
- package/dist/cjs/components/one-time-password-input/utils.js.map +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/autocomplete/CAutocomplete.js +2 -2
- package/dist/esm/components/autocomplete/CAutocomplete.js.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/one-time-password-input/COneTimePassword.d.ts +73 -0
- package/dist/esm/components/one-time-password-input/COneTimePassword.js +176 -0
- package/dist/esm/components/one-time-password-input/COneTimePassword.js.map +1 -0
- package/dist/esm/components/one-time-password-input/COneTimePasswordInput.d.ts +8 -0
- package/dist/esm/components/one-time-password-input/COneTimePasswordInput.js +12 -0
- package/dist/esm/components/one-time-password-input/COneTimePasswordInput.js.map +1 -0
- package/dist/esm/components/one-time-password-input/index.d.ts +5 -0
- package/dist/esm/components/one-time-password-input/utils.d.ts +2 -0
- package/dist/esm/components/one-time-password-input/utils.js +25 -0
- package/dist/esm/components/one-time-password-input/utils.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +9 -9
- package/src/components/autocomplete/CAutocomplete.tsx +18 -16
- package/src/components/index.ts +1 -0
- package/src/components/one-time-password-input/COneTimePassword.tsx +462 -0
- package/src/components/one-time-password-input/COneTimePasswordInput.tsx +25 -0
- package/src/components/one-time-password-input/__tests__/COneTimePassword.spec.tsx +361 -0
- package/src/components/one-time-password-input/__tests__/__snapshots__/COneTimePassword.spec.tsx.snap +130 -0
- package/src/components/one-time-password-input/index.ts +6 -0
- package/src/components/one-time-password-input/utils.ts +23 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ChangeEvent,
|
|
3
|
+
Children,
|
|
4
|
+
ClipboardEvent,
|
|
5
|
+
cloneElement,
|
|
6
|
+
FocusEvent,
|
|
7
|
+
forwardRef,
|
|
8
|
+
HTMLAttributes,
|
|
9
|
+
isValidElement,
|
|
10
|
+
KeyboardEvent,
|
|
11
|
+
useCallback,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useRef,
|
|
15
|
+
useState,
|
|
16
|
+
ReactElement,
|
|
17
|
+
} from 'react'
|
|
18
|
+
import PropTypes from 'prop-types'
|
|
19
|
+
import classNames from 'classnames'
|
|
20
|
+
|
|
21
|
+
import { CFormControlWrapper, CFormControlWrapperProps } from '../form/CFormControlWrapper'
|
|
22
|
+
import { COneTimePasswordInput, COneTimePasswordInputProps } from './COneTimePasswordInput'
|
|
23
|
+
|
|
24
|
+
import { isValidInput, extractValidChars } from './utils'
|
|
25
|
+
import { getNextActiveElement, isRTL } from '../../utils'
|
|
26
|
+
|
|
27
|
+
type ComponentWithDisplayName = {
|
|
28
|
+
displayName?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface COneTimePasswordProps
|
|
32
|
+
extends CFormControlWrapperProps,
|
|
33
|
+
Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
34
|
+
/**
|
|
35
|
+
* Function to generate aria-label for each input field. Receives current index (0-based) and total number of inputs.
|
|
36
|
+
*/
|
|
37
|
+
ariaLabel?: (index: number, total: number) => string
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Automatically submit the form when all one time password fields are filled.
|
|
41
|
+
*/
|
|
42
|
+
autoSubmit?: boolean
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A string of all className you want applied to the React.js OTP component.
|
|
46
|
+
*/
|
|
47
|
+
className?: string
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initial value for uncontrolled React.js one time password input.
|
|
51
|
+
*/
|
|
52
|
+
defaultValue?: string | number
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Disable all one time password (OTP) input fields.
|
|
56
|
+
*/
|
|
57
|
+
disabled?: boolean
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* ID attribute for the hidden input field.
|
|
61
|
+
*/
|
|
62
|
+
id?: string
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Enforce sequential input (users must fill fields in order).
|
|
66
|
+
*/
|
|
67
|
+
linear?: boolean
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Show input as password (masked characters).
|
|
71
|
+
*/
|
|
72
|
+
masked?: boolean
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Name attribute for the hidden input field.
|
|
76
|
+
*/
|
|
77
|
+
name?: string
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Callback triggered when the React.js one time password (OTP) value changes.
|
|
81
|
+
*/
|
|
82
|
+
onChange?: (value: string) => void
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Callback triggered when all React.js one time password (OTP) fields are filled.
|
|
86
|
+
*/
|
|
87
|
+
onComplete?: (value: string) => void
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Placeholder text for input fields. Single character applies to all fields, longer strings apply character-by-character.
|
|
91
|
+
*/
|
|
92
|
+
placeholder?: string
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Make React.js OTP input component read-only.
|
|
96
|
+
*/
|
|
97
|
+
readOnly?: boolean
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Makes the input field required for form validation.
|
|
101
|
+
*/
|
|
102
|
+
required?: boolean
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets the visual size of the React.js one time password (OTP) input. Use 'sm' for small or 'lg' for large input fields.
|
|
106
|
+
*/
|
|
107
|
+
size?: 'sm' | 'lg'
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Input validation type: 'number' for digits only, or 'text' for free text.
|
|
111
|
+
*/
|
|
112
|
+
type?: 'number' | 'text'
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Current value for controlled OTP input.
|
|
116
|
+
*/
|
|
117
|
+
value?: string | number
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const COneTimePassword = forwardRef<HTMLDivElement, COneTimePasswordProps>(
|
|
121
|
+
(
|
|
122
|
+
{
|
|
123
|
+
ariaLabel = (index: number, total: number) => `Digit ${index + 1} of ${total}`,
|
|
124
|
+
autoSubmit = false,
|
|
125
|
+
children,
|
|
126
|
+
className,
|
|
127
|
+
defaultValue,
|
|
128
|
+
disabled = false,
|
|
129
|
+
feedback,
|
|
130
|
+
feedbackInvalid,
|
|
131
|
+
feedbackValid,
|
|
132
|
+
floatingClassName,
|
|
133
|
+
floatingLabel,
|
|
134
|
+
id,
|
|
135
|
+
invalid,
|
|
136
|
+
label,
|
|
137
|
+
linear = true,
|
|
138
|
+
masked = false,
|
|
139
|
+
name,
|
|
140
|
+
onChange,
|
|
141
|
+
onComplete,
|
|
142
|
+
placeholder,
|
|
143
|
+
readOnly = false,
|
|
144
|
+
required = false,
|
|
145
|
+
size,
|
|
146
|
+
text,
|
|
147
|
+
tooltipFeedback,
|
|
148
|
+
type = 'number',
|
|
149
|
+
valid,
|
|
150
|
+
value,
|
|
151
|
+
...rest
|
|
152
|
+
},
|
|
153
|
+
ref
|
|
154
|
+
) => {
|
|
155
|
+
const inputRefs = useRef<(HTMLInputElement | null)[]>([])
|
|
156
|
+
const hiddenInputRef = useRef<HTMLInputElement>(null)
|
|
157
|
+
|
|
158
|
+
// Count valid OTP input children
|
|
159
|
+
const inputCount = useMemo(() => {
|
|
160
|
+
return Children.count(children)
|
|
161
|
+
}, [children])
|
|
162
|
+
|
|
163
|
+
const [inputValues, setInputValues] = useState<string[]>(() => {
|
|
164
|
+
const initialValue = String(value ?? defaultValue ?? '')
|
|
165
|
+
return Array.from({ length: inputCount }, (_, i) => initialValue[i] || '')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Update input values when value prop changes (controlled mode)
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (value !== undefined) {
|
|
171
|
+
const valueString = String(value)
|
|
172
|
+
setInputValues(Array.from({ length: inputCount }, (_, i) => valueString[i] || ''))
|
|
173
|
+
}
|
|
174
|
+
}, [value, inputCount])
|
|
175
|
+
|
|
176
|
+
// Update hidden input and trigger events
|
|
177
|
+
const updateValue = useCallback(
|
|
178
|
+
(newValues: string[]) => {
|
|
179
|
+
const newValue = newValues.join('')
|
|
180
|
+
|
|
181
|
+
if (hiddenInputRef.current) {
|
|
182
|
+
hiddenInputRef.current.value = newValue
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onChange?.(newValue)
|
|
186
|
+
|
|
187
|
+
if (newValue.length === inputCount) {
|
|
188
|
+
onComplete?.(newValue)
|
|
189
|
+
|
|
190
|
+
if (autoSubmit) {
|
|
191
|
+
const form = hiddenInputRef.current?.closest('form')
|
|
192
|
+
if (form && typeof form.requestSubmit === 'function') {
|
|
193
|
+
form.requestSubmit()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
[autoSubmit, onChange, onComplete, inputCount]
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const handleInputChange = useCallback(
|
|
202
|
+
(index: number, event: ChangeEvent<HTMLInputElement>) => {
|
|
203
|
+
const inputValue = event.target.value
|
|
204
|
+
|
|
205
|
+
if (inputValue.length === 1 && !isValidInput(inputValue, type)) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const newValues = [...inputValues]
|
|
210
|
+
newValues[index] = inputValue.length === 1 ? inputValue : ''
|
|
211
|
+
|
|
212
|
+
setInputValues(newValues)
|
|
213
|
+
updateValue(newValues)
|
|
214
|
+
|
|
215
|
+
if (inputValue.length === 1) {
|
|
216
|
+
const nextInput = getNextActiveElement(
|
|
217
|
+
inputRefs.current as HTMLInputElement[],
|
|
218
|
+
event.target,
|
|
219
|
+
true,
|
|
220
|
+
false
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
nextInput?.focus()
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
[inputValues, type, updateValue]
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
const handleInputFocus = useCallback(
|
|
230
|
+
(event: FocusEvent<HTMLInputElement>) => {
|
|
231
|
+
const { target } = event
|
|
232
|
+
|
|
233
|
+
if (target.value) {
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
target.select()
|
|
236
|
+
}, 0)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (linear) {
|
|
241
|
+
const firstEmptyInput = inputRefs.current.find((input) => !input?.value)
|
|
242
|
+
if (firstEmptyInput && firstEmptyInput !== target) {
|
|
243
|
+
firstEmptyInput.focus()
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
[linear]
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
const handleKeyDown = useCallback(
|
|
251
|
+
(event: KeyboardEvent<HTMLInputElement>) => {
|
|
252
|
+
const { key, target } = event
|
|
253
|
+
|
|
254
|
+
if (key === 'Backspace' && (target as HTMLInputElement).value === '') {
|
|
255
|
+
const newValues = [...inputValues]
|
|
256
|
+
const prevInput = getNextActiveElement(
|
|
257
|
+
inputRefs.current as HTMLInputElement[],
|
|
258
|
+
target as HTMLInputElement,
|
|
259
|
+
false,
|
|
260
|
+
false
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if (prevInput) {
|
|
264
|
+
const prevIndex = inputRefs.current.indexOf(prevInput as HTMLInputElement)
|
|
265
|
+
if (prevIndex !== -1) {
|
|
266
|
+
newValues[prevIndex] = ''
|
|
267
|
+
setInputValues(newValues)
|
|
268
|
+
updateValue(newValues)
|
|
269
|
+
prevInput.focus()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (key === 'ArrowRight') {
|
|
277
|
+
if (linear && (target as HTMLInputElement).value === '') {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const shouldMoveNext = !isRTL(target as HTMLInputElement)
|
|
282
|
+
const nextInput = getNextActiveElement(
|
|
283
|
+
inputRefs.current as HTMLInputElement[],
|
|
284
|
+
target as HTMLInputElement,
|
|
285
|
+
shouldMoveNext,
|
|
286
|
+
false
|
|
287
|
+
)
|
|
288
|
+
nextInput?.focus()
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (key === 'ArrowLeft') {
|
|
293
|
+
const shouldMoveNext = isRTL(target as HTMLInputElement)
|
|
294
|
+
const prevInput = getNextActiveElement(
|
|
295
|
+
inputRefs.current as HTMLInputElement[],
|
|
296
|
+
target as HTMLInputElement,
|
|
297
|
+
shouldMoveNext,
|
|
298
|
+
false
|
|
299
|
+
)
|
|
300
|
+
prevInput?.focus()
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
[inputValues, linear, updateValue]
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
const handlePaste = useCallback(
|
|
307
|
+
(index: number, event: ClipboardEvent<HTMLInputElement>) => {
|
|
308
|
+
event.preventDefault()
|
|
309
|
+
const pastedData = event.clipboardData.getData('text')
|
|
310
|
+
const validChars = extractValidChars(pastedData, type)
|
|
311
|
+
|
|
312
|
+
if (!validChars) return
|
|
313
|
+
|
|
314
|
+
const newValues = [...inputValues]
|
|
315
|
+
const startIndex = index
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i < validChars.length && startIndex + i < inputCount; i++) {
|
|
318
|
+
newValues[startIndex + i] = validChars[i]
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
setInputValues(newValues)
|
|
322
|
+
updateValue(newValues)
|
|
323
|
+
|
|
324
|
+
// Focus the next empty input or the last filled input
|
|
325
|
+
const nextEmptyIndex = startIndex + validChars.length
|
|
326
|
+
if (nextEmptyIndex < inputCount) {
|
|
327
|
+
inputRefs.current[nextEmptyIndex]?.focus()
|
|
328
|
+
} else {
|
|
329
|
+
inputRefs.current.at(-1)?.focus()
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
[inputValues, type, updateValue, inputCount]
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
let inputIndex = 0
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<CFormControlWrapper
|
|
339
|
+
describedby={rest['aria-describedby']}
|
|
340
|
+
feedback={feedback}
|
|
341
|
+
feedbackInvalid={feedbackInvalid}
|
|
342
|
+
feedbackValid={feedbackValid}
|
|
343
|
+
floatingClassName={floatingClassName}
|
|
344
|
+
floatingLabel={floatingLabel}
|
|
345
|
+
id={id}
|
|
346
|
+
invalid={invalid}
|
|
347
|
+
label={label}
|
|
348
|
+
text={text}
|
|
349
|
+
tooltipFeedback={tooltipFeedback}
|
|
350
|
+
valid={valid}
|
|
351
|
+
>
|
|
352
|
+
<div
|
|
353
|
+
className={classNames(
|
|
354
|
+
'form-otp',
|
|
355
|
+
{
|
|
356
|
+
[`form-otp-${size}`]: size,
|
|
357
|
+
},
|
|
358
|
+
className
|
|
359
|
+
)}
|
|
360
|
+
role="group"
|
|
361
|
+
ref={ref}
|
|
362
|
+
{...rest}
|
|
363
|
+
>
|
|
364
|
+
{Children.map(children, (child, index) => {
|
|
365
|
+
if (
|
|
366
|
+
!isValidElement(child) ||
|
|
367
|
+
(child.type as ComponentWithDisplayName)?.displayName !== 'COneTimePasswordInput'
|
|
368
|
+
) {
|
|
369
|
+
return child
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const inputChild = child as ReactElement<
|
|
373
|
+
COneTimePasswordInputProps & React.RefAttributes<HTMLInputElement>,
|
|
374
|
+
typeof COneTimePasswordInput
|
|
375
|
+
>
|
|
376
|
+
const currentInputIndex = inputIndex++
|
|
377
|
+
|
|
378
|
+
return cloneElement(inputChild, {
|
|
379
|
+
...inputChild.props,
|
|
380
|
+
type: masked ? 'password' : 'text',
|
|
381
|
+
className: classNames(
|
|
382
|
+
{
|
|
383
|
+
'is-invalid': invalid,
|
|
384
|
+
'is-valid': valid,
|
|
385
|
+
},
|
|
386
|
+
inputChild.props.className
|
|
387
|
+
),
|
|
388
|
+
id: inputChild.props.id || (id ? `${id}-${currentInputIndex}` : undefined),
|
|
389
|
+
name: inputChild.props.name || (name ? `${name}-${currentInputIndex}` : undefined),
|
|
390
|
+
placeholder:
|
|
391
|
+
inputChild.props.placeholder ||
|
|
392
|
+
(placeholder && placeholder.length > 1
|
|
393
|
+
? placeholder[currentInputIndex]
|
|
394
|
+
: placeholder),
|
|
395
|
+
value: inputValues[currentInputIndex] || '',
|
|
396
|
+
tabIndex: currentInputIndex === 0 ? 0 : inputValues[currentInputIndex - 1] ? 0 : -1,
|
|
397
|
+
disabled: disabled || inputChild.props.disabled,
|
|
398
|
+
readOnly: readOnly || inputChild.props.readOnly,
|
|
399
|
+
required: required || inputChild.props.required,
|
|
400
|
+
'aria-label':
|
|
401
|
+
inputChild.props['aria-label'] || ariaLabel(currentInputIndex, inputCount),
|
|
402
|
+
|
|
403
|
+
inputMode: type === 'number' ? 'numeric' : 'text',
|
|
404
|
+
pattern: type === 'number' ? '[0-9]*' : '.*',
|
|
405
|
+
onInput: (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
406
|
+
handleInputChange(currentInputIndex, event)
|
|
407
|
+
inputChild.props.onChange?.(event)
|
|
408
|
+
},
|
|
409
|
+
onFocus: (event: React.FocusEvent<HTMLInputElement>) => {
|
|
410
|
+
handleInputFocus(event)
|
|
411
|
+
inputChild.props.onFocus?.(event)
|
|
412
|
+
},
|
|
413
|
+
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
414
|
+
handleKeyDown(currentInputIndex, event)
|
|
415
|
+
inputChild.props.onKeyDown?.(event)
|
|
416
|
+
},
|
|
417
|
+
onPaste: (event: React.ClipboardEvent<HTMLInputElement>) => {
|
|
418
|
+
handlePaste(index, event)
|
|
419
|
+
inputChild.props.onPaste?.(event)
|
|
420
|
+
},
|
|
421
|
+
ref: (el: HTMLInputElement | null) => {
|
|
422
|
+
inputRefs.current[currentInputIndex] = el
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
})}
|
|
426
|
+
<input
|
|
427
|
+
type="hidden"
|
|
428
|
+
id={id}
|
|
429
|
+
name={name}
|
|
430
|
+
value={inputValues.join('')}
|
|
431
|
+
disabled={disabled}
|
|
432
|
+
ref={hiddenInputRef}
|
|
433
|
+
/>
|
|
434
|
+
</div>
|
|
435
|
+
</CFormControlWrapper>
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
COneTimePassword.propTypes = {
|
|
441
|
+
ariaLabel: PropTypes.func,
|
|
442
|
+
autoSubmit: PropTypes.bool,
|
|
443
|
+
children: PropTypes.node,
|
|
444
|
+
className: PropTypes.string,
|
|
445
|
+
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
446
|
+
disabled: PropTypes.bool,
|
|
447
|
+
id: PropTypes.string,
|
|
448
|
+
linear: PropTypes.bool,
|
|
449
|
+
masked: PropTypes.bool,
|
|
450
|
+
name: PropTypes.string,
|
|
451
|
+
onChange: PropTypes.func,
|
|
452
|
+
onComplete: PropTypes.func,
|
|
453
|
+
placeholder: PropTypes.string,
|
|
454
|
+
readOnly: PropTypes.bool,
|
|
455
|
+
required: PropTypes.bool,
|
|
456
|
+
size: PropTypes.oneOf(['sm', 'lg']),
|
|
457
|
+
type: PropTypes.oneOf(['number', 'text']),
|
|
458
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
459
|
+
...CFormControlWrapper.propTypes,
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
COneTimePassword.displayName = 'COneTimePassword'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { forwardRef, InputHTMLAttributes } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
|
|
4
|
+
export interface COneTimePasswordInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
5
|
+
/**
|
|
6
|
+
* A string of all className you want applied to the component.
|
|
7
|
+
*/
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const COneTimePasswordInput = forwardRef<HTMLInputElement, COneTimePasswordInputProps>(
|
|
12
|
+
({ className, ...rest }, ref) => {
|
|
13
|
+
return (
|
|
14
|
+
<input
|
|
15
|
+
className={classNames('form-otp-control', className)}
|
|
16
|
+
maxLength={1}
|
|
17
|
+
autoComplete="off"
|
|
18
|
+
ref={ref}
|
|
19
|
+
{...rest}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
COneTimePasswordInput.displayName = 'COneTimePasswordInput'
|