@uniai-fe/uds-primitives 0.0.15 → 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 +139 -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/pagination/markup/Carousel.tsx +71 -53
- package/src/components/pagination/markup/Count.tsx +9 -6
- package/src/components/pagination/markup/Pagination.tsx +11 -9
- package/src/components/pagination/styles/index.scss +12 -0
- package/src/components/pagination/types/index.ts +17 -4
- package/src/components/input/markup/text/Identification.tsx +0 -159
|
@@ -295,63 +295,69 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
|
|
|
295
295
|
data-size={size}
|
|
296
296
|
data-block={block ? "true" : undefined}
|
|
297
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
|
-
|
|
298
|
+
<div className="input-field__control">
|
|
299
|
+
{shouldRenderInlineLabel ? (
|
|
300
|
+
<div
|
|
301
|
+
className="input-inline-label"
|
|
302
|
+
aria-hidden="true"
|
|
303
|
+
data-slot="inline-label"
|
|
304
|
+
>
|
|
305
|
+
{label}
|
|
306
|
+
</div>
|
|
307
|
+
) : null}
|
|
308
|
+
{left ? (
|
|
309
|
+
<div
|
|
310
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--left`}
|
|
311
|
+
data-slot="left"
|
|
312
|
+
>
|
|
313
|
+
{left}
|
|
314
|
+
</div>
|
|
315
|
+
) : null}
|
|
316
|
+
<input
|
|
317
|
+
{...restProps}
|
|
318
|
+
id={fieldId}
|
|
319
|
+
ref={mergedRef}
|
|
320
|
+
className={inputElementClassName}
|
|
321
|
+
disabled={isDisabled}
|
|
322
|
+
aria-invalid={resolvedState === "error" ? true : undefined}
|
|
323
|
+
aria-describedby={helperElementId}
|
|
324
|
+
type={type}
|
|
325
|
+
value={value}
|
|
326
|
+
defaultValue={defaultValue}
|
|
327
|
+
name={inputName}
|
|
328
|
+
onChange={handleChange}
|
|
329
|
+
onFocus={handleFocus}
|
|
330
|
+
onBlur={handleBlur}
|
|
331
|
+
/>
|
|
332
|
+
</div>
|
|
333
|
+
{right || showClearIcon || statusSlot ? (
|
|
334
|
+
<div className="input-field__utilities">
|
|
335
|
+
{right ? (
|
|
336
|
+
<div
|
|
337
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--right`}
|
|
338
|
+
data-slot="right"
|
|
339
|
+
>
|
|
340
|
+
{right}
|
|
341
|
+
</div>
|
|
342
|
+
) : null}
|
|
343
|
+
{showClearIcon ? (
|
|
344
|
+
<div
|
|
345
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--clear`}
|
|
346
|
+
data-slot="clear"
|
|
347
|
+
data-visible="true"
|
|
348
|
+
>
|
|
349
|
+
{effectiveClearIcon}
|
|
350
|
+
</div>
|
|
351
|
+
) : null}
|
|
352
|
+
{statusSlot ? (
|
|
353
|
+
<div
|
|
354
|
+
className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--status`}
|
|
355
|
+
data-slot="status"
|
|
356
|
+
data-state={resolvedState}
|
|
357
|
+
>
|
|
358
|
+
{statusSlot}
|
|
359
|
+
</div>
|
|
360
|
+
) : null}
|
|
355
361
|
</div>
|
|
356
362
|
) : null}
|
|
357
363
|
</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";
|
|
@@ -104,18 +104,52 @@
|
|
|
104
104
|
padding-inline: 0;
|
|
105
105
|
padding-block: var(--spacing-padding-4);
|
|
106
106
|
background-color: transparent;
|
|
107
|
+
|
|
108
|
+
&[data-state="active"],
|
|
109
|
+
&[data-state="focused"] {
|
|
110
|
+
border-bottom-color: var(--theme-input-border-active);
|
|
111
|
+
border-bottom-width: var(--theme-input-border-width-emphasis);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
&[data-state="success"] {
|
|
115
|
+
border-bottom-color: var(--theme-input-border-success);
|
|
116
|
+
border-bottom-width: var(--theme-input-border-width-emphasis);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&[data-state="error"] {
|
|
120
|
+
border-bottom-color: var(--theme-input-border-error);
|
|
121
|
+
border-bottom-width: var(--theme-input-border-width-emphasis);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&[data-state="disabled"] {
|
|
125
|
+
border-bottom-color: var(--theme-input-border-underline-disabled);
|
|
126
|
+
border-bottom-width: var(--theme-input-border-width-default);
|
|
127
|
+
}
|
|
107
128
|
}
|
|
108
129
|
|
|
109
130
|
&[data-priority="tertiary"] {
|
|
110
131
|
border-radius: var(--theme-input-radius-tertiary);
|
|
111
132
|
background-color: var(--theme-input-surface);
|
|
112
133
|
min-height: var(--theme-input-height-tertiary);
|
|
113
|
-
flex-wrap: wrap;
|
|
114
134
|
row-gap: var(--spacing-gap-1);
|
|
115
135
|
column-gap: var(--theme-input-gap);
|
|
136
|
+
flex-wrap: wrap;
|
|
137
|
+
align-items: center;
|
|
138
|
+
|
|
139
|
+
.input-field__control {
|
|
140
|
+
display: grid;
|
|
141
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
142
|
+
column-gap: var(--theme-input-gap);
|
|
143
|
+
row-gap: var(--spacing-gap-1);
|
|
144
|
+
align-items: center;
|
|
145
|
+
flex: 1 1 auto;
|
|
146
|
+
min-width: 0;
|
|
147
|
+
}
|
|
116
148
|
|
|
117
149
|
.input-inline-label {
|
|
118
|
-
|
|
150
|
+
grid-column: 1 / -1;
|
|
151
|
+
margin: 0;
|
|
152
|
+
align-self: flex-start;
|
|
119
153
|
}
|
|
120
154
|
|
|
121
155
|
.input-element {
|
|
@@ -124,9 +158,9 @@
|
|
|
124
158
|
flex: 1 1 auto;
|
|
125
159
|
}
|
|
126
160
|
|
|
127
|
-
.input-
|
|
128
|
-
|
|
129
|
-
margin-left:
|
|
161
|
+
.input-field__utilities {
|
|
162
|
+
align-self: center;
|
|
163
|
+
margin-left: 0;
|
|
130
164
|
}
|
|
131
165
|
}
|
|
132
166
|
|
|
@@ -188,6 +222,26 @@
|
|
|
188
222
|
}
|
|
189
223
|
}
|
|
190
224
|
|
|
225
|
+
.input-field__control {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
gap: var(--theme-input-gap);
|
|
229
|
+
flex: 1 1 auto;
|
|
230
|
+
min-width: 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.input-field__utilities {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: var(--spacing-gap-2, 8px);
|
|
237
|
+
flex-shrink: 0;
|
|
238
|
+
margin-left: var(--spacing-gap-3, 12px);
|
|
239
|
+
|
|
240
|
+
.input-affix {
|
|
241
|
+
margin-left: 0;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
191
245
|
.input-inline-label {
|
|
192
246
|
order: -2;
|
|
193
247
|
flex-basis: 100%;
|
|
@@ -219,7 +273,7 @@
|
|
|
219
273
|
}
|
|
220
274
|
|
|
221
275
|
.input-affix {
|
|
222
|
-
display:
|
|
276
|
+
display: flex;
|
|
223
277
|
align-items: center;
|
|
224
278
|
justify-content: center;
|
|
225
279
|
min-width: 20px;
|
|
@@ -313,8 +367,7 @@
|
|
|
313
367
|
}
|
|
314
368
|
}
|
|
315
369
|
|
|
316
|
-
.input-password-toggle
|
|
317
|
-
.input-action-button {
|
|
370
|
+
.input-password-toggle {
|
|
318
371
|
border: none;
|
|
319
372
|
background: transparent;
|
|
320
373
|
color: var(--theme-input-label-accent-color);
|
|
@@ -374,14 +427,46 @@
|
|
|
374
427
|
color: var(--theme-input-helper-color);
|
|
375
428
|
}
|
|
376
429
|
|
|
377
|
-
.email-verification
|
|
430
|
+
.email-verification,
|
|
431
|
+
.phone-verification {
|
|
378
432
|
display: flex;
|
|
379
433
|
flex-direction: column;
|
|
380
434
|
gap: var(--spacing-gap-4);
|
|
381
435
|
}
|
|
382
436
|
|
|
383
|
-
.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
437
|
+
.auth-code-input__actions,
|
|
438
|
+
.email-verification__code-actions,
|
|
439
|
+
.phone-verification__code-actions {
|
|
440
|
+
display: flex;
|
|
441
|
+
align-items: center;
|
|
442
|
+
justify-content: flex-end;
|
|
443
|
+
gap: var(--spacing-gap-3);
|
|
444
|
+
min-width: 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.auth-code-input__countdown,
|
|
448
|
+
.email-verification__countdown,
|
|
449
|
+
.phone-verification__countdown {
|
|
450
|
+
display: flex;
|
|
451
|
+
align-items: center;
|
|
452
|
+
font-weight: 500;
|
|
453
|
+
font-style: normal;
|
|
454
|
+
font-size: 13px;
|
|
455
|
+
line-height: 1em;
|
|
456
|
+
letter-spacing: -0.0025em;
|
|
457
|
+
color: var(--color-primary-default);
|
|
458
|
+
flex-shrink: 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.button.input-utility-button {
|
|
462
|
+
min-height: 32px;
|
|
463
|
+
padding: var(--spacing-padding-2, 4px) var(--spacing-padding-6, 24px);
|
|
464
|
+
border-radius: var(--shape-rounded-1, 8px);
|
|
465
|
+
|
|
466
|
+
.button-label {
|
|
467
|
+
font-size: var(--font-body-xxsmall-size);
|
|
468
|
+
line-height: var(--font-body-xxsmall-line-height);
|
|
469
|
+
letter-spacing: var(--font-body-xxsmall-letter-spacing);
|
|
470
|
+
font-weight: var(--font-body-xxsmall-weight);
|
|
471
|
+
}
|
|
387
472
|
}
|