@uniai-fe/uds-primitives 0.11.0 → 0.12.1

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 (30) hide show
  1. package/README.md +38 -453
  2. package/dist/styles.css +370 -327
  3. package/package.json +2 -2
  4. package/src/components/input/index.tsx +1 -1
  5. package/src/components/input/markup/time/Template.tsx +412 -0
  6. package/src/components/input/markup/time/Trigger.tsx +233 -0
  7. package/src/components/input/markup/time/index.tsx +9 -2
  8. package/src/components/input/styles/time.scss +160 -317
  9. package/src/components/input/styles/variables.scss +9 -47
  10. package/src/components/input/types/time.ts +263 -120
  11. package/src/components/time-picker/img/minus.svg +3 -0
  12. package/src/components/time-picker/img/plus.svg +3 -0
  13. package/src/components/time-picker/index.scss +1 -0
  14. package/src/components/time-picker/index.tsx +11 -0
  15. package/src/components/time-picker/markup/Footer.tsx +53 -0
  16. package/src/components/time-picker/markup/HourSection.tsx +72 -0
  17. package/src/components/time-picker/markup/MinuteSection.tsx +68 -0
  18. package/src/components/time-picker/markup/Summary.tsx +19 -0
  19. package/src/components/time-picker/markup/Template.tsx +86 -0
  20. package/src/components/time-picker/markup/index.tsx +10 -0
  21. package/src/components/time-picker/styles/index.scss +2 -0
  22. package/src/components/time-picker/styles/template.scss +174 -0
  23. package/src/components/time-picker/styles/variables.scss +64 -0
  24. package/src/components/time-picker/types/index.ts +1 -0
  25. package/src/components/time-picker/types/time-picker.ts +193 -0
  26. package/src/components/time-picker/utils/index.ts +1 -0
  27. package/src/components/time-picker/utils/time.ts +110 -0
  28. package/src/index.scss +1 -0
  29. package/src/index.tsx +1 -0
  30. package/src/components/input/markup/time/Picker.tsx +0 -378
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -87,8 +87,8 @@
87
87
  "react-hook-form": "^7.82.0",
88
88
  "sass": "^1.101.3",
89
89
  "typescript": "6.0.3",
90
- "@uniai-fe/next-devkit": "0.4.0",
91
90
  "@uniai-fe/eslint-config": "0.4.2",
91
+ "@uniai-fe/next-devkit": "0.4.0",
92
92
  "@uniai-fe/tsconfig": "0.2.0",
93
93
  "@uniai-fe/util-functions": "0.4.3",
94
94
  "@uniai-fe/uds-foundation": "0.5.0"
@@ -3,7 +3,7 @@
3
3
  * @desc
4
4
  * - `Input.Base`, `Input.TextArea`: foundation control
5
5
  * - `Input.Text.*`: text scenario wrappers
6
- * - `Input.Date`, `Input.Address`, `Input.File`: category presets
6
+ * - `Input.Date`, `Input.Time`, `Input.Address`, `Input.File`: category presets
7
7
  * - hooks/utils/types는 별도 배럴로 함께 export한다
8
8
  */
9
9
  import "./index.scss";
@@ -0,0 +1,412 @@
1
+ "use client";
2
+
3
+ import { useUncontrolled } from "@mantine/hooks";
4
+ import type { CSSProperties, FocusEvent, KeyboardEvent } from "react";
5
+ import { forwardRef, useCallback, useEffect, useState } from "react";
6
+ import { PopOver } from "../../../pop-over";
7
+ import {
8
+ getFormFieldWidthAttr,
9
+ getFormFieldWidthValue,
10
+ } from "../../../form/utils/form-field";
11
+ import { TimePicker } from "../../../time-picker";
12
+ import type {
13
+ TimePickerParts,
14
+ TimePickerPeriod,
15
+ TimePickerUnit,
16
+ } from "../../../time-picker/types";
17
+ import {
18
+ getTimePeriod,
19
+ parseTimeValue,
20
+ serializeTimeValue,
21
+ setTimePeriod,
22
+ stepTimeValue,
23
+ toDisplayHour,
24
+ } from "../../../time-picker/utils";
25
+ import ClockIcon from "../../img/clock.svg";
26
+ import type { InputTimeDraft, InputTimeTemplateProps } from "../../types";
27
+ import InputTimeTrigger from "./Trigger";
28
+
29
+ const getInputTimeDraft = (
30
+ value: string,
31
+ format: "12h" | "24h",
32
+ ): InputTimeDraft => {
33
+ const parts = parseTimeValue(value);
34
+ if (!parts) {
35
+ return {
36
+ fields: { hours: "", minutes: "", seconds: "" },
37
+ period: "am",
38
+ };
39
+ }
40
+
41
+ return {
42
+ fields: {
43
+ hours: String(toDisplayHour(parts.hours, format)).padStart(2, "0"),
44
+ minutes: String(parts.minutes).padStart(2, "0"),
45
+ seconds: String(parts.seconds).padStart(2, "0"),
46
+ },
47
+ period: getTimePeriod(parts.hours),
48
+ };
49
+ };
50
+
51
+ /**
52
+ * Input Time Template; native trigger와 PopOver·TimePicker 선택 panel 조합.
53
+ * 저장값은 24시간 `HH:mm` 또는 `HH:mm:ss` 문자열로 유지한다.
54
+ * @component
55
+ * @param {InputTimeTemplateProps} props
56
+ * @param {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] input priority
57
+ * @param {"small" | "medium" | "large"} [props.size="medium"] input size
58
+ * @param {"12h" | "24h"} [props.format="24h"] 표시 형식
59
+ * @param {boolean} [props.withSeconds=false] 초 field 노출 여부
60
+ * @returns {ReactNode} 시간 입력과 선택 panel
61
+ */
62
+ const InputTimeTemplate = forwardRef<HTMLDivElement, InputTimeTemplateProps>(
63
+ (
64
+ {
65
+ value,
66
+ defaultValue,
67
+ onChange,
68
+ onValueChange,
69
+ name,
70
+ form,
71
+ register,
72
+ priority = "primary",
73
+ size = "medium",
74
+ state: stateProp = "default",
75
+ block = false,
76
+ width,
77
+ className,
78
+ disabled,
79
+ readOnly = false,
80
+ required,
81
+ id,
82
+ format = "24h",
83
+ withSeconds = false,
84
+ clearable = true,
85
+ hoursStep = 1,
86
+ minutesStep = 5,
87
+ secondsStep = 1,
88
+ icon,
89
+ iconPosition,
90
+ iconLabel = "시간 선택",
91
+ hiddenInputProps,
92
+ hoursInputLabel = "시",
93
+ minutesInputLabel = "분",
94
+ secondsInputLabel = "초",
95
+ amPmInputLabel = "오전 또는 오후",
96
+ hoursPlaceholder = "HH",
97
+ minutesPlaceholder = "MM",
98
+ secondsPlaceholder = "SS",
99
+ onFocus,
100
+ onBlur,
101
+ },
102
+ ref,
103
+ ) => {
104
+ const [isOpen, setIsOpen] = useState(false);
105
+ const [activeUnit, setActiveUnit] = useState<TimePickerUnit | null>(null);
106
+ const [timeValue, setTimeValue] = useUncontrolled<string>({
107
+ value,
108
+ defaultValue,
109
+ finalValue: "",
110
+ onChange: nextValue => {
111
+ onChange?.(nextValue);
112
+ onValueChange?.(nextValue);
113
+ },
114
+ });
115
+ const [draft, setDraft] = useState<InputTimeDraft>(() =>
116
+ getInputTimeDraft(value ?? defaultValue ?? "", format),
117
+ );
118
+
119
+ useEffect(() => {
120
+ setDraft(getInputTimeDraft(timeValue, format));
121
+ }, [format, timeValue]);
122
+
123
+ const isDisabled =
124
+ disabled || stateProp === "disabled" || stateProp === "loading";
125
+ const visualState =
126
+ priority === "tertiary"
127
+ ? isDisabled
128
+ ? "disabled"
129
+ : "default"
130
+ : !isDisabled && activeUnit
131
+ ? "active"
132
+ : stateProp;
133
+ const resolvedIconPosition =
134
+ iconPosition ?? (priority === "table" ? "left" : "right");
135
+ const resolvedBlock =
136
+ block || (priority === "table" && width === undefined);
137
+ const widthAttr =
138
+ width !== undefined
139
+ ? getFormFieldWidthAttr(width)
140
+ : resolvedBlock
141
+ ? "full"
142
+ : undefined;
143
+ const widthValue =
144
+ width !== undefined ? getFormFieldWidthValue(width) : undefined;
145
+ const triggerStyle: CSSProperties | undefined = widthValue
146
+ ? { width: widthValue }
147
+ : undefined;
148
+
149
+ const emitRegisterChange = useCallback(
150
+ (nextValue: string) => {
151
+ register?.onChange({
152
+ target: { name: register.name, value: nextValue },
153
+ type: "change",
154
+ });
155
+ },
156
+ [register],
157
+ );
158
+
159
+ const updateValue = useCallback(
160
+ (nextValue: string) => {
161
+ setDraft(getInputTimeDraft(nextValue, format));
162
+ setTimeValue(nextValue);
163
+ emitRegisterChange(nextValue);
164
+ },
165
+ [emitRegisterChange, format, setTimeValue],
166
+ );
167
+
168
+ const commitDraft = useCallback(
169
+ (nextDraft: InputTimeDraft) => {
170
+ const { hours, minutes, seconds } = nextDraft.fields;
171
+ if (
172
+ hours.length !== 2 ||
173
+ minutes.length !== 2 ||
174
+ (withSeconds && seconds.length !== 2)
175
+ ) {
176
+ return;
177
+ }
178
+
179
+ const displayHours = Number(hours);
180
+ const nextMinutes = Number(minutes);
181
+ const nextSeconds = withSeconds ? Number(seconds) : 0;
182
+ const validHours =
183
+ format === "12h"
184
+ ? displayHours >= 1 && displayHours <= 12
185
+ : displayHours >= 0 && displayHours <= 23;
186
+ if (!validHours || nextMinutes > 59 || nextSeconds > 59) {
187
+ return;
188
+ }
189
+
190
+ const storedHours =
191
+ format === "12h"
192
+ ? (displayHours % 12) + (nextDraft.period === "pm" ? 12 : 0)
193
+ : displayHours;
194
+ updateValue(
195
+ serializeTimeValue(
196
+ {
197
+ hours: storedHours,
198
+ minutes: nextMinutes,
199
+ seconds: nextSeconds,
200
+ },
201
+ withSeconds,
202
+ ),
203
+ );
204
+ },
205
+ [format, updateValue, withSeconds],
206
+ );
207
+
208
+ const handleFieldChange = (unit: TimePickerUnit, rawValue: string) => {
209
+ const nextValue = rawValue.replace(/\D/g, "").slice(0, 2);
210
+ const nextDraft = {
211
+ ...draft,
212
+ fields: { ...draft.fields, [unit]: nextValue },
213
+ };
214
+ setDraft(nextDraft);
215
+ commitDraft(nextDraft);
216
+ };
217
+
218
+ const handleFieldFocus = (
219
+ unit: TimePickerUnit,
220
+ event: FocusEvent<HTMLInputElement>,
221
+ ) => {
222
+ setActiveUnit(unit);
223
+ event.currentTarget.select();
224
+ onFocus?.(event);
225
+ };
226
+
227
+ const handleFieldBlur = (unit: TimePickerUnit) => {
228
+ const currentValue = draft.fields[unit];
229
+ if (!currentValue) {
230
+ return;
231
+ }
232
+
233
+ const max = unit === "hours" ? (format === "12h" ? 12 : 23) : 59;
234
+ const min = unit === "hours" && format === "12h" ? 1 : 0;
235
+ const normalized = String(
236
+ Math.min(max, Math.max(min, Number(currentValue))),
237
+ ).padStart(2, "0");
238
+ const nextDraft = {
239
+ ...draft,
240
+ fields: { ...draft.fields, [unit]: normalized },
241
+ };
242
+ setDraft(nextDraft);
243
+ commitDraft(nextDraft);
244
+ };
245
+
246
+ const handleFieldKeyDown = (
247
+ unit: TimePickerUnit,
248
+ event: KeyboardEvent<HTMLInputElement>,
249
+ ) => {
250
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") {
251
+ return;
252
+ }
253
+ event.preventDefault();
254
+ const amount = event.key === "ArrowUp" ? 1 : -1;
255
+ const step =
256
+ unit === "hours"
257
+ ? hoursStep
258
+ : unit === "minutes"
259
+ ? minutesStep
260
+ : secondsStep;
261
+ updateValue(stepTimeValue(timeValue, unit, amount * step, withSeconds));
262
+ };
263
+
264
+ const handleStep = (amount: 1 | -1) => {
265
+ if (!activeUnit) {
266
+ return;
267
+ }
268
+ const step =
269
+ activeUnit === "hours"
270
+ ? hoursStep
271
+ : activeUnit === "minutes"
272
+ ? minutesStep
273
+ : secondsStep;
274
+ updateValue(
275
+ stepTimeValue(timeValue, activeUnit, amount * step, withSeconds),
276
+ );
277
+ };
278
+
279
+ const handlePeriodChange = (period: TimePickerPeriod) => {
280
+ const nextValue = setTimePeriod(timeValue, period, withSeconds);
281
+ updateValue(nextValue);
282
+ };
283
+
284
+ const handleHourSelect = (hours: number) => {
285
+ const parts = parseTimeValue(timeValue) ?? {
286
+ hours: 0,
287
+ minutes: 0,
288
+ seconds: 0,
289
+ };
290
+ const storedHours =
291
+ format === "12h"
292
+ ? (hours % 12) + (draft.period === "pm" ? 12 : 0)
293
+ : hours;
294
+ updateValue(
295
+ serializeTimeValue({ ...parts, hours: storedHours }, withSeconds),
296
+ );
297
+ };
298
+
299
+ const handleMinuteSelect = (minutes: number) => {
300
+ const parts: TimePickerParts = parseTimeValue(timeValue) ?? {
301
+ hours: 0,
302
+ minutes: 0,
303
+ seconds: 0,
304
+ };
305
+ updateValue(serializeTimeValue({ ...parts, minutes }, withSeconds));
306
+ };
307
+
308
+ const handleRootBlur = (event: FocusEvent<HTMLDivElement>) => {
309
+ if (
310
+ event.relatedTarget instanceof Node &&
311
+ event.currentTarget.contains(event.relatedTarget)
312
+ ) {
313
+ return;
314
+ }
315
+ setActiveUnit(null);
316
+ register?.onBlur({
317
+ target: { name: register.name, value: timeValue },
318
+ type: "blur",
319
+ });
320
+ onBlur?.(event);
321
+ };
322
+
323
+ const handleOpenChange = (nextOpen: boolean) => {
324
+ setIsOpen(nextOpen && !isDisabled && !readOnly);
325
+ };
326
+
327
+ return (
328
+ <>
329
+ <PopOver.Root open={isOpen} onOpenChange={handleOpenChange}>
330
+ <PopOver.Trigger asChild>
331
+ <InputTimeTrigger
332
+ ref={ref}
333
+ id={id}
334
+ className={className}
335
+ style={triggerStyle}
336
+ fields={draft.fields}
337
+ period={draft.period}
338
+ activeUnit={activeUnit}
339
+ priority={priority}
340
+ size={size}
341
+ state={visualState}
342
+ format={format}
343
+ withSeconds={withSeconds}
344
+ disabled={Boolean(isDisabled)}
345
+ readOnly={readOnly}
346
+ icon={
347
+ icon === undefined ? <ClockIcon aria-hidden="true" /> : icon
348
+ }
349
+ iconPosition={resolvedIconPosition}
350
+ iconLabel={iconLabel}
351
+ hoursInputLabel={hoursInputLabel}
352
+ minutesInputLabel={minutesInputLabel}
353
+ secondsInputLabel={secondsInputLabel}
354
+ amPmInputLabel={amPmInputLabel}
355
+ hoursPlaceholder={hoursPlaceholder}
356
+ minutesPlaceholder={minutesPlaceholder}
357
+ secondsPlaceholder={secondsPlaceholder}
358
+ data-block={resolvedBlock ? "true" : undefined}
359
+ data-width={widthAttr}
360
+ onFieldChange={handleFieldChange}
361
+ onFieldFocus={handleFieldFocus}
362
+ onFieldBlur={handleFieldBlur}
363
+ onFieldKeyDown={handleFieldKeyDown}
364
+ onPeriodChange={handlePeriodChange}
365
+ onStep={handleStep}
366
+ onRootBlur={handleRootBlur}
367
+ />
368
+ </PopOver.Trigger>
369
+ <PopOver.Content
370
+ className="input-time-pop-over"
371
+ width={336}
372
+ align="start"
373
+ sideOffset={8}
374
+ onOpenAutoFocus={event => event.preventDefault()}
375
+ >
376
+ <TimePicker.Template
377
+ value={timeValue}
378
+ format={format}
379
+ clearable={clearable}
380
+ disabled={Boolean(isDisabled || readOnly)}
381
+ onHourSelect={handleHourSelect}
382
+ onMinuteSelect={handleMinuteSelect}
383
+ onPeriodChange={handlePeriodChange}
384
+ onMinuteStep={amount =>
385
+ updateValue(
386
+ stepTimeValue(timeValue, "minutes", amount, withSeconds),
387
+ )
388
+ }
389
+ onClear={() => updateValue("")}
390
+ onApply={() => setIsOpen(false)}
391
+ />
392
+ </PopOver.Content>
393
+ </PopOver.Root>
394
+ {register || name ? (
395
+ <input
396
+ {...hiddenInputProps}
397
+ {...register}
398
+ type="hidden"
399
+ name={register?.name ?? name}
400
+ form={form ?? hiddenInputProps?.form}
401
+ required={required}
402
+ value={timeValue}
403
+ />
404
+ ) : null}
405
+ </>
406
+ );
407
+ },
408
+ );
409
+
410
+ InputTimeTemplate.displayName = "InputTimeTemplate";
411
+
412
+ export default InputTimeTemplate;
@@ -0,0 +1,233 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import type { ChangeEvent, FocusEvent, KeyboardEvent, MouseEvent } from "react";
5
+ import { forwardRef, useRef } from "react";
6
+ import { Calendar } from "../../../calendar";
7
+ import type { TimePickerUnit } from "../../../time-picker/types";
8
+ import type { InputTimeTriggerProps } from "../../types";
9
+
10
+ /**
11
+ * Input Time trigger; 시·분·초 field와 focus 단위 stepper를 렌더한다.
12
+ * @component
13
+ * @param {InputTimeTriggerProps} props
14
+ * @returns {ReactNode} 시간 입력 trigger
15
+ */
16
+ const InputTimeTrigger = forwardRef<HTMLDivElement, InputTimeTriggerProps>(
17
+ (
18
+ {
19
+ fields,
20
+ period,
21
+ activeUnit,
22
+ priority,
23
+ size,
24
+ state,
25
+ format,
26
+ withSeconds,
27
+ disabled,
28
+ readOnly,
29
+ icon,
30
+ iconPosition,
31
+ iconLabel,
32
+ hoursInputLabel,
33
+ minutesInputLabel,
34
+ secondsInputLabel,
35
+ amPmInputLabel,
36
+ hoursPlaceholder,
37
+ minutesPlaceholder,
38
+ secondsPlaceholder,
39
+ onFieldChange,
40
+ onFieldFocus,
41
+ onFieldBlur,
42
+ onFieldKeyDown,
43
+ onPeriodChange,
44
+ onStep,
45
+ onRootBlur,
46
+ className,
47
+ ...restProps
48
+ },
49
+ ref,
50
+ ) => {
51
+ const hoursRef = useRef<HTMLInputElement | null>(null);
52
+ const onChange =
53
+ (unit: TimePickerUnit) => (event: ChangeEvent<HTMLInputElement>) => {
54
+ onFieldChange(unit, event.currentTarget.value);
55
+ };
56
+ const onFocus =
57
+ (unit: TimePickerUnit) => (event: FocusEvent<HTMLInputElement>) => {
58
+ onFieldFocus(unit, event);
59
+ };
60
+ const onBlur = (unit: TimePickerUnit) => () => onFieldBlur(unit);
61
+ const onKeyDown =
62
+ (unit: TimePickerUnit) => (event: KeyboardEvent<HTMLInputElement>) => {
63
+ onFieldKeyDown(unit, event);
64
+ };
65
+ const onStepperMouseDown = (event: MouseEvent<HTMLButtonElement>) => {
66
+ event.preventDefault();
67
+ };
68
+ const onInteractiveClick = (event: MouseEvent<HTMLElement>) => {
69
+ event.stopPropagation();
70
+ };
71
+ const onClockClick = () => {
72
+ hoursRef.current?.focus();
73
+ };
74
+
75
+ return (
76
+ <div
77
+ {...restProps}
78
+ ref={ref}
79
+ className={clsx("input-time input-time-picker", className)}
80
+ data-priority={priority}
81
+ data-size={size}
82
+ data-state={state}
83
+ data-readonly={readOnly ? "true" : undefined}
84
+ data-active-unit={activeUnit ?? undefined}
85
+ data-icon-position={iconPosition}
86
+ onBlur={onRootBlur}
87
+ >
88
+ <div className="input-time-trigger-input">
89
+ {iconPosition === "left" ? (
90
+ <button
91
+ type="button"
92
+ className="input-time-icon-button"
93
+ tabIndex={-1}
94
+ aria-label={iconLabel}
95
+ disabled={disabled}
96
+ onMouseDown={onStepperMouseDown}
97
+ onClick={onClockClick}
98
+ >
99
+ {icon}
100
+ </button>
101
+ ) : null}
102
+ <div className="input-time-fields-group">
103
+ <input
104
+ ref={hoursRef}
105
+ className="input-time-field"
106
+ type="text"
107
+ inputMode="numeric"
108
+ autoComplete="off"
109
+ maxLength={2}
110
+ value={fields.hours}
111
+ placeholder={hoursPlaceholder}
112
+ aria-label={hoursInputLabel}
113
+ aria-invalid={state === "error" ? true : undefined}
114
+ disabled={disabled}
115
+ readOnly={readOnly}
116
+ onChange={onChange("hours")}
117
+ onClick={onInteractiveClick}
118
+ onFocus={onFocus("hours")}
119
+ onBlur={onBlur("hours")}
120
+ onKeyDown={onKeyDown("hours")}
121
+ />
122
+ <span aria-hidden="true">:</span>
123
+ <input
124
+ className="input-time-field"
125
+ type="text"
126
+ inputMode="numeric"
127
+ autoComplete="off"
128
+ maxLength={2}
129
+ value={fields.minutes}
130
+ placeholder={minutesPlaceholder}
131
+ aria-label={minutesInputLabel}
132
+ aria-invalid={state === "error" ? true : undefined}
133
+ disabled={disabled}
134
+ readOnly={readOnly}
135
+ onChange={onChange("minutes")}
136
+ onClick={onInteractiveClick}
137
+ onFocus={onFocus("minutes")}
138
+ onBlur={onBlur("minutes")}
139
+ onKeyDown={onKeyDown("minutes")}
140
+ />
141
+ {withSeconds ? (
142
+ <>
143
+ <span aria-hidden="true">:</span>
144
+ <input
145
+ className="input-time-field"
146
+ type="text"
147
+ inputMode="numeric"
148
+ autoComplete="off"
149
+ maxLength={2}
150
+ value={fields.seconds}
151
+ placeholder={secondsPlaceholder}
152
+ aria-label={secondsInputLabel}
153
+ aria-invalid={state === "error" ? true : undefined}
154
+ disabled={disabled}
155
+ readOnly={readOnly}
156
+ onChange={onChange("seconds")}
157
+ onClick={onInteractiveClick}
158
+ onFocus={onFocus("seconds")}
159
+ onBlur={onBlur("seconds")}
160
+ onKeyDown={onKeyDown("seconds")}
161
+ />
162
+ </>
163
+ ) : null}
164
+ {format === "12h" ? (
165
+ <select
166
+ className="input-time-period-field"
167
+ value={period}
168
+ aria-label={amPmInputLabel}
169
+ disabled={disabled}
170
+ onClick={onInteractiveClick}
171
+ onChange={event =>
172
+ onPeriodChange(
173
+ event.currentTarget.value === "pm" ? "pm" : "am",
174
+ )
175
+ }
176
+ >
177
+ <option value="am">오전</option>
178
+ <option value="pm">오후</option>
179
+ </select>
180
+ ) : null}
181
+ </div>
182
+ {iconPosition === "right" ? (
183
+ <button
184
+ type="button"
185
+ className="input-time-icon-button"
186
+ tabIndex={-1}
187
+ aria-label={iconLabel}
188
+ disabled={disabled}
189
+ onMouseDown={onStepperMouseDown}
190
+ onClick={onClockClick}
191
+ >
192
+ {icon}
193
+ </button>
194
+ ) : null}
195
+ </div>
196
+ {activeUnit && !disabled && !readOnly ? (
197
+ <div className="input-time-stepper" aria-label="시간 값 조절">
198
+ <button
199
+ type="button"
200
+ className="input-time-stepper-button"
201
+ tabIndex={-1}
202
+ aria-label={`${activeUnit} 증가`}
203
+ onMouseDown={onStepperMouseDown}
204
+ onClick={event => {
205
+ onInteractiveClick(event);
206
+ onStep(1);
207
+ }}
208
+ >
209
+ <Calendar.Icon.Chevron.up aria-hidden="true" />
210
+ </button>
211
+ <button
212
+ type="button"
213
+ className="input-time-stepper-button"
214
+ tabIndex={-1}
215
+ aria-label={`${activeUnit} 감소`}
216
+ onMouseDown={onStepperMouseDown}
217
+ onClick={event => {
218
+ onInteractiveClick(event);
219
+ onStep(-1);
220
+ }}
221
+ >
222
+ <Calendar.Icon.Chevron.down aria-hidden="true" />
223
+ </button>
224
+ </div>
225
+ ) : null}
226
+ </div>
227
+ );
228
+ },
229
+ );
230
+
231
+ InputTimeTrigger.displayName = "InputTimeTrigger";
232
+
233
+ export default InputTimeTrigger;
@@ -1,6 +1,13 @@
1
1
  import "../../styles/time.scss";
2
- import InputTimePicker from "./Picker";
2
+ import InputTimeTemplate from "./Template";
3
+ import InputTimeTrigger from "./Trigger";
4
+
5
+ export const InputTimeDefault = {
6
+ Template: InputTimeTemplate,
7
+ };
3
8
 
4
9
  export const InputTime = {
5
- Picker: InputTimePicker,
10
+ Template: InputTimeTemplate,
11
+ Default: InputTimeDefault,
12
+ Trigger: InputTimeTrigger,
6
13
  };