@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.
@@ -295,63 +295,69 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
295
295
  data-size={size}
296
296
  data-block={block ? "true" : undefined}
297
297
  >
298
- {shouldRenderInlineLabel ? (
299
- <div
300
- className="input-inline-label"
301
- aria-hidden="true"
302
- data-slot="inline-label"
303
- >
304
- {label}
305
- </div>
306
- ) : null}
307
- {left ? (
308
- <div
309
- className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--left`}
310
- data-slot="left"
311
- >
312
- {left}
313
- </div>
314
- ) : null}
315
- <input
316
- {...restProps}
317
- id={fieldId}
318
- ref={mergedRef}
319
- className={inputElementClassName}
320
- disabled={isDisabled}
321
- aria-invalid={resolvedState === "error" ? true : undefined}
322
- aria-describedby={helperElementId}
323
- type={type}
324
- value={value}
325
- defaultValue={defaultValue}
326
- name={inputName}
327
- onChange={handleChange}
328
- onFocus={handleFocus}
329
- onBlur={handleBlur}
330
- />
331
- {right ? (
332
- <div
333
- className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--right`}
334
- data-slot="right"
335
- >
336
- {right}
337
- </div>
338
- ) : null}
339
- {showClearIcon ? (
340
- <div
341
- className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--clear`}
342
- data-slot="clear"
343
- data-visible="true"
344
- >
345
- {effectiveClearIcon}
346
- </div>
347
- ) : null}
348
- {statusSlot ? (
349
- <div
350
- className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--status`}
351
- data-slot="status"
352
- data-state={resolvedState}
353
- >
354
- {statusSlot}
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 { IdentificationInput } from "./Identification";
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
- * EmailVerificationInput props. 이메일 입력 + 인증 요청/코드 입력 옵션을 정의한다.
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 {() => void} [onRequestCode] 인증 요청 버튼 클릭 시 호출(optional).
14
- * @property {string} [requestButtonLabel="인증번호 요청"] 인증 요청 버튼 라벨(optional).
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 EmailVerificationInputProps extends Omit<
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?: () => void;
33
- requestButtonLabel?: string;
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 {EmailVerificationInputProps} props 이메일 인증 props
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 {() => void} [props.onRequestCode] 인증요청 버튼 클릭 시 호출
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 EmailVerificationInput = forwardRef<
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
- <button
103
- type="button"
104
- className="input-action-button"
117
+ <InputUtilityButton
118
+ priority="primary"
105
119
  onClick={onRequestCode}
106
120
  disabled={requestButtonDisabled}
107
121
  >
108
122
  {requestButtonLabel}
109
- </button>
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
- {countdownText ? (
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
- EmailVerificationInput.displayName = "EmailVerificationInput";
161
+ EmailInput.displayName = "EmailInput";
143
162
 
144
- export { EmailVerificationInput };
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
- <button
100
- type="button"
101
- className="input-action-button"
134
+ <InputUtilityButton
135
+ priority="primary"
102
136
  onClick={onRequestCode}
103
137
  disabled={requestButtonDisabled}
104
138
  >
105
139
  {requestButtonLabel}
106
- </button>
140
+ </InputUtilityButton>
107
141
  );
108
142
  }, [onRequestCode, requestButtonDisabled, requestButtonLabel]);
109
143
 
110
- return (
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 { EmailVerificationInput } from "./EmailVerification";
5
+ export { EmailInput } from "./Email";
6
6
  export { SearchInput } from "./Search";
7
- export { IdentificationInput } from "./Identification";
7
+ export { AuthCodeInput } from "./AuthCode";
8
8
  export type { InputPasswordProps } from "./Password";
9
9
  export type { PhoneInputProps } from "./Phone";
10
- export type { EmailVerificationInputProps } from "./EmailVerification";
10
+ export type { EmailInputProps } from "./Email";
11
11
  export type { SearchInputProps } from "./Search";
12
- export type { IdentificationInputProps } from "./Identification";
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
- flex-basis: 100%;
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-element + .input-affix {
128
- // inline label 다음 줄에서 input 오른쪽 끝으로 아이콘을 밀어낸다.
129
- margin-left: auto;
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: inline-flex;
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
- .email-verification__countdown {
384
- font-size: var(--font-caption-medium-size);
385
- line-height: var(--font-caption-medium-line-height);
386
- color: var(--theme-input-helper-color);
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
  }