@uniai-fe/uds-primitives 0.0.16 → 0.0.17
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 +9 -1
- package/dist/styles.css +128 -35
- package/package.json +1 -1
- package/src/components/checkbox/img/check-large.svg +1 -1
- package/src/components/checkbox/img/check-medium.svg +1 -1
- package/src/components/checkbox/markup/Checkbox.tsx +6 -3
- package/src/components/checkbox/styles/index.scss +38 -25
- package/src/components/input/markup/text/AuthCode.tsx +145 -0
- package/src/components/input/markup/text/Base.tsx +63 -57
- package/src/components/input/markup/text/{EmailVerification.tsx → Email.tsx} +50 -31
- package/src/components/input/markup/text/InputUtilityButton.tsx +46 -0
- package/src/components/input/markup/text/Phone.tsx +65 -7
- package/src/components/input/markup/text/index.ts +4 -4
- package/src/components/input/styles/index.scss +98 -13
- package/src/components/input/markup/text/Identification.tsx +0 -159
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ClipboardEvent,
|
|
3
|
-
ChangeEvent,
|
|
4
|
-
forwardRef,
|
|
5
|
-
useImperativeHandle,
|
|
6
|
-
KeyboardEvent,
|
|
7
|
-
ReactNode,
|
|
8
|
-
useCallback,
|
|
9
|
-
useMemo,
|
|
10
|
-
useRef,
|
|
11
|
-
useState,
|
|
12
|
-
} from "react";
|
|
13
|
-
import type { InputState } from "../../types";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* IdentificationInput props. 고정 길이 숫자 코드 입력에 필요한 label/helper/state/onComplete를 제공한다.
|
|
17
|
-
* @property {number} [length=6] 입력칸 개수(4~8 사이로 자동 보정).
|
|
18
|
-
* @property {ReactNode} [label] 상단 라벨.
|
|
19
|
-
* @property {ReactNode} [helper] helper 텍스트.
|
|
20
|
-
* @property {InputState} [state="default"] 시각 상태.
|
|
21
|
-
* @property {(code: string) => void} [onComplete] 모든 셀이 채워졌을 때 호출.
|
|
22
|
-
*/
|
|
23
|
-
export interface IdentificationInputProps {
|
|
24
|
-
length?: number;
|
|
25
|
-
label?: ReactNode;
|
|
26
|
-
helper?: ReactNode;
|
|
27
|
-
state?: InputState;
|
|
28
|
-
onComplete?: (code: string) => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* IdentificationInput — 인증번호 입력 UI. 개별 입력칸을 제공하고 focus 이동/붙여넣기 등을 처리한다.
|
|
33
|
-
* @component
|
|
34
|
-
* @param {IdentificationInputProps} props
|
|
35
|
-
* @param {number} [props.length=6] 입력 필드 길이. 4~8 범위로 자동 보정된다.
|
|
36
|
-
* @param {ReactNode} [props.label] 상단 label 콘텐츠.
|
|
37
|
-
* @param {ReactNode} [props.helper] helper 텍스트.
|
|
38
|
-
* @param {InputState} [props.state] 시각 상태.
|
|
39
|
-
* @param {(code: string) => void} [props.onComplete] 모든 셀이 채워졌을 때 호출되는 콜백.
|
|
40
|
-
*/
|
|
41
|
-
const IdentificationInput = forwardRef<
|
|
42
|
-
HTMLInputElement[],
|
|
43
|
-
IdentificationInputProps
|
|
44
|
-
>(({ length = 6, label, helper, state, onComplete }, forwardedRef) => {
|
|
45
|
-
const safeLength = Math.max(4, Math.min(8, length));
|
|
46
|
-
const [values, setValues] = useState(() =>
|
|
47
|
-
Array.from({ length: safeLength }, () => ""),
|
|
48
|
-
);
|
|
49
|
-
const inputRefs = useRef<Array<HTMLInputElement | null>>(
|
|
50
|
-
Array(safeLength).fill(null),
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const focusCell = useCallback((index: number) => {
|
|
54
|
-
const ref = inputRefs.current[index];
|
|
55
|
-
ref?.focus();
|
|
56
|
-
ref?.select();
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
const updateValues = useCallback(
|
|
60
|
-
(index: number, digit: string) => {
|
|
61
|
-
setValues(prev => {
|
|
62
|
-
const next = [...prev];
|
|
63
|
-
next[index] = digit;
|
|
64
|
-
if (!next.includes("")) {
|
|
65
|
-
onComplete?.(next.join(""));
|
|
66
|
-
}
|
|
67
|
-
return next;
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
[onComplete],
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const handleChange = useCallback(
|
|
74
|
-
(index: number) => (event: ChangeEvent<HTMLInputElement>) => {
|
|
75
|
-
const digit = event.target.value.replace(/\D/g, "").slice(-1);
|
|
76
|
-
updateValues(index, digit);
|
|
77
|
-
if (digit && index < safeLength - 1) {
|
|
78
|
-
focusCell(index + 1);
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
[focusCell, safeLength, updateValues],
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const handleKeyDown = useCallback(
|
|
85
|
-
(index: number) => (event: KeyboardEvent<HTMLInputElement>) => {
|
|
86
|
-
if (event.key === "Backspace" && !values[index] && index > 0) {
|
|
87
|
-
updateValues(index - 1, "");
|
|
88
|
-
focusCell(index - 1);
|
|
89
|
-
event.preventDefault();
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
[focusCell, updateValues, values],
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const handlePaste = useCallback(
|
|
96
|
-
(event: ClipboardEvent<HTMLInputElement>) => {
|
|
97
|
-
const digits = event.clipboardData.getData("text").replace(/\D/g, "");
|
|
98
|
-
if (!digits) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
event.preventDefault();
|
|
102
|
-
setValues(prev => {
|
|
103
|
-
const next = [...prev];
|
|
104
|
-
for (let i = 0; i < safeLength; i += 1) {
|
|
105
|
-
next[i] = digits[i] ?? next[i];
|
|
106
|
-
}
|
|
107
|
-
if (!next.includes("")) {
|
|
108
|
-
onComplete?.(next.join(""));
|
|
109
|
-
}
|
|
110
|
-
return next;
|
|
111
|
-
});
|
|
112
|
-
},
|
|
113
|
-
[onComplete, safeLength],
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const helperNode = useMemo(() => helper, [helper]);
|
|
117
|
-
|
|
118
|
-
// forwardRef 사용자는 각 셀 DOM 배열을 직접 제어할 수 있도록 노출한다.
|
|
119
|
-
useImperativeHandle(
|
|
120
|
-
forwardedRef,
|
|
121
|
-
() =>
|
|
122
|
-
inputRefs.current.filter((element): element is HTMLInputElement =>
|
|
123
|
-
Boolean(element),
|
|
124
|
-
),
|
|
125
|
-
[],
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<div className="one-time-code" data-state={state}>
|
|
130
|
-
{label ? <div className="one-time-code__label">{label}</div> : null}
|
|
131
|
-
<div className="one-time-code__fields">
|
|
132
|
-
{values.map((value, index) => (
|
|
133
|
-
<input
|
|
134
|
-
key={`identification-${index}`}
|
|
135
|
-
ref={element => {
|
|
136
|
-
inputRefs.current[index] = element;
|
|
137
|
-
}}
|
|
138
|
-
type="text"
|
|
139
|
-
inputMode="numeric"
|
|
140
|
-
className="one-time-code__input"
|
|
141
|
-
maxLength={1}
|
|
142
|
-
value={value}
|
|
143
|
-
onChange={handleChange(index)}
|
|
144
|
-
onKeyDown={handleKeyDown(index)}
|
|
145
|
-
onPaste={handlePaste}
|
|
146
|
-
aria-label={`${index + 1}번째 인증번호 숫자`}
|
|
147
|
-
/>
|
|
148
|
-
))}
|
|
149
|
-
</div>
|
|
150
|
-
{helperNode ? (
|
|
151
|
-
<div className="one-time-code__helper">{helperNode}</div>
|
|
152
|
-
) : null}
|
|
153
|
-
</div>
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
IdentificationInput.displayName = "IdentificationInput";
|
|
158
|
-
|
|
159
|
-
export { IdentificationInput };
|