@uniai-fe/uds-primitives 0.12.4 → 0.12.6

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.
Files changed (55) hide show
  1. package/README.md +17 -0
  2. package/dist/styles.css +2 -2
  3. package/package.json +17 -17
  4. package/src/components/form/markup/Provider.tsx +7 -3
  5. package/src/components/form/markup/form-field/Body.tsx +1 -0
  6. package/src/components/form/markup/form-field/Container.tsx +3 -1
  7. package/src/components/form/markup/form-field/Footer.tsx +1 -0
  8. package/src/components/form/markup/form-field/Header.tsx +2 -0
  9. package/src/components/form/markup/form-field/Template.tsx +3 -2
  10. package/src/components/form/markup/form-field/index.tsx +1 -1
  11. package/src/components/form/markup/index.tsx +1 -0
  12. package/src/components/form/styles/form-field/variables.scss +2 -2
  13. package/src/components/form/types/props.ts +21 -19
  14. package/src/components/form/utils/form-field.ts +6 -4
  15. package/src/components/input/markup/date/Template.tsx +83 -33
  16. package/src/components/input/markup/date/Trigger.tsx +28 -12
  17. package/src/components/input/markup/date/range/Template.tsx +94 -35
  18. package/src/components/input/markup/time/Template.tsx +118 -5
  19. package/src/components/input/markup/time/Trigger.tsx +35 -2
  20. package/src/components/input/types/date.ts +176 -33
  21. package/src/components/input/types/time.ts +10 -4
  22. package/src/components/slot/index.tsx +3 -6
  23. package/src/components/slot/markup/Base.tsx +146 -9
  24. package/src/components/slot/markup/index.tsx +2 -4
  25. package/src/components/slot/types/props.ts +119 -23
  26. package/src/components/table/types/foundation.ts +9 -7
  27. package/src/components/time-picker/markup/Footer.tsx +5 -5
  28. package/src/components/time-picker/markup/HourSection.tsx +7 -7
  29. package/src/components/time-picker/markup/MinuteSection.tsx +5 -5
  30. package/src/components/time-picker/markup/Summary.tsx +2 -2
  31. package/src/components/time-picker/markup/Template.tsx +11 -11
  32. package/src/components/time-picker/types/time-picker.ts +6 -0
  33. package/src/components/time-picker/utils/time.ts +21 -9
  34. package/src/index.scss +0 -2
  35. package/src/index.tsx +8 -2
  36. package/src/types/index.ts +1 -4
  37. package/src/utils/index.ts +8 -4
  38. package/src/utils/selected-values.ts +20 -14
  39. package/src/components/scrollbar/hooks/index.ts +0 -4
  40. package/src/components/scrollbar/img/.gitkeep +0 -0
  41. package/src/components/scrollbar/index.scss +0 -1
  42. package/src/components/scrollbar/index.tsx +0 -4
  43. package/src/components/scrollbar/markup/index.tsx +0 -4
  44. package/src/components/scrollbar/styles/index.scss +0 -0
  45. package/src/components/scrollbar/types/index.ts +0 -4
  46. package/src/components/scrollbar/utils/index.ts +0 -4
  47. package/src/components/spinner/hooks/index.ts +0 -4
  48. package/src/components/spinner/img/.gitkeep +0 -0
  49. package/src/components/spinner/index.scss +0 -1
  50. package/src/components/spinner/index.tsx +0 -4
  51. package/src/components/spinner/markup/index.tsx +0 -4
  52. package/src/components/spinner/styles/index.scss +0 -0
  53. package/src/components/spinner/types/index.ts +0 -4
  54. package/src/components/spinner/utils/index.ts +0 -4
  55. package/src/hooks/index.ts +0 -4
@@ -1,6 +1,10 @@
1
1
  "use client";
2
2
 
3
- import type { ChangeEvent, MouseEvent as ReactMouseEvent } from "react";
3
+ import type {
4
+ ChangeEvent,
5
+ FocusEvent as ReactFocusEvent,
6
+ MouseEvent as ReactMouseEvent,
7
+ } from "react";
4
8
  import { forwardRef, useCallback, useMemo, useState } from "react";
5
9
  import { useUncontrolled } from "@mantine/hooks";
6
10
  import type { UseFormRegisterReturn } from "react-hook-form";
@@ -31,29 +35,39 @@ const INPUT_DATE_RANGE_TABLE_FORMAT = "YY-MM-DD";
31
35
  * form 저장은 start/end hidden input 2개로 분리한다.
32
36
  * @component
33
37
  * @param {InputCalendarRangeProps} props range date props
34
- * @param {CalendarRangeValue} [props.value] 제어형 range 값
35
- * @param {CalendarRangeValue} [props.defaultValue] 비제어 range 초기값
36
- * @param {CalendarRangeOnChange} [props.onChange] range 값 변경 이벤트
37
- * @param {CalendarRangeOnChange} [props.onValueChange] onChange alias
38
- * @param {boolean} [props.readOnly] 읽기 전용 여부
39
- * @param {boolean} [props.disabled] 비활성화 여부
40
- * @param {CalendarRangeDatePickerProps} [props.datePickerProps] Mantine range DatePicker 옵션
41
- * @param {string} [props.startName] 시작일 hidden input name
42
- * @param {string} [props.endName] 종료일 hidden input name
43
- * @param {UseFormRegisterReturn} [props.startRegister] 시작일 RHF register
44
- * @param {UseFormRegisterReturn} [props.endRegister] 종료일 RHF register
45
- * @param {string} [props.placeholder="YYYY-MM-DD ~ YYYY-MM-DD"] placeholder
46
- * @param {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] trigger input priority
47
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [props.state="default"] trigger input state
48
- * @param {ReactNode} [props.header] 패널 header 콘텐츠
49
- * @param {ReactNode} [props.footer] 패널 footer 콘텐츠
50
- * @param {InputCalendarTexts} [props.texts] 기본 Date 문구
51
- * @param {boolean} [props.calendarOpened] calendar 열림 제어 상태
52
- * @param {(open: boolean) => void} [props.onCalendarOpen] calendar 열림 변경 이벤트
53
- * @param {string} [props.id] trigger id
54
- * @param {ReactNode} [props.trigger] 커스텀 trigger 슬롯
55
- * @param {(props: InputCalendarTriggerRenderProps) => ReactNode} [props.renderTrigger] 커스텀 trigger 렌더 함수
56
- * @param {(event: MouseEvent<Element>) => void} [props.onClick] trigger 클릭 핸들러
38
+ * @property {[string | null, string | null]} [props.value] 제어형 range 값
39
+ * @property {[string | null, string | null]} [props.defaultValue] 비제어 range 초기값
40
+ * @property {(value: [string | null, string | null]) => void} [props.onChange] range 값 변경 이벤트
41
+ * @property {(value: [string | null, string | null]) => void} [props.onValueChange] onChange alias
42
+ * @property {boolean} [props.readOnly] 읽기 전용 여부
43
+ * @property {boolean} [props.disabled] 비활성화 여부
44
+ * @property {CalendarRangeDatePickerProps} [props.datePickerProps] Mantine range DatePicker 옵션
45
+ * @property {string} [props.startName] 시작일 hidden input name
46
+ * @property {string} [props.endName] 종료일 hidden input name
47
+ * @property {string} [props.form] start/end hidden input form id
48
+ * @property {UseFormRegisterReturn} [props.startRegister] 시작일 RHF register
49
+ * @property {UseFormRegisterReturn} [props.endRegister] 종료일 RHF register
50
+ * @property {boolean} [props.required] start/end hidden input required 여부
51
+ * @property {InputCalendarHiddenInputProps} [props.startHiddenInputProps] 시작일 hidden input native props
52
+ * @property {InputCalendarHiddenInputProps} [props.endHiddenInputProps] 종료일 hidden input native props
53
+ * @property {string} [props.placeholder="YYYY-MM-DD ~ YYYY-MM-DD"] placeholder
54
+ * @property {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] trigger input priority
55
+ * @property {"small" | "medium" | "large"} [props.size="medium"] trigger input size
56
+ * @property {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [props.state="default"] trigger input state
57
+ * @property {boolean} [props.block=false] trigger width 100% 여부
58
+ * @property {"full" | "fit" | "fill" | "auto" | number | string} [props.width] trigger width preset 또는 custom width
59
+ * @property {(event: FocusEvent<HTMLInputElement>) => void} [props.onFocus] trigger focus 핸들러
60
+ * @property {(event: FocusEvent<HTMLInputElement>) => void} [props.onBlur] trigger blur 핸들러
61
+ * @property {ReactNode} [props.header] 패널 header 콘텐츠
62
+ * @property {ReactNode} [props.footer] 패널 footer 콘텐츠
63
+ * @property {InputCalendarTexts} [props.texts] 기본 Date 문구
64
+ * @property {boolean} [props.calendarOpened] calendar 열림 제어 상태
65
+ * @property {(open: boolean) => void} [props.onCalendarOpen] calendar 열림 변경 이벤트
66
+ * @property {string} [props.id] trigger id
67
+ * @property {ReactNode} [props.trigger] 커스텀 trigger 슬롯
68
+ * @property {(props: InputCalendarTriggerRenderProps) => ReactNode} [props.renderTrigger] 커스텀 trigger 렌더 함수
69
+ * @property {(event: MouseEvent<Element>) => void} [props.onClick] trigger 클릭 핸들러
70
+ * @desc 기간 표시 trigger, Calendar.Range panel과 start/end hidden form storage를 조합한다.
57
71
  * @example
58
72
  * <Input.Date.Range.Template
59
73
  * startName="start_date"
@@ -74,11 +88,20 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
74
88
  datePickerProps,
75
89
  startName,
76
90
  endName,
91
+ form,
77
92
  startRegister,
78
93
  endRegister,
94
+ required,
95
+ startHiddenInputProps,
96
+ endHiddenInputProps,
79
97
  placeholder = "YYYY-MM-DD ~ YYYY-MM-DD",
80
98
  priority = "primary",
99
+ size = "medium",
81
100
  state = "default",
101
+ block = false,
102
+ width,
103
+ onFocus,
104
+ onBlur,
82
105
  header,
83
106
  footer,
84
107
  texts,
@@ -99,7 +122,7 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
99
122
  * Input Date Range Template; Calendar open 상태 변경 전달 함수.
100
123
  * 비제어 range calendar 상태를 갱신하고, 제어형 owner가 열림 상태를 조율할 수 있게 next open 값을 전달한다.
101
124
  * @param {boolean} nextOpen 다음 range calendar open 상태
102
- * @returns {void}
125
+ * @return {void}
103
126
  */
104
127
  const handleCalendarOpenChange = useCallback(
105
128
  (nextOpen: boolean) => {
@@ -125,7 +148,7 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
125
148
  * Input Date Range Template; RHF hidden input 변경 이벤트 동기화 함수.
126
149
  * range tuple의 start/end 값을 각각 직렬화해 startRegister/endRegister에 synthetic change로 전달한다.
127
150
  * @param {CalendarRangeValue} nextValue 다음 range calendar 선택 값
128
- * @returns {void}
151
+ * @return {void}
129
152
  */
130
153
  const emitRegisterChange = useCallback(
131
154
  (nextValue: CalendarRangeValue) => {
@@ -134,7 +157,7 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
134
157
  * start 또는 end register 한쪽에 직렬화 값을 전달한다.
135
158
  * @param {UseFormRegisterReturn | undefined} register RHF register 결과
136
159
  * @param {CalendarValue} nextCalendarValue 다음 start/end 선택 값
137
- * @returns {void}
160
+ * @return {void}
138
161
  */
139
162
  const emit = (
140
163
  register: UseFormRegisterReturn | undefined,
@@ -169,7 +192,7 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
169
192
  * Input Date Range Template; range value 갱신 함수.
170
193
  * Calendar.Range tuple 값을 갱신하고 start/end hidden input 저장값을 같은 tuple 기준으로 동기화한다.
171
194
  * @param {CalendarRangeValue} nextValue 다음 range calendar 선택 값
172
- * @returns {void}
195
+ * @return {void}
173
196
  */
174
197
  const updateValue = useCallback(
175
198
  (nextValue: CalendarRangeValue) => {
@@ -194,17 +217,37 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
194
217
  * Input Date Range Template; trigger click 위임 함수.
195
218
  * Calendar.Range Root의 PopOver toggle은 asChild trigger props가 담당하고, 이 함수는 소비자가 넘긴 trigger onClick만 보존한다.
196
219
  * @param {ReactMouseEvent<Element>} event trigger click event
197
- * @returns {void}
220
+ * @return {void}
198
221
  */
199
222
  const handleTriggerClick = (event: ReactMouseEvent<Element>) => {
200
223
  triggerOnClick?.(event);
201
224
  };
202
225
 
226
+ /**
227
+ * Input Date Range Template; trigger blur와 start/end RHF touched 상태 동기화 함수.
228
+ * 표시 trigger는 저장 endpoint를 소유하지 않으므로 직렬화한 start/end 값을 각 register에 전달한다.
229
+ * @utility
230
+ * @param {ReactFocusEvent<HTMLInputElement>} event trigger blur event
231
+ * @desc 소비자 blur callback과 두 hidden input register blur를 같은 range 값으로 동기화한다.
232
+ * @return {void}
233
+ */
234
+ const handleTriggerBlur = (event: ReactFocusEvent<HTMLInputElement>) => {
235
+ startRegister?.onBlur({
236
+ target: { name: startRegister.name, value: serializedStartValue },
237
+ type: "blur",
238
+ });
239
+ endRegister?.onBlur({
240
+ target: { name: endRegister.name, value: serializedEndValue },
241
+ type: "blur",
242
+ });
243
+ onBlur?.(event);
244
+ };
245
+
203
246
  /**
204
247
  * Input Date Range Template; Calendar Range Core change adapter 함수.
205
248
  * Calendar.Range.Root의 onChange 값을 Template tuple/RHF 동기화 pipeline으로 연결한다.
206
249
  * @param {CalendarRangeValue} nextValue 다음 range calendar 선택 값
207
- * @returns {void}
250
+ * @return {void}
208
251
  */
209
252
  const handleCalendarChange = (nextValue: CalendarRangeValue) => {
210
253
  updateValue(nextValue);
@@ -232,7 +275,7 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
232
275
  >
233
276
  {texts?.clear ?? "삭제하기"}
234
277
  </InputDateClearButton>
235
- {/* 변경: range footer는 삭제/적용을 같은 row에 두고 Apply 폭을 content 기준으로 줄인다. */}
278
+ {/* range footer는 삭제/적용을 같은 row에 두고 Apply 폭을 content 기준으로 줄인다. */}
236
279
  <InputDateApplyButton
237
280
  className="input-date-range-apply-button"
238
281
  disabled={disabled}
@@ -252,7 +295,12 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
252
295
  readOnly,
253
296
  onClick: handleTriggerClick,
254
297
  priority,
298
+ size,
255
299
  state,
300
+ block,
301
+ width,
302
+ onFocus,
303
+ onBlur: handleTriggerBlur,
256
304
  };
257
305
 
258
306
  const triggerNode = trigger ?? renderTrigger?.(triggerRenderProps) ?? (
@@ -265,6 +313,11 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
265
313
  readOnly={readOnly}
266
314
  onClick={handleTriggerClick}
267
315
  priority={priority}
316
+ size={size}
317
+ block={block}
318
+ width={width}
319
+ onFocus={onFocus}
320
+ onBlur={handleTriggerBlur}
268
321
  />
269
322
  );
270
323
 
@@ -287,20 +340,26 @@ const InputDateRangeTemplate = forwardRef<HTMLElement, InputCalendarRangeProps>(
287
340
  </Calendar.Range.Root>
288
341
  {(startRegister?.name || startName) && (
289
342
  <input
290
- // 변경: range 시작일 저장값은 trigger 표시와 분리해 hidden input이 담당한다.
343
+ // range 시작일 저장값은 trigger 표시와 분리해 hidden input이 담당한다.
344
+ {...startHiddenInputProps}
345
+ {...startRegister}
291
346
  type="hidden"
292
347
  value={serializedStartValue}
293
348
  name={startRegister?.name ?? startName}
294
- {...startRegister}
349
+ form={form ?? startHiddenInputProps?.form}
350
+ required={required ?? startHiddenInputProps?.required}
295
351
  />
296
352
  )}
297
353
  {(endRegister?.name || endName) && (
298
354
  <input
299
- // 변경: range 종료일 저장값은 trigger 표시와 분리해 hidden input이 담당한다.
355
+ // range 종료일 저장값은 trigger 표시와 분리해 hidden input이 담당한다.
356
+ {...endHiddenInputProps}
357
+ {...endRegister}
300
358
  type="hidden"
301
359
  value={serializedEndValue}
302
360
  name={endRegister?.name ?? endName}
303
- {...endRegister}
361
+ form={form ?? endHiddenInputProps?.form}
362
+ required={required ?? endHiddenInputProps?.required}
304
363
  />
305
364
  )}
306
365
  </>
@@ -46,6 +46,14 @@ const defaultClockIcon = (
46
46
  </svg>
47
47
  );
48
48
 
49
+ /**
50
+ * Input Time Utility; 24시간 저장 문자열을 trigger draft로 변환한다.
51
+ * @utility
52
+ * @param {string} value `HH:mm` 또는 `HH:mm:ss` 저장값
53
+ * @param {"12h" | "24h"} format 표시 형식
54
+ * @desc 유효하지 않거나 빈 저장값은 빈 field와 `am` period로 초기화한다.
55
+ * @return {InputTimeDraft} 표시용 field 문자열과 period
56
+ */
49
57
  const getInputTimeDraft = (
50
58
  value: string,
51
59
  format: "12h" | "24h",
@@ -73,11 +81,43 @@ const getInputTimeDraft = (
73
81
  * 저장값은 24시간 `HH:mm` 또는 `HH:mm:ss` 문자열로 유지한다.
74
82
  * @component
75
83
  * @param {InputTimeTemplateProps} props
76
- * @param {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] input priority
77
- * @param {"small" | "medium" | "large"} [props.size="medium"] input size
78
- * @param {"12h" | "24h"} [props.format="24h"] 표시 형식
79
- * @param {boolean} [props.withSeconds=false] field 노출 여부
80
- * @returns {ReactNode} 시간 입력과 선택 panel
84
+ * @property {string} [props.value] 제어형 24시간 저장값
85
+ * @property {string} [props.defaultValue] 비제어 초기 24시간 저장값
86
+ * @property {(value: string) => void} [props.onChange] 변경 핸들러
87
+ * @property {(value: string) => void} [props.onValueChange] 변경 별칭
88
+ * @property {string} [props.name] hidden input form name
89
+ * @property {string} [props.form] hidden input form id
90
+ * @property {UseFormRegisterReturn} [props.register] hidden input react-hook-form register 결과
91
+ * @property {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] input priority
92
+ * @property {"small" | "medium" | "large"} [props.size="medium"] input size
93
+ * @property {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [props.state="default"] input state
94
+ * @property {boolean} [props.block=false] width 100% 여부
95
+ * @property {"full" | "fit" | "fill" | "auto" | number | string} [props.width] width preset 또는 custom width
96
+ * @property {string} [props.className] root className
97
+ * @property {boolean} [props.disabled] disabled 여부
98
+ * @property {boolean} [props.readOnly=false] readOnly 여부
99
+ * @property {boolean} [props.required] hidden input required 여부
100
+ * @property {string} [props.id] trigger root id
101
+ * @property {"12h" | "24h"} [props.format="24h"] 표시 형식
102
+ * @property {boolean} [props.withSeconds=false] 초 field 노출 여부
103
+ * @property {boolean} [props.clearable=true] 삭제 action 노출 여부
104
+ * @property {number} [props.hoursStep=1] 시 증감 단위
105
+ * @property {number} [props.minutesStep=5] 분 증감 단위
106
+ * @property {number} [props.secondsStep=1] 초 증감 단위
107
+ * @property {ReactNode} [props.icon] clock icon override
108
+ * @property {"left" | "right"} [props.iconPosition] clock icon 위치
109
+ * @property {string} [props.iconLabel="시간 선택"] clock icon 접근성 라벨
110
+ * @property {InputTimeHiddenInputProps} [props.hiddenInputProps] hidden input native props
111
+ * @property {string} [props.hoursInputLabel="시"] 시 input 접근성 라벨
112
+ * @property {string} [props.minutesInputLabel="분"] 분 input 접근성 라벨
113
+ * @property {string} [props.secondsInputLabel="초"] 초 input 접근성 라벨
114
+ * @property {string} [props.amPmInputLabel="오전 또는 오후"] 오전·오후 input 접근성 라벨
115
+ * @property {string} [props.hoursPlaceholder="HH"] 시 placeholder
116
+ * @property {string} [props.minutesPlaceholder="MM"] 분 placeholder
117
+ * @property {string} [props.secondsPlaceholder="SS"] 초 placeholder
118
+ * @property {(event: FocusEvent<HTMLInputElement>) => void} [props.onFocus] field focus 핸들러
119
+ * @property {(event: FocusEvent<HTMLDivElement>) => void} [props.onBlur] trigger root blur 핸들러
120
+ * @desc 표시 draft를 편집·정규화하고 TimePicker 선택값과 hidden input 저장값을 동기화한다.
81
121
  */
82
122
  const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
83
123
  (
@@ -136,6 +176,7 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
136
176
  getInputTimeDraft(value ?? defaultValue ?? "", format),
137
177
  );
138
178
 
179
+ // 외부 value 또는 표시 형식 변경 시 저장값을 기준으로 편집 draft를 다시 계산한다.
139
180
  useEffect(() => {
140
181
  setDraft(getInputTimeDraft(timeValue, format));
141
182
  }, [format, timeValue]);
@@ -165,6 +206,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
165
206
  ? { width: widthValue }
166
207
  : undefined;
167
208
 
209
+ /**
210
+ * Input Time Utility; RHF hidden input 변경 이벤트 동기화 함수.
211
+ * @utility
212
+ * @param {string} nextValue 다음 24시간 저장값
213
+ * @desc 표시 field와 분리된 hidden input register에 synthetic change를 전달한다.
214
+ * @return {void}
215
+ */
168
216
  const emitRegisterChange = useCallback(
169
217
  (nextValue: string) => {
170
218
  register?.onChange({
@@ -175,6 +223,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
175
223
  [register],
176
224
  );
177
225
 
226
+ /**
227
+ * Input Time Utility; 시간 저장값 갱신 함수.
228
+ * @utility
229
+ * @param {string} nextValue 다음 24시간 저장값
230
+ * @desc draft, controlled/uncontrolled value와 RHF 저장값을 같은 값으로 동기화한다.
231
+ * @return {void}
232
+ */
178
233
  const updateValue = useCallback(
179
234
  (nextValue: string) => {
180
235
  setDraft(getInputTimeDraft(nextValue, format));
@@ -184,6 +239,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
184
239
  [emitRegisterChange, format, setTimeValue],
185
240
  );
186
241
 
242
+ /**
243
+ * Input Time Utility; 완성된 trigger draft를 저장값으로 commit한다.
244
+ * @utility
245
+ * @param {InputTimeDraft} nextDraft 다음 field 문자열과 period
246
+ * @desc 필요한 field가 두 자리이고 범위가 유효할 때만 24시간 문자열로 직렬화한다.
247
+ * @return {void}
248
+ */
187
249
  const commitDraft = useCallback(
188
250
  (nextDraft: InputTimeDraft) => {
189
251
  const { hours, minutes, seconds } = nextDraft.fields;
@@ -224,6 +286,14 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
224
286
  [format, updateValue, withSeconds],
225
287
  );
226
288
 
289
+ /**
290
+ * Input Time Utility; field 입력 정규화 함수.
291
+ * @utility
292
+ * @param {"hours" | "minutes" | "seconds"} unit 변경 단위
293
+ * @param {string} rawValue 입력 문자열
294
+ * @desc 숫자 두 자리만 draft에 남기고 완성된 값은 즉시 commit한다.
295
+ * @return {void}
296
+ */
227
297
  const handleFieldChange = (unit: TimePickerUnit, rawValue: string) => {
228
298
  const nextValue = rawValue.replace(/\D/g, "").slice(0, 2);
229
299
  const nextDraft = {
@@ -243,6 +313,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
243
313
  onFocus?.(event);
244
314
  };
245
315
 
316
+ /**
317
+ * Input Time Utility; field blur 정규화 함수.
318
+ * @utility
319
+ * @param {"hours" | "minutes" | "seconds"} unit blur 단위
320
+ * @desc 빈 field는 유지하고 입력값은 단위·표시 형식 범위로 clamp한 뒤 commit한다.
321
+ * @return {void}
322
+ */
246
323
  const handleFieldBlur = (unit: TimePickerUnit) => {
247
324
  const currentValue = draft.fields[unit];
248
325
  if (!currentValue) {
@@ -262,6 +339,14 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
262
339
  commitDraft(nextDraft);
263
340
  };
264
341
 
342
+ /**
343
+ * Input Time Utility; field 방향키 증감 함수.
344
+ * @utility
345
+ * @param {"hours" | "minutes" | "seconds"} unit 활성 단위
346
+ * @param {KeyboardEvent<HTMLInputElement>} event field keyboard event
347
+ * @desc 위·아래 방향키만 단위별 step으로 순환 증감한다.
348
+ * @return {void}
349
+ */
265
350
  const handleFieldKeyDown = (
266
351
  unit: TimePickerUnit,
267
352
  event: KeyboardEvent<HTMLInputElement>,
@@ -280,6 +365,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
280
365
  updateValue(stepTimeValue(timeValue, unit, amount * step, withSeconds));
281
366
  };
282
367
 
368
+ /**
369
+ * Input Time Utility; trigger stepper 증감 함수.
370
+ * @utility
371
+ * @param {1 | -1} amount 증감 방향
372
+ * @desc focus된 단위가 있을 때만 해당 단위 step으로 저장값을 순환 증감한다.
373
+ * @return {void}
374
+ */
283
375
  const handleStep = (amount: 1 | -1) => {
284
376
  if (!activeUnit) {
285
377
  return;
@@ -300,6 +392,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
300
392
  updateValue(nextValue);
301
393
  };
302
394
 
395
+ /**
396
+ * Input Time Utility; TimePicker 시 선택 반영 함수.
397
+ * @utility
398
+ * @param {number} hours 선택한 표시 시
399
+ * @desc 12시간제는 현재 draft period를 결합해 24시간 저장 시로 변환한다.
400
+ * @return {void}
401
+ */
303
402
  const handleHourSelect = (hours: number) => {
304
403
  const parts = parseTimeValue(timeValue) ?? {
305
404
  hours: 0,
@@ -324,6 +423,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
324
423
  updateValue(serializeTimeValue({ ...parts, minutes }, withSeconds));
325
424
  };
326
425
 
426
+ /**
427
+ * Input Time Utility; trigger root blur와 RHF touched 상태 동기화 함수.
428
+ * @utility
429
+ * @param {FocusEvent<HTMLDivElement>} event trigger root blur event
430
+ * @desc root 내부 focus 이동은 무시하고 외부로 벗어날 때만 register와 소비자 callback을 호출한다.
431
+ * @return {void}
432
+ */
327
433
  const handleRootBlur = (event: FocusEvent<HTMLDivElement>) => {
328
434
  if (
329
435
  event.relatedTarget instanceof Node &&
@@ -339,6 +445,13 @@ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
339
445
  onBlur?.(event);
340
446
  };
341
447
 
448
+ /**
449
+ * Input Time Utility; TimePicker panel open 상태 갱신 함수.
450
+ * @utility
451
+ * @param {boolean} nextOpen 다음 open 상태
452
+ * @desc disabled 또는 readOnly 상태에서는 panel을 열지 않는다.
453
+ * @return {void}
454
+ */
342
455
  const handleOpenChange = (nextOpen: boolean) => {
343
456
  setIsOpen(nextOpen && !isDisabled && !readOnly);
344
457
  };
@@ -12,7 +12,34 @@ import type { InputTimeTriggerProps } from "../../types";
12
12
  * Input Time trigger; 시·분·초 field와 focus 단위 stepper를 렌더한다.
13
13
  * @component
14
14
  * @param {InputTimeTriggerProps} props
15
- * @returns {ReactNode} 시간 입력 trigger
15
+ * @property {InputTimeTriggerFields} props.fields 시·분·초 입력값
16
+ * @property {"am" | "pm"} props.period 오전·오후 구분
17
+ * @property {"hours" | "minutes" | "seconds" | null} props.activeUnit 현재 활성 단위
18
+ * @property {"primary" | "secondary" | "tertiary" | "table"} props.priority input priority
19
+ * @property {"small" | "medium" | "large"} props.size input size
20
+ * @property {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} props.state input state
21
+ * @property {"12h" | "24h"} props.format 표시 형식
22
+ * @property {boolean} props.withSeconds 초 field 노출 여부
23
+ * @property {boolean} props.disabled 비활성화 여부
24
+ * @property {boolean} props.readOnly 읽기 전용 여부
25
+ * @property {ReactNode} props.icon clock icon
26
+ * @property {"left" | "right"} props.iconPosition clock icon 위치
27
+ * @property {string} props.iconLabel clock icon 접근성 라벨
28
+ * @property {string} props.hoursInputLabel 시 input 접근성 라벨
29
+ * @property {string} props.minutesInputLabel 분 input 접근성 라벨
30
+ * @property {string} props.secondsInputLabel 초 input 접근성 라벨
31
+ * @property {string} props.amPmInputLabel 오전·오후 input 접근성 라벨
32
+ * @property {string} props.hoursPlaceholder 시 placeholder
33
+ * @property {string} props.minutesPlaceholder 분 placeholder
34
+ * @property {string} props.secondsPlaceholder 초 placeholder
35
+ * @property {(unit: "hours" | "minutes" | "seconds", value: string) => void} props.onFieldChange field 변경 핸들러
36
+ * @property {(unit: "hours" | "minutes" | "seconds", event: FocusEvent<HTMLInputElement>) => void} props.onFieldFocus field focus 핸들러
37
+ * @property {(unit: "hours" | "minutes" | "seconds") => void} props.onFieldBlur field blur 핸들러
38
+ * @property {(unit: "hours" | "minutes" | "seconds", event: KeyboardEvent<HTMLInputElement>) => void} props.onFieldKeyDown field keyboard 핸들러
39
+ * @property {(period: "am" | "pm") => void} props.onPeriodChange 오전·오후 변경 핸들러
40
+ * @property {(amount: 1 | -1) => void} props.onStep 활성 단위 증감 핸들러
41
+ * @property {(event: FocusEvent<HTMLDivElement>) => void} props.onRootBlur root blur 핸들러
42
+ * @desc field별 입력과 focus를 Template의 draft·commit pipeline으로 전달한다.
16
43
  */
17
44
  const InputTimeTrigger = forwardRef<HTMLDivElement, InputTimeTriggerProps>(
18
45
  (
@@ -73,7 +100,13 @@ const InputTimeTrigger = forwardRef<HTMLDivElement, InputTimeTriggerProps>(
73
100
  const onClockClick = () => {
74
101
  hoursRef.current?.focus();
75
102
  };
76
- // PopOver.Trigger가 주입한 click은 clock button에서만 popup toggle로 위임한다.
103
+ /**
104
+ * Input Time Utility; PopOver trigger click 위임 함수.
105
+ * @utility
106
+ * @param {MouseEvent<HTMLDivElement>} event trigger root click event
107
+ * @desc field 편집 click은 panel을 toggle하지 않고 clock button click만 PopOver에 전달한다.
108
+ * @return {void}
109
+ */
77
110
  const onRootClick = (event: MouseEvent<HTMLDivElement>) => {
78
111
  if (
79
112
  event.target instanceof Element &&