@uniai-fe/uds-primitives 0.0.16 → 0.0.18
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 +130 -35
- package/package.json +2 -2
- 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 +71 -58
- 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 +104 -13
- package/src/components/input/types/index.ts +1 -0
- package/src/components/input/markup/text/Identification.tsx +0 -159
|
@@ -138,6 +138,9 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
|
|
|
138
138
|
if (resolvedState === "disabled") {
|
|
139
139
|
return "disabled";
|
|
140
140
|
}
|
|
141
|
+
if (resolvedState === "loading") {
|
|
142
|
+
return "loading";
|
|
143
|
+
}
|
|
141
144
|
if (resolvedState === "error") {
|
|
142
145
|
return "error";
|
|
143
146
|
}
|
|
@@ -183,8 +186,12 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
|
|
|
183
186
|
}, [visualState]);
|
|
184
187
|
|
|
185
188
|
const effectiveClearIcon = clear ?? defaultClearIcon;
|
|
189
|
+
const shouldBlockClear =
|
|
190
|
+
resolvedState === "disabled" ||
|
|
191
|
+
resolvedState === "loading" ||
|
|
192
|
+
restProps.readOnly;
|
|
186
193
|
const showClearIcon = Boolean(
|
|
187
|
-
effectiveClearIcon && hasValue &&
|
|
194
|
+
effectiveClearIcon && hasValue && !shouldBlockClear,
|
|
188
195
|
);
|
|
189
196
|
const isDisabled = resolvedState === "disabled";
|
|
190
197
|
const labelFor = labelProps?.htmlFor ?? fieldId;
|
|
@@ -295,63 +302,69 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
|
|
|
295
302
|
data-size={size}
|
|
296
303
|
data-block={block ? "true" : undefined}
|
|
297
304
|
>
|
|
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
|
-
|
|
305
|
+
<div className="input-field__control">
|
|
306
|
+
{shouldRenderInlineLabel ? (
|
|
307
|
+
<div
|
|
308
|
+
className="input-inline-label"
|
|
309
|
+
aria-hidden="true"
|
|
310
|
+
data-slot="inline-label"
|
|
311
|
+
>
|
|
312
|
+
{label}
|
|
313
|
+
</div>
|
|
314
|
+
) : null}
|
|
315
|
+
{left ? (
|
|
316
|
+
<div
|
|
317
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--left`}
|
|
318
|
+
data-slot="left"
|
|
319
|
+
>
|
|
320
|
+
{left}
|
|
321
|
+
</div>
|
|
322
|
+
) : null}
|
|
323
|
+
<input
|
|
324
|
+
{...restProps}
|
|
325
|
+
id={fieldId}
|
|
326
|
+
ref={mergedRef}
|
|
327
|
+
className={inputElementClassName}
|
|
328
|
+
disabled={isDisabled}
|
|
329
|
+
aria-invalid={resolvedState === "error" ? true : undefined}
|
|
330
|
+
aria-describedby={helperElementId}
|
|
331
|
+
type={type}
|
|
332
|
+
value={value}
|
|
333
|
+
defaultValue={defaultValue}
|
|
334
|
+
name={inputName}
|
|
335
|
+
onChange={handleChange}
|
|
336
|
+
onFocus={handleFocus}
|
|
337
|
+
onBlur={handleBlur}
|
|
338
|
+
/>
|
|
339
|
+
</div>
|
|
340
|
+
{right || showClearIcon || statusSlot ? (
|
|
341
|
+
<div className="input-field__utilities">
|
|
342
|
+
{right ? (
|
|
343
|
+
<div
|
|
344
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--right`}
|
|
345
|
+
data-slot="right"
|
|
346
|
+
>
|
|
347
|
+
{right}
|
|
348
|
+
</div>
|
|
349
|
+
) : null}
|
|
350
|
+
{showClearIcon ? (
|
|
351
|
+
<div
|
|
352
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--clear`}
|
|
353
|
+
data-slot="clear"
|
|
354
|
+
data-visible="true"
|
|
355
|
+
>
|
|
356
|
+
{effectiveClearIcon}
|
|
357
|
+
</div>
|
|
358
|
+
) : null}
|
|
359
|
+
{statusSlot ? (
|
|
360
|
+
<div
|
|
361
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--status`}
|
|
362
|
+
data-slot="status"
|
|
363
|
+
data-state={resolvedState}
|
|
364
|
+
>
|
|
365
|
+
{statusSlot}
|
|
366
|
+
</div>
|
|
367
|
+
) : null}
|
|
355
368
|
</div>
|
|
356
369
|
) : null}
|
|
357
370
|
</div>
|
|
@@ -2,26 +2,34 @@ import type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
2
2
|
import { forwardRef, useCallback, useMemo } from "react";
|
|
3
3
|
import type { InputProps, InputState } from "../../types";
|
|
4
4
|
import { Text } from "./Base";
|
|
5
|
-
import {
|
|
5
|
+
import { AuthCodeInput } from "./AuthCode";
|
|
6
|
+
import type { AuthCodeInputProps } from "./AuthCode";
|
|
7
|
+
import { InputUtilityButton } from "./InputUtilityButton";
|
|
8
|
+
import type { InputUtilityButtonClickHandler } from "./InputUtilityButton";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* EmailInput props. 이메일 입력 + 인증 요청/코드 입력 옵션을 정의한다.
|
|
9
12
|
* @property {string} [value] 제어형 값.
|
|
10
13
|
* @property {string} [defaultValue] 비제어 초기값.
|
|
11
14
|
* @property {(value: string) => void} [onValueChange] 값 변경 시 호출.
|
|
12
15
|
* @property {ComponentPropsWithoutRef<"input">["onChange"]} [onChange] native onChange override.
|
|
13
|
-
* @property {
|
|
14
|
-
* @property {
|
|
16
|
+
* @property {InputUtilityButtonClickHandler} [onRequestCode] 인증 요청 버튼 클릭 시 호출(optional).
|
|
17
|
+
* @property {ReactNode} [requestButtonLabel="인증번호 요청"] 인증 요청 버튼 라벨(optional).
|
|
15
18
|
* @property {boolean} [requestButtonDisabled] 인증 요청 버튼 disabled(optional).
|
|
16
19
|
* @property {ReactNode} [countdownText] 인증 제한 시간 안내 텍스트(optional).
|
|
20
|
+
* @property {ReactNode} [countdownActionLabel="시간연장"] 제한 시간 연장 버튼 라벨(optional).
|
|
21
|
+
* @property {InputUtilityButtonClickHandler} [onCountdownAction] 제한 시간 연장 버튼 클릭 콜백(optional).
|
|
22
|
+
* @property {boolean} [countdownActionDisabled] 제한 시간 연장 버튼 disabled(optional).
|
|
17
23
|
* @property {boolean} [codeVisible] 인증번호 입력 UI 노출 여부(optional).
|
|
18
24
|
* @property {number} [codeLength=6] 인증번호 길이(optional).
|
|
19
25
|
* @property {ReactNode} [codeLabel] 인증번호 입력 label(optional).
|
|
20
26
|
* @property {ReactNode} [codeHelper] 인증번호 helper(optional).
|
|
21
27
|
* @property {InputState} [codeState] 인증번호 입력 상태(optional).
|
|
22
28
|
* @property {(code: string) => void} [onCodeComplete] 인증번호 입력 완료 시 호출(optional).
|
|
29
|
+
* @property {string} [codePlaceholder] 인증번호 입력 placeholder(optional).
|
|
30
|
+
* @property {Partial<AuthCodeInputProps>} [codeInputProps] 인증코드 입력 추가 설정(register 등) 전달용.
|
|
23
31
|
*/
|
|
24
|
-
export interface
|
|
32
|
+
export interface EmailInputProps extends Omit<
|
|
25
33
|
InputProps,
|
|
26
34
|
"type" | "inputMode" | "pattern" | "onChange" | "value" | "defaultValue"
|
|
27
35
|
> {
|
|
@@ -29,27 +37,32 @@ export interface EmailVerificationInputProps extends Omit<
|
|
|
29
37
|
defaultValue?: string;
|
|
30
38
|
onValueChange?: (value: string) => void;
|
|
31
39
|
onChange?: ComponentPropsWithoutRef<"input">["onChange"];
|
|
32
|
-
onRequestCode?:
|
|
33
|
-
requestButtonLabel?:
|
|
40
|
+
onRequestCode?: InputUtilityButtonClickHandler;
|
|
41
|
+
requestButtonLabel?: ReactNode;
|
|
34
42
|
requestButtonDisabled?: boolean;
|
|
35
43
|
countdownText?: ReactNode;
|
|
44
|
+
countdownActionLabel?: ReactNode;
|
|
45
|
+
onCountdownAction?: InputUtilityButtonClickHandler;
|
|
46
|
+
countdownActionDisabled?: boolean;
|
|
36
47
|
codeVisible?: boolean;
|
|
37
48
|
codeLength?: number;
|
|
38
49
|
codeLabel?: ReactNode;
|
|
39
50
|
codeHelper?: ReactNode;
|
|
40
51
|
codeState?: InputState;
|
|
41
52
|
onCodeComplete?: (code: string) => void;
|
|
53
|
+
codePlaceholder?: string;
|
|
54
|
+
codeInputProps?: Partial<AuthCodeInputProps>;
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
/**
|
|
45
58
|
* 이메일 인증 입력 컴포넌트; 이메일 입력 + 인증요청 버튼 + OneTimeCode 입력을 옵션으로 제공한다.
|
|
46
59
|
* @component
|
|
47
|
-
* @param {
|
|
60
|
+
* @param {EmailInputProps} props 이메일 인증 props
|
|
48
61
|
* @param {string} [props.value] 제어형 이메일 값
|
|
49
62
|
* @param {string} [props.defaultValue] 비제어 이메일 초기값
|
|
50
63
|
* @param {(value: string) => void} [props.onValueChange] 이메일 변경 콜백
|
|
51
64
|
* @param {ComponentPropsWithoutRef<"input">["onChange"]} [props.onChange] native onChange override
|
|
52
|
-
* @param {
|
|
65
|
+
* @param {InputUtilityButtonClickHandler} [props.onRequestCode] 인증요청 버튼 클릭 시 호출
|
|
53
66
|
* @param {string} [props.requestButtonLabel] 인증요청 버튼 라벨
|
|
54
67
|
* @param {boolean} [props.requestButtonDisabled] 인증요청 버튼 disabled
|
|
55
68
|
* @param {ReactNode} [props.countdownText] 제한 시간 안내 텍스트
|
|
@@ -60,10 +73,7 @@ export interface EmailVerificationInputProps extends Omit<
|
|
|
60
73
|
* @param {InputState} [props.codeState] 인증번호 입력 상태
|
|
61
74
|
* @param {(code: string) => void} [props.onCodeComplete] 인증번호 입력 완료 시 호출
|
|
62
75
|
*/
|
|
63
|
-
const
|
|
64
|
-
HTMLInputElement,
|
|
65
|
-
EmailVerificationInputProps
|
|
66
|
-
>(
|
|
76
|
+
const EmailInput = forwardRef<HTMLInputElement, EmailInputProps>(
|
|
67
77
|
(
|
|
68
78
|
{
|
|
69
79
|
value,
|
|
@@ -74,13 +84,18 @@ const EmailVerificationInput = forwardRef<
|
|
|
74
84
|
requestButtonLabel = "인증번호 요청",
|
|
75
85
|
requestButtonDisabled,
|
|
76
86
|
countdownText,
|
|
87
|
+
countdownActionLabel = "시간연장",
|
|
88
|
+
onCountdownAction,
|
|
89
|
+
countdownActionDisabled,
|
|
77
90
|
codeVisible,
|
|
78
91
|
codeLength = 6,
|
|
79
92
|
codeLabel,
|
|
80
93
|
codeHelper,
|
|
81
94
|
codeState,
|
|
82
95
|
onCodeComplete,
|
|
96
|
+
codePlaceholder = "인증코드 입력",
|
|
83
97
|
right,
|
|
98
|
+
codeInputProps,
|
|
84
99
|
...restProps
|
|
85
100
|
},
|
|
86
101
|
forwardedRef,
|
|
@@ -99,17 +114,32 @@ const EmailVerificationInput = forwardRef<
|
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
return (
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
className="input-action-button"
|
|
117
|
+
<InputUtilityButton
|
|
118
|
+
priority="primary"
|
|
105
119
|
onClick={onRequestCode}
|
|
106
120
|
disabled={requestButtonDisabled}
|
|
107
121
|
>
|
|
108
122
|
{requestButtonLabel}
|
|
109
|
-
</
|
|
123
|
+
</InputUtilityButton>
|
|
110
124
|
);
|
|
111
125
|
}, [onRequestCode, requestButtonDisabled, requestButtonLabel]);
|
|
112
126
|
|
|
127
|
+
const resolvedCodeProps: AuthCodeInputProps = {
|
|
128
|
+
...(codeInputProps ?? {}),
|
|
129
|
+
length: codeLength ?? codeInputProps?.length,
|
|
130
|
+
label: codeLabel ?? codeInputProps?.label,
|
|
131
|
+
helper: codeHelper ?? codeInputProps?.helper,
|
|
132
|
+
state: codeState ?? codeInputProps?.state,
|
|
133
|
+
placeholder: codePlaceholder ?? codeInputProps?.placeholder,
|
|
134
|
+
countdownText: countdownText ?? codeInputProps?.countdownText,
|
|
135
|
+
countdownActionLabel:
|
|
136
|
+
countdownActionLabel ?? codeInputProps?.countdownActionLabel,
|
|
137
|
+
onCountdownAction: onCountdownAction ?? codeInputProps?.onCountdownAction,
|
|
138
|
+
countdownActionDisabled:
|
|
139
|
+
countdownActionDisabled ?? codeInputProps?.countdownActionDisabled,
|
|
140
|
+
onComplete: onCodeComplete ?? codeInputProps?.onComplete,
|
|
141
|
+
};
|
|
142
|
+
|
|
113
143
|
return (
|
|
114
144
|
<div className="email-verification">
|
|
115
145
|
<Text
|
|
@@ -122,23 +152,12 @@ const EmailVerificationInput = forwardRef<
|
|
|
122
152
|
onChange={handleChange}
|
|
123
153
|
right={right ?? actionButton}
|
|
124
154
|
/>
|
|
125
|
-
{
|
|
126
|
-
<div className="email-verification__countdown">{countdownText}</div>
|
|
127
|
-
) : null}
|
|
128
|
-
{codeVisible ? (
|
|
129
|
-
<IdentificationInput
|
|
130
|
-
length={codeLength}
|
|
131
|
-
label={codeLabel}
|
|
132
|
-
helper={codeHelper}
|
|
133
|
-
state={codeState}
|
|
134
|
-
onComplete={onCodeComplete}
|
|
135
|
-
/>
|
|
136
|
-
) : null}
|
|
155
|
+
{codeVisible ? <AuthCodeInput {...resolvedCodeProps} /> : null}
|
|
137
156
|
</div>
|
|
138
157
|
);
|
|
139
158
|
},
|
|
140
159
|
);
|
|
141
160
|
|
|
142
|
-
|
|
161
|
+
EmailInput.displayName = "EmailInput";
|
|
143
162
|
|
|
144
|
-
export {
|
|
163
|
+
export { EmailInput };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { MouseEvent, ReactNode } from "react";
|
|
2
|
+
import { Button } from "../../../button";
|
|
3
|
+
import type { ButtonPriority, ButtonProps } from "../../../button/types";
|
|
4
|
+
|
|
5
|
+
export type InputUtilityButtonClickHandler = (
|
|
6
|
+
event?: MouseEvent<HTMLButtonElement> | MouseEvent<HTMLAnchorElement>,
|
|
7
|
+
) => void;
|
|
8
|
+
|
|
9
|
+
interface InputUtilityButtonProps {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
priority?: ButtonPriority;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
onClick?: InputUtilityButtonClickHandler;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 입력 필드 우측에 배치되는 유틸리티 버튼; outlined-small scale을 공통으로 사용한다.
|
|
18
|
+
* @component
|
|
19
|
+
* @param {InputUtilityButtonProps} props 유틸 버튼 props
|
|
20
|
+
* @param {React.ReactNode} props.children 라벨
|
|
21
|
+
* @param {ButtonPriority} [props.priority="primary"] 색상 우선순위
|
|
22
|
+
* @param {boolean} [props.disabled] disabled 여부
|
|
23
|
+
* @param {InputUtilityButtonClickHandler} [props.onClick] onClick 핸들러
|
|
24
|
+
*/
|
|
25
|
+
export function InputUtilityButton({
|
|
26
|
+
children,
|
|
27
|
+
priority = "primary",
|
|
28
|
+
disabled,
|
|
29
|
+
onClick,
|
|
30
|
+
}: InputUtilityButtonProps) {
|
|
31
|
+
const handleButtonClick: ButtonProps["onClick"] = event => {
|
|
32
|
+
onClick?.(event);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Button.Default
|
|
37
|
+
scale="outlined-small"
|
|
38
|
+
priority={priority}
|
|
39
|
+
className="input-utility-button"
|
|
40
|
+
onClick={handleButtonClick}
|
|
41
|
+
disabled={disabled}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</Button.Default>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { maskPhone } from "@uniai-fe/util-functions";
|
|
2
|
-
import type { ChangeEvent, ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from "react";
|
|
3
3
|
import { forwardRef, useCallback, useMemo, useState } from "react";
|
|
4
|
-
import type { InputProps } from "../../types";
|
|
4
|
+
import type { InputProps, InputState } from "../../types";
|
|
5
5
|
import { Text } from "./Base";
|
|
6
|
+
import { AuthCodeInput } from "./AuthCode";
|
|
7
|
+
import { InputUtilityButton } from "./InputUtilityButton";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* PhoneInput 전용 props. Text Input 중 전화번호 UX에 필요한 필드를 정의한다.
|
|
@@ -13,6 +15,17 @@ import { Text } from "./Base";
|
|
|
13
15
|
* @property {() => void} [onRequestCode] 인증 요청 버튼 클릭 시 호출(optional).
|
|
14
16
|
* @property {string} [requestButtonLabel="인증번호 요청"] 인증 요청 버튼 라벨(optional).
|
|
15
17
|
* @property {boolean} [requestButtonDisabled] 인증 요청 버튼 disabled(optional).
|
|
18
|
+
* @property {React.ReactNode} [countdownText] 인증 제한 시간 안내 텍스트(optional).
|
|
19
|
+
* @property {React.ReactNode} [countdownActionLabel="시간연장"] 제한 시간 연장 버튼(optional).
|
|
20
|
+
* @property {() => void} [onCountdownAction] 제한 시간 연장 핸들러(optional).
|
|
21
|
+
* @property {boolean} [countdownActionDisabled] 제한 시간 연장 disabled(optional).
|
|
22
|
+
* @property {boolean} [codeVisible] 인증번호 입력 UI 노출 여부(optional).
|
|
23
|
+
* @property {number} [codeLength=6] 인증번호 길이(optional).
|
|
24
|
+
* @property {React.ReactNode} [codeLabel] 인증번호 label(optional).
|
|
25
|
+
* @property {React.ReactNode} [codeHelper] 인증번호 helper 텍스트(optional).
|
|
26
|
+
* @property {InputState} [codeState] 인증번호 입력 상태(optional).
|
|
27
|
+
* @property {(code: string) => void} [onCodeComplete] 인증번호 입력 완료 콜백(optional).
|
|
28
|
+
* @property {string} [codePlaceholder="인증코드 입력"] 인증번호 placeholder(optional).
|
|
16
29
|
*/
|
|
17
30
|
export interface PhoneInputProps extends Omit<
|
|
18
31
|
InputProps,
|
|
@@ -25,6 +38,17 @@ export interface PhoneInputProps extends Omit<
|
|
|
25
38
|
onRequestCode?: () => void;
|
|
26
39
|
requestButtonLabel?: string;
|
|
27
40
|
requestButtonDisabled?: boolean;
|
|
41
|
+
countdownText?: ReactNode;
|
|
42
|
+
countdownActionLabel?: ReactNode;
|
|
43
|
+
onCountdownAction?: () => void;
|
|
44
|
+
countdownActionDisabled?: boolean;
|
|
45
|
+
codeVisible?: boolean;
|
|
46
|
+
codeLength?: number;
|
|
47
|
+
codeLabel?: ReactNode;
|
|
48
|
+
codeHelper?: ReactNode;
|
|
49
|
+
codeState?: InputState;
|
|
50
|
+
onCodeComplete?: (code: string) => void;
|
|
51
|
+
codePlaceholder?: string;
|
|
28
52
|
}
|
|
29
53
|
|
|
30
54
|
const MAX_DIGITS = 11;
|
|
@@ -56,6 +80,17 @@ const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
|
|
|
56
80
|
onRequestCode,
|
|
57
81
|
requestButtonLabel = "인증번호 요청",
|
|
58
82
|
requestButtonDisabled,
|
|
83
|
+
countdownText,
|
|
84
|
+
countdownActionLabel = "시간연장",
|
|
85
|
+
onCountdownAction,
|
|
86
|
+
countdownActionDisabled,
|
|
87
|
+
codeVisible,
|
|
88
|
+
codeLength = 6,
|
|
89
|
+
codeLabel,
|
|
90
|
+
codeHelper,
|
|
91
|
+
codeState,
|
|
92
|
+
onCodeComplete,
|
|
93
|
+
codePlaceholder = "인증코드 입력",
|
|
59
94
|
right,
|
|
60
95
|
...restProps
|
|
61
96
|
},
|
|
@@ -96,28 +131,51 @@ const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
|
|
|
96
131
|
}
|
|
97
132
|
// 휴대폰 인증 입력은 우측 right 슬롯에 인증 버튼을 둔다.
|
|
98
133
|
return (
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
className="input-action-button"
|
|
134
|
+
<InputUtilityButton
|
|
135
|
+
priority="primary"
|
|
102
136
|
onClick={onRequestCode}
|
|
103
137
|
disabled={requestButtonDisabled}
|
|
104
138
|
>
|
|
105
139
|
{requestButtonLabel}
|
|
106
|
-
</
|
|
140
|
+
</InputUtilityButton>
|
|
107
141
|
);
|
|
108
142
|
}, [onRequestCode, requestButtonDisabled, requestButtonLabel]);
|
|
109
143
|
|
|
110
|
-
|
|
144
|
+
const phoneField = (
|
|
111
145
|
<Text
|
|
112
146
|
{...restProps}
|
|
113
147
|
ref={forwardedRef}
|
|
114
148
|
type="tel"
|
|
115
149
|
inputMode="tel"
|
|
150
|
+
placeholder="000-0000-0000"
|
|
116
151
|
value={formattedValue}
|
|
117
152
|
onChange={handleChange}
|
|
118
153
|
right={right ?? actionButton}
|
|
119
154
|
/>
|
|
120
155
|
);
|
|
156
|
+
|
|
157
|
+
// 인증번호 UI가 비활성화된 경우에는 기존 PhoneInput DOM 구조를 그대로 유지한다.
|
|
158
|
+
if (!codeVisible) {
|
|
159
|
+
return phoneField;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="phone-verification">
|
|
164
|
+
{phoneField}
|
|
165
|
+
<AuthCodeInput
|
|
166
|
+
length={codeLength}
|
|
167
|
+
label={codeLabel}
|
|
168
|
+
helper={codeHelper}
|
|
169
|
+
state={codeState}
|
|
170
|
+
placeholder={codePlaceholder}
|
|
171
|
+
countdownText={countdownText}
|
|
172
|
+
countdownActionLabel={countdownActionLabel}
|
|
173
|
+
onCountdownAction={onCountdownAction}
|
|
174
|
+
countdownActionDisabled={countdownActionDisabled}
|
|
175
|
+
onComplete={onCodeComplete}
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
121
179
|
},
|
|
122
180
|
);
|
|
123
181
|
|
|
@@ -2,11 +2,11 @@ export { Text } from "./Base";
|
|
|
2
2
|
export { Text as Input } from "./Base";
|
|
3
3
|
export { PasswordInput } from "./Password";
|
|
4
4
|
export { PhoneInput } from "./Phone";
|
|
5
|
-
export {
|
|
5
|
+
export { EmailInput } from "./Email";
|
|
6
6
|
export { SearchInput } from "./Search";
|
|
7
|
-
export {
|
|
7
|
+
export { AuthCodeInput } from "./AuthCode";
|
|
8
8
|
export type { InputPasswordProps } from "./Password";
|
|
9
9
|
export type { PhoneInputProps } from "./Phone";
|
|
10
|
-
export type {
|
|
10
|
+
export type { EmailInputProps } from "./Email";
|
|
11
11
|
export type { SearchInputProps } from "./Search";
|
|
12
|
-
export type {
|
|
12
|
+
export type { AuthCodeInputProps } from "./AuthCode";
|