@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.
- package/README.md +17 -0
- package/dist/styles.css +2 -2
- package/package.json +17 -17
- package/src/components/form/markup/Provider.tsx +7 -3
- package/src/components/form/markup/form-field/Body.tsx +1 -0
- package/src/components/form/markup/form-field/Container.tsx +3 -1
- package/src/components/form/markup/form-field/Footer.tsx +1 -0
- package/src/components/form/markup/form-field/Header.tsx +2 -0
- package/src/components/form/markup/form-field/Template.tsx +3 -2
- package/src/components/form/markup/form-field/index.tsx +1 -1
- package/src/components/form/markup/index.tsx +1 -0
- package/src/components/form/styles/form-field/variables.scss +2 -2
- package/src/components/form/types/props.ts +21 -19
- package/src/components/form/utils/form-field.ts +6 -4
- package/src/components/input/markup/date/Template.tsx +83 -33
- package/src/components/input/markup/date/Trigger.tsx +28 -12
- package/src/components/input/markup/date/range/Template.tsx +94 -35
- package/src/components/input/markup/time/Template.tsx +118 -5
- package/src/components/input/markup/time/Trigger.tsx +35 -2
- package/src/components/input/types/date.ts +176 -33
- package/src/components/input/types/time.ts +10 -4
- package/src/components/slot/index.tsx +3 -6
- package/src/components/slot/markup/Base.tsx +146 -9
- package/src/components/slot/markup/index.tsx +2 -4
- package/src/components/slot/types/props.ts +119 -23
- package/src/components/table/types/foundation.ts +9 -7
- package/src/components/time-picker/markup/Footer.tsx +5 -5
- package/src/components/time-picker/markup/HourSection.tsx +7 -7
- package/src/components/time-picker/markup/MinuteSection.tsx +5 -5
- package/src/components/time-picker/markup/Summary.tsx +2 -2
- package/src/components/time-picker/markup/Template.tsx +11 -11
- package/src/components/time-picker/types/time-picker.ts +6 -0
- package/src/components/time-picker/utils/time.ts +21 -9
- package/src/index.scss +0 -2
- package/src/index.tsx +8 -2
- package/src/types/index.ts +1 -4
- package/src/utils/index.ts +8 -4
- package/src/utils/selected-values.ts +20 -14
- package/src/components/scrollbar/hooks/index.ts +0 -4
- package/src/components/scrollbar/img/.gitkeep +0 -0
- package/src/components/scrollbar/index.scss +0 -1
- package/src/components/scrollbar/index.tsx +0 -4
- package/src/components/scrollbar/markup/index.tsx +0 -4
- package/src/components/scrollbar/styles/index.scss +0 -0
- package/src/components/scrollbar/types/index.ts +0 -4
- package/src/components/scrollbar/utils/index.ts +0 -4
- package/src/components/spinner/hooks/index.ts +0 -4
- package/src/components/spinner/img/.gitkeep +0 -0
- package/src/components/spinner/index.scss +0 -1
- package/src/components/spinner/index.tsx +0 -4
- package/src/components/spinner/markup/index.tsx +0 -4
- package/src/components/spinner/styles/index.scss +0 -0
- package/src/components/spinner/types/index.ts +0 -4
- package/src/components/spinner/utils/index.ts +0 -4
- package/src/hooks/index.ts +0 -4
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
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
|
-
* @
|
|
35
|
-
* @
|
|
36
|
-
* @
|
|
37
|
-
* @
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
40
|
-
* @
|
|
41
|
-
* @
|
|
42
|
-
* @
|
|
43
|
-
* @
|
|
44
|
-
* @
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
47
|
-
* @
|
|
48
|
-
* @
|
|
49
|
-
* @
|
|
50
|
-
* @
|
|
51
|
-
* @
|
|
52
|
-
* @
|
|
53
|
-
* @
|
|
54
|
-
* @
|
|
55
|
-
* @
|
|
56
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
{/*
|
|
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
|
-
//
|
|
343
|
+
// range 시작일 저장값은 trigger 표시와 분리해 hidden input이 담당한다.
|
|
344
|
+
{...startHiddenInputProps}
|
|
345
|
+
{...startRegister}
|
|
291
346
|
type="hidden"
|
|
292
347
|
value={serializedStartValue}
|
|
293
348
|
name={startRegister?.name ?? startName}
|
|
294
|
-
{
|
|
349
|
+
form={form ?? startHiddenInputProps?.form}
|
|
350
|
+
required={required ?? startHiddenInputProps?.required}
|
|
295
351
|
/>
|
|
296
352
|
)}
|
|
297
353
|
{(endRegister?.name || endName) && (
|
|
298
354
|
<input
|
|
299
|
-
//
|
|
355
|
+
// range 종료일 저장값은 trigger 표시와 분리해 hidden input이 담당한다.
|
|
356
|
+
{...endHiddenInputProps}
|
|
357
|
+
{...endRegister}
|
|
300
358
|
type="hidden"
|
|
301
359
|
value={serializedEndValue}
|
|
302
360
|
name={endRegister?.name ?? endName}
|
|
303
|
-
{
|
|
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
|
-
* @
|
|
77
|
-
* @
|
|
78
|
-
* @
|
|
79
|
-
* @
|
|
80
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
|
|
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 &&
|