@westpac/ui 0.29.0 → 0.30.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/CHANGELOG.md +6 -0
- package/dist/component-type.json +1 -1
- package/dist/components/pass-code/pass-code.component.d.ts +40 -2
- package/dist/components/pass-code/pass-code.component.js +79 -30
- package/dist/components/pass-code/pass-code.types.d.ts +35 -3
- package/dist/css/westpac-ui.css +3 -0
- package/dist/css/westpac-ui.min.css +3 -0
- package/package.json +3 -3
- package/src/components/pass-code/pass-code.component.tsx +140 -80
- package/src/components/pass-code/pass-code.types.ts +39 -3
|
@@ -1,2 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PassCodeRef } from './pass-code.types.js';
|
|
3
|
+
export declare const PassCode: React.ForwardRefExoticComponent<{
|
|
4
|
+
length: number;
|
|
5
|
+
onBlur?: (index: number, event: React.FocusEvent<HTMLInputElement>) => void;
|
|
6
|
+
onChange?: (passcode: string[]) => void;
|
|
7
|
+
onComplete?: (passcode: string) => void;
|
|
8
|
+
type?: "numbers" | "letters" | "alphanumeric";
|
|
9
|
+
value?: string[];
|
|
10
|
+
} & import("tailwind-variants").VariantProps<import("tailwind-variants").TVReturnType<{
|
|
11
|
+
[key: string]: {
|
|
12
|
+
[key: string]: import("tailwind-variants").ClassValue | {
|
|
13
|
+
base?: import("tailwind-variants").ClassValue;
|
|
14
|
+
input?: import("tailwind-variants").ClassValue;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
} | {
|
|
18
|
+
[x: string]: {
|
|
19
|
+
[x: string]: import("tailwind-variants").ClassValue | {
|
|
20
|
+
base?: import("tailwind-variants").ClassValue;
|
|
21
|
+
input?: import("tailwind-variants").ClassValue;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
} | {}, {
|
|
25
|
+
base: string;
|
|
26
|
+
input: string;
|
|
27
|
+
}, undefined, TVConfig<V, EV>, {
|
|
28
|
+
[key: string]: {
|
|
29
|
+
[key: string]: import("tailwind-variants").ClassValue | {
|
|
30
|
+
base?: import("tailwind-variants").ClassValue;
|
|
31
|
+
input?: import("tailwind-variants").ClassValue;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
} | {}, {
|
|
35
|
+
base: string;
|
|
36
|
+
input: string;
|
|
37
|
+
}, import("tailwind-variants").TVReturnType<unknown, {
|
|
38
|
+
base: string;
|
|
39
|
+
input: string;
|
|
40
|
+
}, undefined, TVConfig<V, EV>, unknown, unknown, undefined>>> & Omit<React.HTMLAttributes<Element>, "onChange"> & React.RefAttributes<PassCodeRef>>;
|
|
@@ -13,39 +13,65 @@ function _extends() {
|
|
|
13
13
|
};
|
|
14
14
|
return _extends.apply(this, arguments);
|
|
15
15
|
}
|
|
16
|
-
import React, { useCallback, useRef, useState } from 'react';
|
|
16
|
+
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
|
17
17
|
import { Input } from '../index.js';
|
|
18
18
|
import { styles as passCodeStyles } from './pass-code.styles.js';
|
|
19
|
-
export
|
|
20
|
-
const [
|
|
19
|
+
export const PassCode = forwardRef(({ length , value , onChange , onComplete , className , type ='alphanumeric' , onBlur , ...props }, ref)=>{
|
|
20
|
+
const [internalPasscode, setInternalPasscode] = useState(Array.from({
|
|
21
21
|
length
|
|
22
|
-
}).map(()=>
|
|
22
|
+
}).map(()=>''));
|
|
23
|
+
const passcode = value ? value : internalPasscode;
|
|
23
24
|
const inputRefs = useRef([]);
|
|
24
25
|
const styles = passCodeStyles({});
|
|
26
|
+
useImperativeHandle(ref, ()=>{
|
|
27
|
+
return {
|
|
28
|
+
focus: ()=>{
|
|
29
|
+
var _inputRefs_current_;
|
|
30
|
+
(_inputRefs_current_ = inputRefs.current[0]) === null || _inputRefs_current_ === void 0 ? void 0 : _inputRefs_current_.focus();
|
|
31
|
+
},
|
|
32
|
+
clear: ()=>{
|
|
33
|
+
setInternalPasscode(Array.from({
|
|
34
|
+
length
|
|
35
|
+
}).map(()=>''));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
});
|
|
25
39
|
const handleChange = useCallback((index, event)=>{
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
const inputValue = event.target.value.slice(-1);
|
|
41
|
+
if (type === 'numbers' && /^\d$/.test(inputValue) || type === 'letters' && /^[a-zA-Z]$/.test(inputValue) || type === 'alphanumeric' && /^[a-zA-Z0-9]$/.test(inputValue)) {
|
|
42
|
+
const newPasscode = [
|
|
43
|
+
...passcode.slice(0, index),
|
|
44
|
+
inputValue,
|
|
45
|
+
...passcode.slice(index + 1)
|
|
46
|
+
];
|
|
47
|
+
if (onChange) {
|
|
48
|
+
onChange(newPasscode);
|
|
49
|
+
} else {
|
|
50
|
+
setInternalPasscode(newPasscode);
|
|
51
|
+
}
|
|
52
|
+
if (index < length - 1 && inputValue !== '') {
|
|
53
|
+
var _inputRefs_current_;
|
|
54
|
+
(_inputRefs_current_ = inputRefs.current[index + 1]) === null || _inputRefs_current_ === void 0 ? void 0 : _inputRefs_current_.focus();
|
|
55
|
+
}
|
|
56
|
+
if (newPasscode.filter((passcode)=>!passcode).length === 0 && onComplete) {
|
|
57
|
+
onComplete(newPasscode.join(''));
|
|
58
|
+
}
|
|
39
59
|
}
|
|
40
60
|
}, [
|
|
41
61
|
passcode,
|
|
42
62
|
length,
|
|
43
|
-
|
|
63
|
+
onChange,
|
|
64
|
+
onComplete,
|
|
65
|
+
type
|
|
44
66
|
]);
|
|
45
67
|
const handlePaste = useCallback((index, event)=>{
|
|
46
68
|
event.preventDefault();
|
|
47
69
|
const pastedData = event.clipboardData.getData('text');
|
|
48
|
-
const validData = pastedData.slice(0, length - index).split('')
|
|
70
|
+
const validData = pastedData.slice(0, length - index).split('').filter((char)=>{
|
|
71
|
+
if (type === 'numbers') return /^\d$/.test(char);
|
|
72
|
+
if (type === 'letters') return /^[a-zA-Z]$/.test(char);
|
|
73
|
+
return /^[a-zA-Z0-9]$/.test(char);
|
|
74
|
+
});
|
|
49
75
|
const previousSlice = passcode.slice(0, index);
|
|
50
76
|
const afterSlice = passcode.slice(index);
|
|
51
77
|
const newPasscode = [
|
|
@@ -55,24 +81,34 @@ export function PassCode({ length , onComplete , className , ...props }) {
|
|
|
55
81
|
...afterSlice.slice(validData.length)
|
|
56
82
|
]
|
|
57
83
|
].slice(0, length);
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
if (onChange) {
|
|
85
|
+
onChange(newPasscode);
|
|
86
|
+
} else {
|
|
87
|
+
setInternalPasscode(newPasscode);
|
|
88
|
+
}
|
|
89
|
+
if (newPasscode.filter((passcode)=>!passcode).length === 0 && onComplete) {
|
|
60
90
|
onComplete(newPasscode.join(''));
|
|
61
91
|
}
|
|
62
92
|
}, [
|
|
93
|
+
passcode,
|
|
63
94
|
length,
|
|
95
|
+
onChange,
|
|
64
96
|
onComplete,
|
|
65
|
-
|
|
97
|
+
type
|
|
66
98
|
]);
|
|
67
99
|
const handleKeyDown = useCallback((index, event)=>{
|
|
68
|
-
if (event.key === 'Backspace'
|
|
100
|
+
if (event.key === 'Backspace') {
|
|
69
101
|
event.preventDefault();
|
|
70
102
|
const newPasscode = [
|
|
71
103
|
...passcode.slice(0, index),
|
|
72
|
-
|
|
104
|
+
'',
|
|
73
105
|
...passcode.slice(index + 1)
|
|
74
106
|
];
|
|
75
|
-
|
|
107
|
+
if (onChange) {
|
|
108
|
+
onChange(newPasscode);
|
|
109
|
+
} else {
|
|
110
|
+
setInternalPasscode(newPasscode);
|
|
111
|
+
}
|
|
76
112
|
const previousInput = inputRefs.current[index - 1];
|
|
77
113
|
const currentInput = inputRefs.current[index];
|
|
78
114
|
if (previousInput) {
|
|
@@ -83,7 +119,8 @@ export function PassCode({ length , onComplete , className , ...props }) {
|
|
|
83
119
|
}
|
|
84
120
|
}
|
|
85
121
|
}, [
|
|
86
|
-
passcode
|
|
122
|
+
passcode,
|
|
123
|
+
onChange
|
|
87
124
|
]);
|
|
88
125
|
const handleFocus = useCallback((index)=>{
|
|
89
126
|
var _inputRefs_current_index;
|
|
@@ -91,20 +128,32 @@ export function PassCode({ length , onComplete , className , ...props }) {
|
|
|
91
128
|
}, [
|
|
92
129
|
inputRefs
|
|
93
130
|
]);
|
|
131
|
+
const handleBlur = useCallback((index, event)=>{
|
|
132
|
+
if (onBlur) {
|
|
133
|
+
onBlur(index, event);
|
|
134
|
+
}
|
|
135
|
+
}, [
|
|
136
|
+
onBlur
|
|
137
|
+
]);
|
|
94
138
|
return React.createElement("div", _extends({}, props, {
|
|
95
139
|
className: styles.base({
|
|
96
140
|
className
|
|
97
141
|
})
|
|
98
|
-
}),
|
|
142
|
+
}), Array.from({
|
|
143
|
+
length
|
|
144
|
+
}).map((_, index)=>React.createElement(Input, {
|
|
99
145
|
size: "large",
|
|
100
146
|
key: index,
|
|
101
|
-
value:
|
|
147
|
+
value: passcode[index] || '',
|
|
102
148
|
onChange: (e)=>handleChange(index, e),
|
|
103
149
|
onPaste: (e)=>handlePaste(index, e),
|
|
104
150
|
onKeyDown: (e)=>handleKeyDown(index, e),
|
|
105
151
|
onFocus: ()=>handleFocus(index),
|
|
152
|
+
onBlur: (e)=>handleBlur(index, e),
|
|
106
153
|
ref: (input)=>inputRefs.current[index] = input,
|
|
107
154
|
className: styles.input({}),
|
|
108
|
-
"aria-label": `Passcode digit ${index + 1}
|
|
155
|
+
"aria-label": `Passcode digit ${index + 1}`,
|
|
156
|
+
inputMode: type === 'numbers' ? 'numeric' : 'text'
|
|
109
157
|
})));
|
|
110
|
-
}
|
|
158
|
+
});
|
|
159
|
+
PassCode.displayName = 'PassCode';
|
|
@@ -1,7 +1,39 @@
|
|
|
1
|
-
import { HTMLAttributes } from 'react';
|
|
1
|
+
import { FocusEvent, HTMLAttributes } from 'react';
|
|
2
2
|
import { type VariantProps } from 'tailwind-variants';
|
|
3
3
|
import { styles } from './pass-code.styles.js';
|
|
4
4
|
export type PassCodeProps = {
|
|
5
|
+
/**
|
|
6
|
+
* Number of passcode inputs
|
|
7
|
+
*/
|
|
5
8
|
length: number;
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Callback when the input is blurred
|
|
11
|
+
*/
|
|
12
|
+
onBlur?: (index: number, event: FocusEvent<HTMLInputElement>) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Callback when the input value changes
|
|
15
|
+
*/
|
|
16
|
+
onChange?: (passcode: string[]) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Callback when the passcode is completely typed
|
|
19
|
+
*/
|
|
20
|
+
onComplete?: (passcode: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Type of passcode input
|
|
23
|
+
*/
|
|
24
|
+
type?: 'numbers' | 'letters' | 'alphanumeric';
|
|
25
|
+
/**
|
|
26
|
+
* Value of the passcode input
|
|
27
|
+
*/
|
|
28
|
+
value?: string[];
|
|
29
|
+
} & VariantProps<typeof styles> & Omit<HTMLAttributes<Element>, 'onChange'>;
|
|
30
|
+
export type PassCodeRef = {
|
|
31
|
+
/**
|
|
32
|
+
* Clear the passcode input, for non-controlled component only
|
|
33
|
+
*/
|
|
34
|
+
clear: () => void;
|
|
35
|
+
/**
|
|
36
|
+
* Focus on the first input
|
|
37
|
+
*/
|
|
38
|
+
focus: () => void;
|
|
39
|
+
};
|
package/dist/css/westpac-ui.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@westpac/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -257,9 +257,9 @@
|
|
|
257
257
|
"typescript": "^5.5.4",
|
|
258
258
|
"vite": "^5.2.12",
|
|
259
259
|
"vitest": "^0.30.1",
|
|
260
|
-
"@westpac/
|
|
260
|
+
"@westpac/eslint-config": "~0.4.0",
|
|
261
261
|
"@westpac/test-config": "~0.0.0",
|
|
262
|
-
"@westpac/
|
|
262
|
+
"@westpac/ts-config": "~0.0.0"
|
|
263
263
|
},
|
|
264
264
|
"dependencies": {
|
|
265
265
|
"@duetds/date-picker": "~1.4.0",
|
|
@@ -1,97 +1,157 @@
|
|
|
1
|
+
/* eslint-disable sonarjs/cognitive-complexity */
|
|
1
2
|
'use client';
|
|
2
3
|
|
|
3
|
-
import React, {
|
|
4
|
+
import React, {
|
|
5
|
+
ChangeEvent,
|
|
6
|
+
ClipboardEvent,
|
|
7
|
+
FocusEvent,
|
|
8
|
+
KeyboardEvent,
|
|
9
|
+
forwardRef,
|
|
10
|
+
useCallback,
|
|
11
|
+
useImperativeHandle,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
} from 'react';
|
|
4
15
|
|
|
5
16
|
import { Input } from '../index.js';
|
|
6
17
|
|
|
7
18
|
import { styles as passCodeStyles } from './pass-code.styles.js';
|
|
8
|
-
import {
|
|
19
|
+
import { PassCodeProps, PassCodeRef } from './pass-code.types.js';
|
|
9
20
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
export const PassCode = forwardRef<PassCodeRef, PassCodeProps>(
|
|
22
|
+
({ length, value, onChange, onComplete, className, type = 'alphanumeric', onBlur, ...props }, ref) => {
|
|
23
|
+
const [internalPasscode, setInternalPasscode] = useState<string[]>(Array.from({ length }).map(() => ''));
|
|
24
|
+
const passcode = value ? value : internalPasscode;
|
|
25
|
+
const inputRefs = useRef<Array<HTMLInputElement | null>>([]);
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
const styles = passCodeStyles({});
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
useImperativeHandle(ref, () => ({
|
|
30
|
+
focus: () => {
|
|
31
|
+
inputRefs.current[0]?.focus();
|
|
32
|
+
},
|
|
33
|
+
clear: () => {
|
|
34
|
+
setInternalPasscode(Array.from({ length }).map(() => ''));
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
19
37
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
const handleChange = useCallback(
|
|
39
|
+
(index: number, event: ChangeEvent<HTMLInputElement>) => {
|
|
40
|
+
const inputValue = event.target.value.slice(-1);
|
|
41
|
+
if (
|
|
42
|
+
(type === 'numbers' && /^\d$/.test(inputValue)) ||
|
|
43
|
+
(type === 'letters' && /^[a-zA-Z]$/.test(inputValue)) ||
|
|
44
|
+
(type === 'alphanumeric' && /^[a-zA-Z0-9]$/.test(inputValue))
|
|
45
|
+
) {
|
|
46
|
+
const newPasscode = [...passcode.slice(0, index), inputValue, ...passcode.slice(index + 1)];
|
|
47
|
+
if (onChange) {
|
|
48
|
+
onChange(newPasscode);
|
|
49
|
+
} else {
|
|
50
|
+
setInternalPasscode(newPasscode);
|
|
51
|
+
}
|
|
23
52
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const handlePaste = useCallback(
|
|
38
|
-
(index: number, event: ClipboardEvent<HTMLInputElement>) => {
|
|
39
|
-
event.preventDefault();
|
|
40
|
-
const pastedData = event.clipboardData.getData('text');
|
|
41
|
-
const validData = pastedData.slice(0, length - index).split('');
|
|
42
|
-
const previousSlice = passcode.slice(0, index);
|
|
43
|
-
const afterSlice = passcode.slice(index);
|
|
44
|
-
const newPasscode = [...previousSlice, ...[...validData, ...afterSlice.slice(validData.length)]].slice(0, length);
|
|
45
|
-
setPasscode(newPasscode);
|
|
46
|
-
if (newPasscode.filter(passcode => !passcode).length === 0) {
|
|
47
|
-
onComplete(newPasscode.join(''));
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
[length, onComplete, passcode],
|
|
51
|
-
);
|
|
53
|
+
// Move to the next input if available
|
|
54
|
+
if (index < length - 1 && inputValue !== '') {
|
|
55
|
+
inputRefs.current[index + 1]?.focus();
|
|
56
|
+
}
|
|
57
|
+
if (newPasscode.filter(passcode => !passcode).length === 0 && onComplete) {
|
|
58
|
+
onComplete(newPasscode.join(''));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
[passcode, length, onChange, onComplete, type],
|
|
63
|
+
);
|
|
52
64
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (event.key === 'Backspace' && index > 0) {
|
|
65
|
+
const handlePaste = useCallback(
|
|
66
|
+
(index: number, event: ClipboardEvent<HTMLInputElement>) => {
|
|
56
67
|
event.preventDefault();
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
const pastedData = event.clipboardData.getData('text');
|
|
69
|
+
const validData = pastedData
|
|
70
|
+
.slice(0, length - index)
|
|
71
|
+
.split('')
|
|
72
|
+
.filter(char => {
|
|
73
|
+
if (type === 'numbers') return /^\d$/.test(char);
|
|
74
|
+
if (type === 'letters') return /^[a-zA-Z]$/.test(char);
|
|
75
|
+
return /^[a-zA-Z0-9]$/.test(char);
|
|
76
|
+
});
|
|
77
|
+
const previousSlice = passcode.slice(0, index);
|
|
78
|
+
const afterSlice = passcode.slice(index);
|
|
79
|
+
const newPasscode = [...previousSlice, ...[...validData, ...afterSlice.slice(validData.length)]].slice(
|
|
80
|
+
0,
|
|
81
|
+
length,
|
|
82
|
+
);
|
|
83
|
+
if (onChange) {
|
|
84
|
+
onChange(newPasscode);
|
|
85
|
+
} else {
|
|
86
|
+
setInternalPasscode(newPasscode);
|
|
87
|
+
}
|
|
88
|
+
if (newPasscode.filter(passcode => !passcode).length === 0 && onComplete) {
|
|
89
|
+
onComplete(newPasscode.join(''));
|
|
63
90
|
}
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
},
|
|
92
|
+
[passcode, length, onChange, onComplete, type],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const handleKeyDown = useCallback(
|
|
96
|
+
(index: number, event: KeyboardEvent<HTMLInputElement>) => {
|
|
97
|
+
if (event.key === 'Backspace') {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
const newPasscode = [...passcode.slice(0, index), '', ...passcode.slice(index + 1)];
|
|
100
|
+
if (onChange) {
|
|
101
|
+
onChange(newPasscode);
|
|
102
|
+
} else {
|
|
103
|
+
setInternalPasscode(newPasscode);
|
|
104
|
+
}
|
|
105
|
+
const previousInput = inputRefs.current[index - 1];
|
|
106
|
+
const currentInput = inputRefs.current[index];
|
|
107
|
+
if (previousInput) {
|
|
108
|
+
previousInput.focus();
|
|
109
|
+
}
|
|
110
|
+
if (currentInput) {
|
|
111
|
+
currentInput.value = '';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
[passcode, onChange],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const handleFocus = useCallback(
|
|
119
|
+
(index: number) => {
|
|
120
|
+
inputRefs.current[index]?.select();
|
|
121
|
+
},
|
|
122
|
+
[inputRefs],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const handleBlur = useCallback(
|
|
126
|
+
(index: number, event: FocusEvent<HTMLInputElement>) => {
|
|
127
|
+
if (onBlur) {
|
|
128
|
+
onBlur(index, event);
|
|
66
129
|
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
);
|
|
130
|
+
},
|
|
131
|
+
[onBlur],
|
|
132
|
+
);
|
|
71
133
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
134
|
+
return (
|
|
135
|
+
<div {...props} className={styles.base({ className })}>
|
|
136
|
+
{Array.from({ length }).map((_, index) => (
|
|
137
|
+
<Input
|
|
138
|
+
size="large"
|
|
139
|
+
key={index}
|
|
140
|
+
value={passcode[index] || ''}
|
|
141
|
+
onChange={e => handleChange(index, e)}
|
|
142
|
+
onPaste={e => handlePaste(index, e)}
|
|
143
|
+
onKeyDown={e => handleKeyDown(index, e)}
|
|
144
|
+
onFocus={() => handleFocus(index)}
|
|
145
|
+
onBlur={e => handleBlur(index, e)}
|
|
146
|
+
ref={input => (inputRefs.current[index] = input)}
|
|
147
|
+
className={styles.input({})}
|
|
148
|
+
aria-label={`Passcode digit ${index + 1}`}
|
|
149
|
+
inputMode={type === 'numbers' ? 'numeric' : 'text'}
|
|
150
|
+
/>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
);
|
|
78
156
|
|
|
79
|
-
|
|
80
|
-
<div {...props} className={styles.base({ className })}>
|
|
81
|
-
{passcode.map((digit, index) => (
|
|
82
|
-
<Input
|
|
83
|
-
size="large"
|
|
84
|
-
key={index}
|
|
85
|
-
value={digit}
|
|
86
|
-
onChange={e => handleChange(index, e)}
|
|
87
|
-
onPaste={e => handlePaste(index, e)}
|
|
88
|
-
onKeyDown={e => handleKeyDown(index, e)}
|
|
89
|
-
onFocus={() => handleFocus(index)}
|
|
90
|
-
ref={input => (inputRefs.current[index] = input)}
|
|
91
|
-
className={styles.input({})}
|
|
92
|
-
aria-label={`Passcode digit ${index + 1}`}
|
|
93
|
-
/>
|
|
94
|
-
))}
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
157
|
+
PassCode.displayName = 'PassCode';
|
|
@@ -1,10 +1,46 @@
|
|
|
1
|
-
import { HTMLAttributes } from 'react';
|
|
1
|
+
import { FocusEvent, HTMLAttributes } from 'react';
|
|
2
2
|
import { type VariantProps } from 'tailwind-variants';
|
|
3
3
|
|
|
4
4
|
import { styles } from './pass-code.styles.js';
|
|
5
5
|
|
|
6
6
|
export type PassCodeProps = {
|
|
7
|
+
/**
|
|
8
|
+
* Number of passcode inputs
|
|
9
|
+
*/
|
|
7
10
|
length: number;
|
|
8
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Callback when the input is blurred
|
|
13
|
+
*/
|
|
14
|
+
onBlur?: (index: number, event: FocusEvent<HTMLInputElement>) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Callback when the input value changes
|
|
17
|
+
*/
|
|
18
|
+
onChange?: (passcode: string[]) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Callback when the passcode is completely typed
|
|
21
|
+
*/
|
|
22
|
+
onComplete?: (passcode: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Type of passcode input
|
|
25
|
+
*/
|
|
26
|
+
type?: 'numbers' | 'letters' | 'alphanumeric';
|
|
27
|
+
/**
|
|
28
|
+
* Value of the passcode input
|
|
29
|
+
*/
|
|
30
|
+
value?: string[];
|
|
9
31
|
} & VariantProps<typeof styles> &
|
|
10
|
-
HTMLAttributes<Element>;
|
|
32
|
+
Omit<HTMLAttributes<Element>, 'onChange'>;
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Passcode input ref used to access the passcode input functions via useImperativeHandle hook
|
|
36
|
+
*/
|
|
37
|
+
export type PassCodeRef = {
|
|
38
|
+
/**
|
|
39
|
+
* Clear the passcode input, for non-controlled component only
|
|
40
|
+
*/
|
|
41
|
+
clear: () => void;
|
|
42
|
+
/**
|
|
43
|
+
* Focus on the first input
|
|
44
|
+
*/
|
|
45
|
+
focus: () => void;
|
|
46
|
+
};
|