@uniai-fe/uds-primitives 0.0.11 → 0.0.12

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 CHANGED
@@ -194,4 +194,6 @@ src/components/{category}/
194
194
  - `CONTEXT.md` 및 `CONTEXT-*.md`: 각 컴포넌트의 상태/진행/디자인 근거
195
195
  - `RADIX-SIZE-GUIDE.md`: primitives 사이즈 체계와 Radix 매핑 규칙
196
196
 
197
+ * **컨벤션**: 모든 컴포넌트/스토리/문서는 slot/prefix/suffix 용어를 사용하지 않고, 레이아웃 기준(`header/body/footer`, 2단 구조는 `upper/lower`)과 `util*` 키워드를 사용한다. 인터랙션 함수는 `on*` 접두사를 사용하고, JSDoc `@param`은 depth 전체를 풀어 쓴다.
198
+
197
199
  필요한 컨텍스트를 확인한 뒤 컴포넌트를 import해 사용하면 됩니다.
package/dist/styles.css CHANGED
@@ -2501,7 +2501,7 @@ figure.chip {
2501
2501
  .input-field[data-size=large] {
2502
2502
  min-height: var(--theme-input-height-large);
2503
2503
  }
2504
- .input-field[data-appearance=secondary] {
2504
+ .input-field[data-priority=secondary] {
2505
2505
  border: none;
2506
2506
  border-bottom: var(--theme-input-border-width-default) solid var(--theme-input-border-color);
2507
2507
  border-radius: 0;
@@ -2509,7 +2509,7 @@ figure.chip {
2509
2509
  padding-block: var(--spacing-padding-4);
2510
2510
  background-color: transparent;
2511
2511
  }
2512
- .input-field[data-appearance=tertiary] {
2512
+ .input-field[data-priority=tertiary] {
2513
2513
  border-radius: var(--theme-input-radius-tertiary);
2514
2514
  background-color: var(--theme-input-surface);
2515
2515
  min-height: var(--theme-input-height-tertiary);
@@ -2517,30 +2517,30 @@ figure.chip {
2517
2517
  row-gap: var(--spacing-gap-1);
2518
2518
  column-gap: var(--theme-input-gap);
2519
2519
  }
2520
- .input-field[data-appearance=tertiary] .input-inline-label {
2520
+ .input-field[data-priority=tertiary] .input-inline-label {
2521
2521
  flex-basis: 100%;
2522
2522
  }
2523
- .input-field[data-appearance=tertiary] .input-element {
2523
+ .input-field[data-priority=tertiary] .input-element {
2524
2524
  min-height: var(--theme-size-medium-2);
2525
2525
  width: auto;
2526
2526
  flex: 1 1 auto;
2527
2527
  }
2528
- .input-field[data-appearance=tertiary] .input-element + .input-affix {
2528
+ .input-field[data-priority=tertiary] .input-element + .input-affix {
2529
2529
  margin-left: auto;
2530
2530
  }
2531
- .input-field:not([data-appearance=secondary])[data-state=active], .input-field:not([data-appearance=secondary])[data-state=focused] {
2531
+ .input-field:not([data-priority=secondary])[data-state=active], .input-field:not([data-priority=secondary])[data-state=focused] {
2532
2532
  border-color: var(--theme-input-border-active);
2533
2533
  border-width: var(--theme-input-border-width-emphasis);
2534
2534
  }
2535
- .input-field:not([data-appearance=secondary])[data-state=success] {
2535
+ .input-field:not([data-priority=secondary])[data-state=success] {
2536
2536
  border-color: var(--theme-input-border-success);
2537
2537
  border-width: var(--theme-input-border-width-emphasis);
2538
2538
  }
2539
- .input-field:not([data-appearance=secondary])[data-state=error] {
2539
+ .input-field:not([data-priority=secondary])[data-state=error] {
2540
2540
  border-color: var(--theme-input-border-error);
2541
2541
  border-width: var(--theme-input-border-width-emphasis);
2542
2542
  }
2543
- .input-field:not([data-appearance=secondary])[data-state=disabled] {
2543
+ .input-field:not([data-priority=secondary])[data-state=disabled] {
2544
2544
  border-color: var(--theme-input-border-disabled);
2545
2545
  border-width: var(--theme-input-border-width-default);
2546
2546
  background-color: var(--theme-input-surface-disabled);
@@ -2580,11 +2580,11 @@ figure.chip {
2580
2580
  color: var(--theme-input-label-color);
2581
2581
  }
2582
2582
 
2583
- .input-field[data-appearance=secondary] .input-element {
2583
+ .input-field[data-priority=secondary] .input-element {
2584
2584
  padding-inline: 0;
2585
2585
  }
2586
2586
 
2587
- .input-field[data-appearance=tertiary] .input-element {
2587
+ .input-field[data-priority=tertiary] .input-element {
2588
2588
  min-height: var(--theme-size-medium-2);
2589
2589
  }
2590
2590
 
@@ -2607,14 +2607,14 @@ figure.chip {
2607
2607
  min-width: 20px;
2608
2608
  color: var(--theme-input-helper-color);
2609
2609
  }
2610
- .input-affix--prefix {
2610
+ .input-affix--left {
2611
2611
  order: -1;
2612
2612
  margin-right: var(--spacing-gap-3);
2613
2613
  }
2614
- .input-affix--suffix, .input-affix--reset, .input-affix--status {
2614
+ .input-affix--right, .input-affix--clear, .input-affix--status {
2615
2615
  margin-left: var(--spacing-gap-3);
2616
2616
  }
2617
- .input-affix--reset, .input-affix--status {
2617
+ .input-affix--clear, .input-affix--status {
2618
2618
  color: var(--theme-input-text-color);
2619
2619
  }
2620
2620
  .input-affix--status[data-state=error] {
@@ -2624,22 +2624,22 @@ figure.chip {
2624
2624
  color: var(--color-primary-default);
2625
2625
  }
2626
2626
 
2627
- .input-field[data-appearance=secondary] {
2627
+ .input-field[data-priority=secondary] {
2628
2628
  border-bottom-width: var(--theme-input-border-width-default);
2629
2629
  }
2630
- .input-field[data-appearance=secondary][data-state=active], .input-field[data-appearance=secondary][data-state=focused] {
2630
+ .input-field[data-priority=secondary][data-state=active], .input-field[data-priority=secondary][data-state=focused] {
2631
2631
  border-bottom-color: var(--theme-input-border-active);
2632
2632
  border-bottom-width: var(--theme-input-border-width-emphasis);
2633
2633
  }
2634
- .input-field[data-appearance=secondary][data-state=success] {
2634
+ .input-field[data-priority=secondary][data-state=success] {
2635
2635
  border-bottom-color: var(--theme-input-border-success);
2636
2636
  border-bottom-width: var(--theme-input-border-width-emphasis);
2637
2637
  }
2638
- .input-field[data-appearance=secondary][data-state=error] {
2638
+ .input-field[data-priority=secondary][data-state=error] {
2639
2639
  border-bottom-color: var(--theme-input-border-error);
2640
2640
  border-bottom-width: var(--theme-input-border-width-emphasis);
2641
2641
  }
2642
- .input-field[data-appearance=secondary][data-state=disabled] {
2642
+ .input-field[data-priority=secondary][data-state=disabled] {
2643
2643
  border-bottom-color: var(--theme-input-border-underline-disabled);
2644
2644
  border-bottom-width: var(--theme-input-border-width-default);
2645
2645
  }
@@ -2671,7 +2671,7 @@ figure.chip {
2671
2671
  border-color: var(--theme-input-border-color);
2672
2672
  background-color: var(--theme-input-surface-disabled);
2673
2673
  }
2674
- .input[data-state=disabled] .input-field[data-appearance=secondary] {
2674
+ .input[data-state=disabled] .input-field[data-priority=secondary] {
2675
2675
  background-color: transparent;
2676
2676
  }
2677
2677
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,4 +1,5 @@
1
1
  import clsx from "clsx";
2
+ import type { ForwardedRef } from "react";
2
3
  import {
3
4
  ChangeEvent,
4
5
  FocusEvent,
@@ -7,6 +8,7 @@ import {
7
8
  useEffect,
8
9
  useId,
9
10
  useMemo,
11
+ useRef,
10
12
  useState,
11
13
  } from "react";
12
14
  import type { InputProps } from "../../types";
@@ -18,95 +20,103 @@ import {
18
20
  composeInputClassName,
19
21
  } from "../../utils";
20
22
  import ErrorIcon from "../../img/error.svg";
21
- import SuccessIcon from "../../img/success.svg";
22
23
  import ResetIcon from "../../img/reset.svg";
24
+ import SuccessIcon from "../../img/success.svg";
25
+
26
+ const setForwardedRef = <T,>(ref: ForwardedRef<T>, value: T | null): void => {
27
+ if (!ref) {
28
+ return;
29
+ }
30
+ if (typeof ref === "function") {
31
+ ref(value);
32
+ return;
33
+ }
34
+ ref.current = value;
35
+ };
23
36
 
24
37
  /**
25
- * Native `<input>` 기반 텍스트 필드. appearance/size/state 축, prefix/suffix/reset/status 슬롯,
26
- * label/helperText 같은 피드백 슬롯을 모두 제공한다.
38
+ * Native `<input>` 기반 텍스트 필드.
39
+ * priority/size/state 축과 left/right/clear/status 슬롯, label/helper 피드백 슬롯을 모두 제공하며
40
+ * react-hook-form `register` 결과를 그대로 전달받아 내부 ref/onChange/onBlur에 병합한다.
41
+ *
27
42
  * @component
28
- * @param {InputProps} props
29
- * @param {"primary" | "secondary" | "tertiary"} [props.appearance="primary"] 토큰 세트.
30
- * @param {"small" | "medium" | "large"} [props.size="medium"] 높이/spacing 세트.
31
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.state="default"] 시각 상태.
32
- * @param {boolean} [props.block=false] true면 width 100%.
33
- * @param {React.ReactNode} [props.prefix] 입력 왼쪽 추가 슬롯.
34
- * @param {React.ReactNode} [props.suffix] 입력 오른쪽 추가 슬롯.
35
- * @param {React.ReactElement} [props.resetSlot] 초기화 버튼 슬롯. 미지정 기본 reset 아이콘.
36
- * @param {React.ReactNode} [props.successIcon] success 상태에서 사용할 아이콘.
37
- * @param {React.ReactNode} [props.errorIcon] error 상태에서 사용할 아이콘.
38
- * @param {React.ReactNode} [props.label] 상단/inset label 콘텐츠.
39
- * @param {React.ReactNode} [props.helperText] helper 영역 텍스트.
40
- * @param {boolean} [props.hideHelperText] true면 helper 영역 숨김.
41
- * @param {string} [props.inputClassName] 실제 `<input>` 요소 className.
42
- * @param {string} [props.wrapperClassName] `.input-box` wrapper className.
43
- * @param {object} [props.labelProps] label attr 커스터마이즈.
44
- * @param {object} [props.helperTextProps] helper 영역 attr.
45
- * @param {boolean} [props.disabled] native disabled.
46
- * @param {string} [props.id] external id. label htmlFor 공유한다.
47
- * @param {string} [props.className] root `.input` className 합성용.
48
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.data-simulated-state]
49
- * Storybook 등에서 강제 상태 표현.
50
- * @param {string} [props.type="text"] native input type.
51
- * @param {string | number | readonly string[]} [props.defaultValue] 비제어 초기값.
52
- * @param {string | number | readonly string[]} [props.value] 제어형 값.
53
- * @param {(event: ChangeEvent<HTMLInputElement>) => void} [props.onChange] 입력 변경 핸들러.
54
- * @param {(event: FocusEvent<HTMLInputElement>) => void} [props.onFocus] focus 핸들러.
55
- * @param {(event: FocusEvent<HTMLInputElement>) => void} [props.onBlur] blur 핸들러.
43
+ * @param {InputProps} props Input 컴포넌트 공통 props
44
+ * @param {"primary" | "secondary" | "tertiary"} [props.priority="primary"] 디자인 토큰 우선순위
45
+ * @param {"small" | "medium" | "large"} [props.size="medium"] 높이/타이포 세트
46
+ * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.state="default"] 시각 상태
47
+ * @param {boolean} [props.block=false] true면 width 100%
48
+ * @param {React.ReactNode} [props.left] 입력 왼쪽 슬롯(아이콘/텍스트)
49
+ * @param {React.ReactNode} [props.right] 입력 오른쪽 슬롯
50
+ * @param {React.ReactNode} [props.clearIcon] 입력값 초기화 아이콘. 지정하지 않으면 기본 Reset 아이콘
51
+ * @param {React.ReactNode} [props.successIcon] success 상태 아이콘 override
52
+ * @param {React.ReactNode} [props.errorIcon] error 상태 아이콘 override
53
+ * @param {React.ReactNode} [props.label] 상단 또는 inline label 콘텐츠
54
+ * @param {React.ReactNode} [props.helper] helper 텍스트 콘텐츠
55
+ * @param {boolean} [props.hideHelper] true면 helper 영역 숨김
56
+ * @param {string} [props.inputClassName] 실제 `<input>` 요소 className
57
+ * @param {string} [props.boxClassName] `.input-box` className
58
+ * @param {ComponentPropsWithoutRef<"label">} [props.labelProps] label 추가 속성
59
+ * @param {ComponentPropsWithoutRef<"div">} [props.helperProps] helper container 속성
60
+ * @param {boolean} [props.disabled] native disabled
61
+ * @param {string} [props.id] 외부 id. label htmlFor와 공유된다
62
+ * @param {string} [props.className] root `.input` className
63
+ * @param {UseFormRegisterReturn} [props.register] react-hook-form register 반환값
64
+ * @param {string} [props.name] native name. register 사용 시 자동으로 병합
65
+ * @param {InputProps["data-simulated-state"]} [props.data-simulated-state] Storybook 등에서 시각 상태 강제용
66
+ * @param {(event: ChangeEvent<HTMLInputElement>) => void} [props.onChange] change 핸들러
67
+ * @param {(event: FocusEvent<HTMLInputElement>) => void} [props.onFocus] focus 핸들러
68
+ * @param {(event: FocusEvent<HTMLInputElement>) => void} [props.onBlur] blur 핸들러
69
+ * @param {string | number | readonly string[]} [props.value] 제어형
70
+ * @param {string | number | readonly string[]} [props.defaultValue] 비제어 초기값
71
+ * @param {string} [props.type="text"] native input type
56
72
  */
57
73
  const Text = forwardRef<HTMLInputElement, InputProps>(
58
74
  (
59
75
  {
60
- appearance = "primary",
76
+ priority = "primary",
61
77
  size = "medium",
62
78
  state: stateProp = "default",
63
79
  block = false,
64
- prefix,
65
- suffix,
66
- resetSlot,
80
+ left,
81
+ right,
82
+ clearIcon,
67
83
  successIcon,
68
84
  errorIcon,
69
85
  label,
70
- helperText,
71
- hideHelperText,
86
+ helper,
87
+ hideHelper,
72
88
  inputClassName,
73
- wrapperClassName,
74
- helperTextProps,
89
+ boxClassName,
90
+ helperProps,
75
91
  labelProps,
76
92
  disabled,
77
93
  id,
78
94
  className,
95
+ register,
79
96
  "data-simulated-state": simulatedState,
80
- type = "text",
81
- defaultValue,
82
97
  value,
98
+ defaultValue,
99
+ name,
83
100
  onChange,
84
101
  onFocus,
85
102
  onBlur,
103
+ type = "text",
86
104
  ...restProps
87
105
  },
88
106
  forwardedRef,
89
107
  ) => {
90
108
  const generatedId = useId();
91
- const [uncontrolledValue, setUncontrolledValue] = useState<
92
- string | number | readonly string[] | undefined
93
- >(defaultValue);
109
+ const registerRef = register?.ref;
110
+ const registerOnChange = register?.onChange;
111
+ const registerOnBlur = register?.onBlur;
112
+ const inputRef = useRef<HTMLInputElement | null>(null);
94
113
  const [isFocused, setIsFocused] = useState(false);
95
- const resolvedState = useMemo(
96
- () => (disabled ? "disabled" : stateProp),
97
- [disabled, stateProp],
98
- );
99
- const fieldId = useMemo(
100
- () => id ?? labelProps?.htmlFor ?? generatedId,
101
- [generatedId, id, labelProps?.htmlFor],
102
- );
103
- const shouldShowHelper = Boolean(helperText) && !hideHelperText;
104
- const helperElementId = useMemo(() => {
105
- if (!shouldShowHelper) {
106
- return undefined;
107
- }
108
- return helperTextProps?.id ?? `${fieldId}-helper-text`;
109
- }, [fieldId, helperTextProps?.id, shouldShowHelper]);
114
+ const [hasValue, setHasValue] = useState(() => {
115
+ const initial = value ?? defaultValue;
116
+ return initial !== undefined && initial !== null
117
+ ? String(initial).length > 0
118
+ : false;
119
+ });
110
120
 
111
121
  useEffect(() => {
112
122
  if (stateProp === "disabled" || disabled) {
@@ -114,6 +124,16 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
114
124
  }
115
125
  }, [disabled, stateProp]);
116
126
 
127
+ useEffect(() => {
128
+ if (value !== undefined && value !== null) {
129
+ setHasValue(String(value).length > 0);
130
+ }
131
+ }, [value]);
132
+
133
+ const resolvedState = useMemo(
134
+ () => (disabled ? "disabled" : stateProp),
135
+ [disabled, stateProp],
136
+ );
117
137
  const visualState = useMemo(() => {
118
138
  if (resolvedState === "disabled") {
119
139
  return "disabled";
@@ -124,6 +144,17 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
124
144
  return isFocused ? "active" : resolvedState;
125
145
  }, [isFocused, resolvedState]);
126
146
 
147
+ const fieldId = useMemo(
148
+ () => id ?? labelProps?.htmlFor ?? generatedId,
149
+ [generatedId, id, labelProps?.htmlFor],
150
+ );
151
+ const helperElementId = useMemo(() => {
152
+ if (!helper || hideHelper) {
153
+ return undefined;
154
+ }
155
+ return helperProps?.id ?? `${fieldId}-helper-text`;
156
+ }, [fieldId, helperProps?.id, helper, hideHelper]);
157
+
127
158
  const defaultStatusIcon = useMemo(() => {
128
159
  if (resolvedState === "success") {
129
160
  return <SuccessIcon aria-hidden="true" />;
@@ -144,51 +175,46 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
144
175
  return null;
145
176
  }, [defaultStatusIcon, errorIcon, resolvedState, successIcon]);
146
177
 
147
- const defaultResetSlot = useMemo(() => {
178
+ const defaultClearIcon = useMemo(() => {
148
179
  if (visualState === "active") {
149
180
  return <ResetIcon aria-hidden="true" />;
150
181
  }
151
182
  return null;
152
183
  }, [visualState]);
153
184
 
154
- const effectiveResetSlot = resetSlot ?? defaultResetSlot;
155
- const currentValue = value ?? uncontrolledValue;
156
- const hasInputValue =
157
- currentValue !== undefined && currentValue !== null
158
- ? String(currentValue).length > 0
159
- : false;
160
- const showResetSlot = Boolean(
161
- effectiveResetSlot && hasInputValue && resolvedState !== "disabled",
185
+ const effectiveClearIcon = clearIcon ?? defaultClearIcon;
186
+ const showClearIcon = Boolean(
187
+ effectiveClearIcon && hasValue && resolvedState !== "disabled",
162
188
  );
163
189
  const isDisabled = resolvedState === "disabled";
164
190
  const labelFor = labelProps?.htmlFor ?? fieldId;
165
191
  const containerClassName = useMemo(
166
192
  () =>
167
193
  composeInputClassName({
168
- appearance,
194
+ priority,
169
195
  size,
170
196
  state: visualState,
171
197
  block,
172
198
  className,
173
199
  }),
174
- [appearance, block, className, size, visualState],
200
+ [priority, block, className, size, visualState],
175
201
  );
176
- const boxClassName = useMemo(
202
+ const fieldBoxClassName = useMemo(
177
203
  () =>
178
204
  composeInputBoxClassName({
179
- appearance,
205
+ priority,
180
206
  size,
181
207
  state: visualState,
182
208
  block,
183
- className: wrapperClassName,
209
+ className: boxClassName,
184
210
  }),
185
- [appearance, block, size, visualState, wrapperClassName],
211
+ [priority, block, size, visualState, boxClassName],
186
212
  );
187
213
  const helperClassName = useMemo(
188
- () => clsx("input-helper-text", helperTextProps?.className),
189
- [helperTextProps?.className],
214
+ () => clsx("input-helper-text", helperProps?.className),
215
+ [helperProps?.className],
190
216
  );
191
- const shouldRenderInlineLabel = appearance === "tertiary" && Boolean(label);
217
+ const shouldRenderInlineLabel = priority === "tertiary" && Boolean(label);
192
218
  const labelClassName = useMemo(
193
219
  () =>
194
220
  clsx(
@@ -203,6 +229,15 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
203
229
  [inputClassName],
204
230
  );
205
231
 
232
+ const mergedRef = useCallback(
233
+ (node: HTMLInputElement | null) => {
234
+ inputRef.current = node;
235
+ setForwardedRef(forwardedRef, node);
236
+ registerRef?.(node);
237
+ },
238
+ [forwardedRef, registerRef],
239
+ );
240
+
206
241
  const handleFocus = useCallback(
207
242
  (event: FocusEvent<HTMLInputElement>) => {
208
243
  setIsFocused(true);
@@ -214,25 +249,27 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
214
249
  const handleBlur = useCallback(
215
250
  (event: FocusEvent<HTMLInputElement>) => {
216
251
  setIsFocused(false);
252
+ registerOnBlur?.(event);
217
253
  onBlur?.(event);
218
254
  },
219
- [onBlur],
255
+ [onBlur, registerOnBlur],
220
256
  );
221
257
 
222
258
  const handleChange = useCallback(
223
259
  (event: ChangeEvent<HTMLInputElement>) => {
224
- if (value === undefined) {
225
- setUncontrolledValue(event.target.value);
226
- }
260
+ setHasValue(event.currentTarget.value.length > 0);
261
+ registerOnChange?.(event);
227
262
  onChange?.(event);
228
263
  },
229
- [onChange, value],
264
+ [onChange, registerOnChange],
230
265
  );
231
266
 
267
+ const inputName = register?.name ?? name;
268
+
232
269
  return (
233
270
  <div
234
271
  className={containerClassName}
235
- data-appearance={appearance}
272
+ data-priority={priority}
236
273
  data-size={size}
237
274
  data-state={visualState}
238
275
  data-block={block ? "true" : undefined}
@@ -244,17 +281,17 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
244
281
  className={labelClassName}
245
282
  htmlFor={labelFor}
246
283
  data-slot="label"
247
- data-appearance={appearance}
284
+ data-priority={priority}
248
285
  data-state={visualState}
249
286
  >
250
287
  {label}
251
288
  </label>
252
289
  ) : null}
253
- <div className={boxClassName} data-slot="box">
290
+ <div className={fieldBoxClassName} data-slot="box">
254
291
  <div
255
292
  className={INPUT_FIELD_CLASSNAME}
256
293
  data-state={visualState}
257
- data-appearance={appearance}
294
+ data-priority={priority}
258
295
  data-size={size}
259
296
  data-block={block ? "true" : undefined}
260
297
  >
@@ -267,44 +304,45 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
267
304
  {label}
268
305
  </div>
269
306
  ) : null}
270
- {prefix ? (
307
+ {left ? (
271
308
  <div
272
- className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--prefix`}
273
- data-slot="prefix"
309
+ className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--left`}
310
+ data-slot="left"
274
311
  >
275
- {prefix}
312
+ {left}
276
313
  </div>
277
314
  ) : null}
278
315
  <input
279
316
  {...restProps}
280
317
  id={fieldId}
281
- ref={forwardedRef}
318
+ ref={mergedRef}
282
319
  className={inputElementClassName}
283
320
  disabled={isDisabled}
284
321
  aria-invalid={resolvedState === "error" ? true : undefined}
285
322
  aria-describedby={helperElementId}
286
323
  type={type}
287
- defaultValue={defaultValue}
288
324
  value={value}
325
+ defaultValue={defaultValue}
326
+ name={inputName}
289
327
  onChange={handleChange}
290
328
  onFocus={handleFocus}
291
329
  onBlur={handleBlur}
292
330
  />
293
- {suffix ? (
331
+ {right ? (
294
332
  <div
295
- className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--suffix`}
296
- data-slot="suffix"
333
+ className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--right`}
334
+ data-slot="right"
297
335
  >
298
- {suffix}
336
+ {right}
299
337
  </div>
300
338
  ) : null}
301
- {showResetSlot ? (
339
+ {showClearIcon ? (
302
340
  <div
303
- className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--reset`}
304
- data-slot="reset"
341
+ className={`${INPUT_AFFIX_CLASSNAME} ${INPUT_AFFIX_CLASSNAME}--clear`}
342
+ data-slot="clear"
305
343
  data-visible="true"
306
344
  >
307
- {effectiveResetSlot}
345
+ {effectiveClearIcon}
308
346
  </div>
309
347
  ) : null}
310
348
  {statusSlot ? (
@@ -318,16 +356,16 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
318
356
  ) : null}
319
357
  </div>
320
358
  </div>
321
- {shouldShowHelper ? (
359
+ {helper && !hideHelper ? (
322
360
  <div
323
- {...helperTextProps}
361
+ {...helperProps}
324
362
  className={helperClassName}
325
363
  id={helperElementId}
326
- data-slot="helper-text"
327
- data-appearance={appearance}
364
+ data-slot="helper"
365
+ data-priority={priority}
328
366
  data-state={visualState}
329
367
  >
330
- {helperText}
368
+ {helper}
331
369
  </div>
332
370
  ) : null}
333
371
  </div>
@@ -15,14 +15,14 @@ import type { InputProps } from "../../types";
15
15
  * IdentificationInput props. 고정 길이 숫자 코드 입력에 필요한 label/helper/state/onComplete를 제공한다.
16
16
  * @property {number} [length=6] 입력칸 개수(4~8 사이로 자동 보정).
17
17
  * @property {InputProps["label"]} [label] 상단 라벨.
18
- * @property {InputProps["helperText"]} [helperText] helper 텍스트.
18
+ * @property {InputProps["helper"]} [helper] helper 텍스트.
19
19
  * @property {InputProps["state"]} [state="default"] 시각 상태.
20
20
  * @property {(code: string) => void} [onComplete] 모든 셀이 채워졌을 때 호출.
21
21
  */
22
22
  export interface IdentificationInputProps {
23
23
  length?: number;
24
24
  label?: InputProps["label"];
25
- helperText?: InputProps["helperText"];
25
+ helper?: InputProps["helper"];
26
26
  state?: InputProps["state"];
27
27
  onComplete?: (code: string) => void;
28
28
  }
@@ -33,14 +33,14 @@ export interface IdentificationInputProps {
33
33
  * @param {IdentificationInputProps} props
34
34
  * @param {number} [props.length=6] 입력 필드 길이. 4~8 범위로 자동 보정된다.
35
35
  * @param {InputProps["label"]} [props.label] 상단 label 콘텐츠.
36
- * @param {InputProps["helperText"]} [props.helperText] helper 텍스트.
36
+ * @param {InputProps["helper"]} [props.helper] helper 텍스트.
37
37
  * @param {InputProps["state"]} [props.state] 시각 상태.
38
38
  * @param {(code: string) => void} [props.onComplete] 모든 셀이 채워졌을 때 호출되는 콜백.
39
39
  */
40
40
  const IdentificationInput = forwardRef<
41
41
  HTMLInputElement[],
42
42
  IdentificationInputProps
43
- >(({ length = 6, label, helperText, state, onComplete }, forwardedRef) => {
43
+ >(({ length = 6, label, helper, state, onComplete }, forwardedRef) => {
44
44
  const safeLength = Math.max(4, Math.min(8, length));
45
45
  const [values, setValues] = useState(() =>
46
46
  Array.from({ length: safeLength }, () => ""),
@@ -112,7 +112,7 @@ const IdentificationInput = forwardRef<
112
112
  [onComplete, safeLength],
113
113
  );
114
114
 
115
- const helperNode = useMemo(() => helperText, [helperText]);
115
+ const helperNode = useMemo(() => helper, [helper]);
116
116
 
117
117
  // forwardRef 사용자는 각 셀 DOM 배열을 직접 제어할 수 있도록 노출한다.
118
118
  useImperativeHandle(
@@ -5,61 +5,32 @@ import HideOffIcon from "../../img/hide-off.svg";
5
5
  import HideOnIcon from "../../img/hide-on.svg";
6
6
 
7
7
  /**
8
- * PasswordInput 전용 props. Text Input props에서 type/defaultValue 제약을 재정의하고 toggle 옵션을 추가한다.
8
+ * PasswordInput 전용 props. Text Input props에서 type password 전용으로 고정하고 보기/숨김 토글 옵션을 확장한다.
9
9
  * @property {boolean} [defaultVisible=false] 초기 렌더 시 비밀번호를 드러낼지 여부.
10
10
  * @property {{show: string; hide: string}} [toggleLabel] 토글 버튼에 사용할 라벨 텍스트 집합.
11
- * @property {string} [defaultValue] 비제어 초기값.
12
11
  */
13
- export interface PasswordInputProps extends Omit<
14
- InputProps,
15
- "type" | "defaultValue"
16
- > {
12
+ export interface PasswordInputProps extends Omit<InputProps, "type"> {
17
13
  defaultVisible?: boolean;
18
14
  toggleLabel?: {
19
15
  show: string;
20
16
  hide: string;
21
17
  };
22
- defaultValue?: string;
23
18
  }
24
19
 
25
20
  /**
26
- * PasswordInput — 기본 Text 입력을 비밀번호 토글 UX로 확장.
21
+ * PasswordInput — 기본 Text 입력을 비밀번호 토글 UX로 확장한다.
27
22
  * @component
28
23
  * @param {PasswordInputProps} props
29
- * @param {"primary" | "secondary" | "tertiary"} [props.appearance="primary"] 토큰 세트.
30
- * @param {"small" | "medium" | "large"} [props.size="medium"] 높이/spacing.
31
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.state="default"] 시각 상태.
32
- * @param {boolean} [props.block=false] true면 width 100%.
33
- * @param {React.ReactNode} [props.prefix] prefix 슬롯(Password에서는 기본 제공하지 않음).
34
- * @param {React.ReactNode} [props.suffix] suffix 슬롯. 미지정 시 보기/숨김 토글 버튼 자동 배치.
35
- * @param {React.ReactNode} [props.successIcon] success 상태 아이콘.
36
- * @param {React.ReactNode} [props.errorIcon] error 상태 아이콘.
37
- * @param {React.ReactNode} [props.label] label 영역 콘텐츠.
38
- * @param {React.ReactNode} [props.helperText] helper 영역 텍스트.
39
- * @param {boolean} [props.hideHelperText] helper 숨김 여부.
40
- * @param {string} [props.inputClassName] `<input>` className.
41
- * @param {string} [props.wrapperClassName] `.input-box` className.
42
- * @param {object} [props.labelProps] label attr.
43
- * @param {object} [props.helperTextProps] helper attr.
44
- * @param {boolean} [props.disabled] native disabled.
45
- * @param {string} [props.id] 외부 id.
46
- * @param {string} [props.className] root className.
47
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.data-simulated-state]
48
- * Storybook 시각 상태 강제용.
49
- * @param {string} [props.defaultValue] 비제어 초기값.
50
- * @param {string | number | readonly string[]} [props.value] 제어형 값.
51
- * @param {(event: React.ChangeEvent<HTMLInputElement>) => void} [props.onChange] 변경 핸들러.
52
- * @param {(event: React.FocusEvent<HTMLInputElement>) => void} [props.onFocus] focus 핸들러.
53
- * @param {(event: React.FocusEvent<HTMLInputElement>) => void} [props.onBlur] blur 핸들러.
54
24
  * @param {boolean} [props.defaultVisible=false] 초기 노출 여부.
55
- * @param {{show: string; hide: string}} [props.toggleLabel] 토글 버튼 라벨.
25
+ * @param {{show: string; hide: string}} [props.toggleLabel={show:"보기",hide:"숨김"}] 토글 버튼 라벨.
26
+ * 나머지 props는 Text Input과 동일하다.
56
27
  */
57
28
  const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
58
29
  (
59
30
  {
60
31
  defaultVisible = false,
61
32
  toggleLabel = { show: "보기", hide: "숨김" },
62
- suffix,
33
+ right,
63
34
  defaultValue,
64
35
  ...restProps
65
36
  },
@@ -93,7 +64,7 @@ const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
93
64
  ref={forwardedRef}
94
65
  type={visible ? "text" : "password"}
95
66
  defaultValue={defaultValue}
96
- suffix={suffix ?? toggleButton}
67
+ right={right ?? toggleButton}
97
68
  />
98
69
  );
99
70
  },
@@ -10,19 +10,13 @@ import { Text } from "./Base";
10
10
  * @property {string} [defaultValue] 비제어 초기값(숫자/포맷 모두 허용).
11
11
  * @property {(value: string, digits: string) => void} [onValueChange] 포맷팅 값/숫자만 값을 함께 전달.
12
12
  * @property {ComponentPropsWithoutRef<"input">["onChange"]} [onChange] native onChange override.
13
- * @property {() => void} [onRequestCode] suffix 버튼 클릭 시 호출.
14
- * @property {string} [requestButtonLabel="인증번호 요청"] suffix 버튼 라벨.
15
- * @property {boolean} [requestButtonDisabled] suffix 버튼 disabled 여부.
13
+ * @property {() => void} [onRequestCode] right 슬롯 버튼 클릭 시 호출.
14
+ * @property {string} [requestButtonLabel="인증번호 요청"] right 슬롯 버튼 라벨.
15
+ * @property {boolean} [requestButtonDisabled] right 슬롯 버튼 disabled 여부.
16
16
  */
17
17
  export interface PhoneInputProps extends Omit<
18
18
  InputProps,
19
- | "type"
20
- | "prefix"
21
- | "inputMode"
22
- | "pattern"
23
- | "onChange"
24
- | "value"
25
- | "defaultValue"
19
+ "type" | "inputMode" | "pattern" | "onChange" | "value" | "defaultValue"
26
20
  > {
27
21
  value?: string;
28
22
  defaultValue?: string;
@@ -43,33 +37,8 @@ const formatPhoneNumber = (digits: string) => maskPhone(digits);
43
37
  /**
44
38
  * PhoneInput — 휴대폰 번호 마스킹과 인증번호 요청 버튼을 제공하는 입력.
45
39
  * @component
46
- * @param {PhoneInputProps} props
47
- * @param {"primary" | "secondary" | "tertiary"} [props.appearance="primary"] 토큰 세트.
48
- * @param {"small" | "medium" | "large"} [props.size="medium"] 높이/spacing.
49
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.state="default"] 시각 상태.
50
- * @param {boolean} [props.block=false] true면 width 100%.
51
- * @param {React.ReactNode} [props.suffix] suffix 슬롯. 미지정 시 인증 버튼 자동 배치.
52
- * @param {React.ReactNode} [props.successIcon] success 상태 아이콘.
53
- * @param {React.ReactNode} [props.errorIcon] error 상태 아이콘.
54
- * @param {React.ReactNode} [props.label] label 콘텐츠.
55
- * @param {React.ReactNode} [props.helperText] helper 텍스트.
56
- * @param {boolean} [props.hideHelperText] helper 숨김 여부.
57
- * @param {string} [props.inputClassName] `<input>` className.
58
- * @param {string} [props.wrapperClassName] `.input-box` className.
59
- * @param {object} [props.labelProps] label attr.
60
- * @param {object} [props.helperTextProps] helper attr.
61
- * @param {boolean} [props.disabled] native disabled.
62
- * @param {string} [props.id] 외부 id.
63
- * @param {string} [props.className] root className.
64
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.data-simulated-state]
65
- * Storybook 시각 상태 강제용.
66
- * @param {string} [props.value] 제어형 값(포맷팅).
67
- * @param {string} [props.defaultValue] 비제어 초기값.
68
- * @param {(value: string, digits: string) => void} [props.onValueChange] 포맷팅/숫자 값 동시 전달.
69
- * @param {(event: ChangeEvent<HTMLInputElement>) => void} [props.onChange] native onChange override.
70
- * @param {() => void} [props.onRequestCode] 인증번호 요청 버튼 핸들러.
71
- * @param {string} [props.requestButtonLabel="인증번호 요청"] 버튼 라벨.
72
- * @param {boolean} [props.requestButtonDisabled] 버튼 disabled.
40
+ * @param {PhoneInputProps} props Phone 전용 props.
41
+ * 나머지 Text Input 공통 props(priority/size/state/helper 등)도 동일하게 사용할 수 있다.
73
42
  */
74
43
  const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
75
44
  (
@@ -81,7 +50,7 @@ const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
81
50
  onRequestCode,
82
51
  requestButtonLabel = "인증번호 요청",
83
52
  requestButtonDisabled,
84
- suffix,
53
+ right,
85
54
  ...restProps
86
55
  },
87
56
  forwardedRef,
@@ -119,7 +88,7 @@ const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
119
88
  if (!onRequestCode) {
120
89
  return null;
121
90
  }
122
- // 휴대폰 인증 입력은 우측 suffix에 인증 버튼을 둔다.
91
+ // 휴대폰 인증 입력은 우측 right 슬롯에 인증 버튼을 둔다.
123
92
  return (
124
93
  <button
125
94
  type="button"
@@ -140,7 +109,7 @@ const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
140
109
  inputMode="tel"
141
110
  value={formattedValue}
142
111
  onChange={handleChange}
143
- suffix={suffix ?? actionButton}
112
+ right={right ?? actionButton}
144
113
  />
145
114
  );
146
115
  },
@@ -9,52 +9,21 @@ import SearchIcon from "../../img/search.svg";
9
9
  export interface SearchInputProps extends Omit<InputProps, "type"> {}
10
10
 
11
11
  /**
12
- * SearchInput — 기본 Text 입력에 검색 아이콘 prefix를 제공한다.
13
- * @component
14
- * @param {SearchInputProps} props
15
- * @param {"primary" | "secondary" | "tertiary"} [props.appearance="primary"] 토큰 세트.
16
- * @param {"small" | "medium" | "large"} [props.size="medium"] 높이/spacing.
17
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.state="default"] 시각 상태.
18
- * @param {boolean} [props.block=false] true면 width 100%.
19
- * @param {React.ReactNode} [props.prefix] prefix 슬롯. 미지정 시 돋보기 아이콘이 자동 배치된다.
20
- * @param {React.ReactNode} [props.suffix] suffix 슬롯.
21
- * @param {React.ReactNode} [props.successIcon] success 상태 아이콘.
22
- * @param {React.ReactNode} [props.errorIcon] error 상태 아이콘.
23
- * @param {React.ReactNode} [props.label] label 콘텐츠.
24
- * @param {React.ReactNode} [props.helperText] helper 텍스트.
25
- * @param {boolean} [props.hideHelperText] helper 숨김 여부.
26
- * @param {string} [props.inputClassName] `<input>` className.
27
- * @param {string} [props.wrapperClassName] `.input-box` className.
28
- * @param {object} [props.labelProps] label attr.
29
- * @param {object} [props.helperTextProps] helper attr.
30
- * @param {boolean} [props.disabled] native disabled.
31
- * @param {string} [props.id] 외부 id.
32
- * @param {string} [props.className] root className.
33
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.data-simulated-state]
34
- * Storybook 시각 상태 강제용.
35
- * @param {string | number | readonly string[]} [props.defaultValue] 비제어 초기값.
36
- * @param {string | number | readonly string[]} [props.value] 제어형 값.
37
- * @param {(event: React.ChangeEvent<HTMLInputElement>) => void} [props.onChange] 입력 변경 핸들러.
38
- * @param {(event: React.FocusEvent<HTMLInputElement>) => void} [props.onFocus] focus 핸들러.
39
- * @param {(event: React.FocusEvent<HTMLInputElement>) => void} [props.onBlur] blur 핸들러.
12
+ * SearchInput — 기본 Text 입력에 검색 아이콘 left 슬롯과 type="search"를 제공한다.
13
+ * 다른 props(priority/size/helper 등)는 Text Input과 동일하다.
40
14
  */
41
15
  const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
42
- ({ prefix, ...restProps }, forwardedRef) => {
43
- const prefixNode = useMemo(() => {
44
- if (prefix) {
45
- return prefix;
16
+ ({ left, ...restProps }, forwardedRef) => {
17
+ const leftNode = useMemo(() => {
18
+ if (left) {
19
+ return left;
46
20
  }
47
- // 검색 input은 기본적으로 돋보기 아이콘 prefix를 노출한다.
21
+ // 검색 input은 기본적으로 돋보기 아이콘을 left 슬롯에 노출한다.
48
22
  return <SearchIcon aria-hidden="true" />;
49
- }, [prefix]);
23
+ }, [left]);
50
24
 
51
25
  return (
52
- <Text
53
- {...restProps}
54
- ref={forwardedRef}
55
- type="search"
56
- prefix={prefixNode}
57
- />
26
+ <Text {...restProps} ref={forwardedRef} type="search" left={leftNode} />
58
27
  );
59
28
  },
60
29
  );
@@ -96,7 +96,7 @@
96
96
  min-height: var(--theme-input-height-large);
97
97
  }
98
98
 
99
- &[data-appearance="secondary"] {
99
+ &[data-priority="secondary"] {
100
100
  border: none;
101
101
  border-bottom: var(--theme-input-border-width-default) solid
102
102
  var(--theme-input-border-color);
@@ -106,7 +106,7 @@
106
106
  background-color: transparent;
107
107
  }
108
108
 
109
- &[data-appearance="tertiary"] {
109
+ &[data-priority="tertiary"] {
110
110
  border-radius: var(--theme-input-radius-tertiary);
111
111
  background-color: var(--theme-input-surface);
112
112
  min-height: var(--theme-input-height-tertiary);
@@ -130,7 +130,7 @@
130
130
  }
131
131
  }
132
132
 
133
- &:not([data-appearance="secondary"]) {
133
+ &:not([data-priority="secondary"]) {
134
134
  &[data-state="active"],
135
135
  &[data-state="focused"] {
136
136
  border-color: var(--theme-input-border-active);
@@ -196,11 +196,11 @@
196
196
  color: var(--theme-input-label-color);
197
197
  }
198
198
 
199
- .input-field[data-appearance="secondary"] .input-element {
199
+ .input-field[data-priority="secondary"] .input-element {
200
200
  padding-inline: 0;
201
201
  }
202
202
 
203
- .input-field[data-appearance="tertiary"] .input-element {
203
+ .input-field[data-priority="tertiary"] .input-element {
204
204
  min-height: var(--theme-size-medium-2);
205
205
  }
206
206
 
@@ -225,18 +225,18 @@
225
225
  min-width: 20px;
226
226
  color: var(--theme-input-helper-color);
227
227
 
228
- &--prefix {
228
+ &--left {
229
229
  order: -1;
230
230
  margin-right: var(--spacing-gap-3);
231
231
  }
232
232
 
233
- &--suffix,
234
- &--reset,
233
+ &--right,
234
+ &--clear,
235
235
  &--status {
236
236
  margin-left: var(--spacing-gap-3);
237
237
  }
238
238
 
239
- &--reset,
239
+ &--clear,
240
240
  &--status {
241
241
  color: var(--theme-input-text-color);
242
242
  }
@@ -250,7 +250,7 @@
250
250
  color: var(--color-primary-default);
251
251
  }
252
252
  }
253
- .input-field[data-appearance="secondary"] {
253
+ .input-field[data-priority="secondary"] {
254
254
  border-bottom-width: var(--theme-input-border-width-default);
255
255
 
256
256
  &[data-state="active"],
@@ -307,7 +307,7 @@
307
307
  border-color: var(--theme-input-border-color);
308
308
  background-color: var(--theme-input-surface-disabled);
309
309
 
310
- &[data-appearance="secondary"] {
310
+ &[data-priority="secondary"] {
311
311
  background-color: transparent;
312
312
  }
313
313
  }
@@ -1,9 +1,10 @@
1
- import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from "react";
1
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
+ import type { UseFormRegisterReturn } from "react-hook-form";
2
3
 
3
4
  /**
4
- * appearance 축은 tokens 기반 테마 계층을 지정한다.
5
+ * priority 축은 tokens 기반 테마 계층을 지정한다.
5
6
  */
6
- export const INPUT_APPEARANCES = ["primary", "secondary", "tertiary"] as const;
7
+ export const INPUT_PRIORITIES = ["primary", "secondary", "tertiary"] as const;
7
8
  /**
8
9
  * size 축은 높이/타이포/spacing을 결정한다.
9
10
  */
@@ -20,48 +21,56 @@ export const INPUT_STATES = [
20
21
  "disabled",
21
22
  ] as const;
22
23
 
23
- export type InputAppearance = (typeof INPUT_APPEARANCES)[number];
24
+ export type InputPriority = (typeof INPUT_PRIORITIES)[number];
24
25
  export type InputSize = (typeof INPUT_SIZES)[number];
25
26
  export type InputState = (typeof INPUT_STATES)[number];
26
27
 
27
28
  type NativeInputProps = ComponentPropsWithoutRef<"input">;
28
29
 
29
30
  /**
30
- * prefix/suffix와 status 아이콘 슬롯 정의.
31
+ * 좌우 슬롯과 status 아이콘 정의.
31
32
  */
32
- export interface InputAffix {
33
- prefix?: ReactNode;
34
- suffix?: ReactNode;
35
- resetSlot?: ReactElement;
33
+ export interface InputSlots {
34
+ left?: ReactNode;
35
+ right?: ReactNode;
36
+ clearIcon?: ReactNode;
36
37
  successIcon?: ReactNode;
37
38
  errorIcon?: ReactNode;
38
39
  }
39
40
 
40
41
  /**
41
- * label/helperText 등 피드백 슬롯 정의.
42
+ * label/helper 등 피드백 슬롯 정의.
42
43
  */
43
44
  export interface InputFeedback {
44
45
  label?: ReactNode;
45
- helperText?: ReactNode;
46
- hideHelperText?: boolean;
46
+ helper?: ReactNode;
47
+ hideHelper?: boolean;
47
48
  }
48
49
 
49
50
  /**
50
- * 텍스트 입력의 핵심 props. native input 속성에서 size/prefix/suffix는 제외하고 자체 슬롯으로 교체했다.
51
+ * 텍스트 입력의 핵심 props. native input 속성에서 size는 제외하고 left/right 슬롯을 별도로 정의한다.
51
52
  */
52
53
  export interface InputProps
53
- extends
54
- Omit<NativeInputProps, "prefix" | "suffix" | "size">,
55
- InputAffix,
56
- InputFeedback {
57
- appearance?: InputAppearance;
54
+ extends Omit<NativeInputProps, "size">, InputSlots, InputFeedback {
55
+ /** semantic color/token 세트 */
56
+ priority?: InputPriority;
57
+ /** 높이/타이포 세트 */
58
58
  size?: InputSize;
59
+ /** 시각 상태. disabled prop과 조합된다 */
59
60
  state?: InputState;
61
+ /** true면 width:100% */
60
62
  block?: boolean;
63
+ /** 실제 `<input>` className */
61
64
  inputClassName?: string;
62
- wrapperClassName?: string;
65
+ /** `.input-box` className */
66
+ boxClassName?: string;
67
+ /** label attr 커스터마이즈 */
63
68
  labelProps?: ComponentPropsWithoutRef<"label">;
64
- helperTextProps?: ComponentPropsWithoutRef<"div">;
69
+ /** helper attr 커스터마이즈 */
70
+ helperProps?: ComponentPropsWithoutRef<"div">;
71
+ /** react-hook-form register 반환값 */
72
+ register?: UseFormRegisterReturn;
73
+ /** Storybook 등에서 강제 상태 표현용 */
65
74
  "data-simulated-state"?: InputState;
66
75
  }
67
76
 
@@ -69,7 +78,7 @@ export interface InputProps
69
78
  * className composer helper가 필요로 하는 파라미터 집합.
70
79
  */
71
80
  export interface InputClassNameOptions {
72
- appearance: InputAppearance;
81
+ priority: InputPriority;
73
82
  size: InputSize;
74
83
  state: InputState;
75
84
  block: boolean;
@@ -11,7 +11,7 @@ const INPUT_AFFIX_CLASSNAME = "input-affix";
11
11
  * container `.input` element className을 조립한다.
12
12
  */
13
13
  const composeInputClassName = ({
14
- appearance,
14
+ priority,
15
15
  size,
16
16
  state,
17
17
  block,
@@ -19,7 +19,7 @@ const composeInputClassName = ({
19
19
  }: InputClassNameOptions) =>
20
20
  clsx(
21
21
  INPUT_CLASSNAME,
22
- `${INPUT_CLASSNAME}--appearance-${appearance}`,
22
+ `${INPUT_CLASSNAME}--priority-${priority}`,
23
23
  `${INPUT_CLASSNAME}--size-${size}`,
24
24
  {
25
25
  [`${INPUT_CLASSNAME}--state-${state}`]: state !== "default",
@@ -32,7 +32,7 @@ const composeInputClassName = ({
32
32
  * 박스(wrapper) `.input-box` className을 조립한다.
33
33
  */
34
34
  const composeInputBoxClassName = ({
35
- appearance,
35
+ priority,
36
36
  size,
37
37
  state,
38
38
  block,
@@ -40,7 +40,7 @@ const composeInputBoxClassName = ({
40
40
  }: InputClassNameOptions) =>
41
41
  clsx(
42
42
  INPUT_BOX_CLASSNAME,
43
- `${INPUT_BOX_CLASSNAME}--appearance-${appearance}`,
43
+ `${INPUT_BOX_CLASSNAME}--priority-${priority}`,
44
44
  `${INPUT_BOX_CLASSNAME}--size-${size}`,
45
45
  {
46
46
  [`${INPUT_BOX_CLASSNAME}--state-${state}`]: state !== "default",