@uniai-fe/uds-primitives 0.10.4 → 0.12.0
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 +38 -452
- package/dist/styles.css +1233 -508
- package/package.json +3 -3
- package/src/components/button/markup/Base.tsx +1 -1
- package/src/components/button/styles/button.scss +84 -1
- package/src/components/button/styles/variables.scss +73 -0
- package/src/components/button/types/options.ts +8 -0
- package/src/components/button/types/props.ts +30 -6
- package/src/components/input/markup/address/Button.tsx +1 -1
- package/src/components/input/markup/time/Template.tsx +423 -0
- package/src/components/input/markup/time/Trigger.tsx +289 -0
- package/src/components/input/markup/time/index.tsx +2 -2
- package/src/components/input/styles/time.scss +160 -317
- package/src/components/input/styles/variables.scss +9 -47
- package/src/components/input/types/time.ts +61 -120
- package/src/components/time-picker/img/minus.svg +3 -0
- package/src/components/time-picker/img/plus.svg +3 -0
- package/src/components/time-picker/index.scss +1 -0
- package/src/components/time-picker/index.tsx +11 -0
- package/src/components/time-picker/markup/Footer.tsx +71 -0
- package/src/components/time-picker/markup/HourSection.tsx +99 -0
- package/src/components/time-picker/markup/MinuteSection.tsx +86 -0
- package/src/components/time-picker/markup/Summary.tsx +24 -0
- package/src/components/time-picker/markup/Template.tsx +86 -0
- package/src/components/time-picker/markup/index.tsx +10 -0
- package/src/components/time-picker/styles/index.scss +2 -0
- package/src/components/time-picker/styles/template.scss +174 -0
- package/src/components/time-picker/styles/variables.scss +64 -0
- package/src/components/time-picker/types/index.ts +1 -0
- package/src/components/time-picker/types/time-picker.ts +94 -0
- package/src/components/time-picker/utils/index.ts +1 -0
- package/src/components/time-picker/utils/time.ts +110 -0
- package/src/index.scss +1 -0
- package/src/index.tsx +1 -0
- package/src/components/input/markup/time/Picker.tsx +0 -378
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
import type { PopoverProps, ScrollAreaProps } from "@mantine/core";
|
|
2
|
-
import type {
|
|
3
|
-
TimePickerAmPmLabels,
|
|
4
|
-
TimePickerFormat,
|
|
5
|
-
TimePickerPasteSplit,
|
|
6
|
-
TimePickerPresets,
|
|
7
|
-
} from "@mantine/dates";
|
|
8
1
|
import type { ComponentPropsWithoutRef, FocusEvent, ReactNode } from "react";
|
|
9
2
|
import type { UseFormRegisterReturn } from "react-hook-form";
|
|
10
3
|
import type { FormFieldWidth } from "../../form/types/props";
|
|
4
|
+
import type { TimePickerFormat } from "../../time-picker/types";
|
|
11
5
|
import type { InputPriority, InputSize, InputState } from "./foundation";
|
|
12
6
|
|
|
13
7
|
/**
|
|
14
|
-
* Input Time icon
|
|
8
|
+
* Input Time icon 위치.
|
|
9
|
+
* @typedef {"left" | "right"} InputTimeIconPosition
|
|
15
10
|
*/
|
|
16
11
|
export type InputTimeIconPosition = "left" | "right";
|
|
17
12
|
|
|
18
13
|
/**
|
|
19
|
-
* Input
|
|
14
|
+
* Input Time hidden input props.
|
|
20
15
|
*/
|
|
21
16
|
export type InputTimeHiddenInputProps = Omit<
|
|
22
17
|
ComponentPropsWithoutRef<"input">,
|
|
@@ -24,7 +19,8 @@ export type InputTimeHiddenInputProps = Omit<
|
|
|
24
19
|
>;
|
|
25
20
|
|
|
26
21
|
/**
|
|
27
|
-
* Input
|
|
22
|
+
* Input Time Template props.
|
|
23
|
+
* 저장값은 24시간 `HH:mm` 또는 `HH:mm:ss` 문자열을 사용한다.
|
|
28
24
|
* @property {string} [value] 제어형 시간 값
|
|
29
25
|
* @property {string} [defaultValue] 비제어 초기 시간 값
|
|
30
26
|
* @property {(value:string)=>void} [onChange] 값 변경 핸들러
|
|
@@ -32,234 +28,179 @@ export type InputTimeHiddenInputProps = Omit<
|
|
|
32
28
|
* @property {string} [name] form name
|
|
33
29
|
* @property {string} [form] form id
|
|
34
30
|
* @property {UseFormRegisterReturn} [register] react-hook-form register 결과
|
|
35
|
-
* @property {
|
|
36
|
-
* @property {
|
|
37
|
-
* @property {
|
|
31
|
+
* @property {"primary" | "secondary" | "tertiary" | "table"} [priority] input priority
|
|
32
|
+
* @property {"small" | "medium" | "large"} [size] input size
|
|
33
|
+
* @property {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [state] input state
|
|
38
34
|
* @property {boolean} [block] width 100% 여부
|
|
39
|
-
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset
|
|
35
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset
|
|
40
36
|
* @property {string} [className] root className
|
|
41
37
|
* @property {boolean} [disabled] disabled 여부
|
|
42
38
|
* @property {boolean} [readOnly] readOnly 여부
|
|
43
39
|
* @property {boolean} [required] required 여부
|
|
44
40
|
* @property {string} [id] root id
|
|
45
|
-
* @property {"12h" | "24h"} [format]
|
|
46
|
-
* @property {boolean} [withSeconds]
|
|
47
|
-
* @property {boolean} [
|
|
48
|
-
* @property {
|
|
49
|
-
* @property {
|
|
50
|
-
* @property {
|
|
51
|
-
* @property {number} [hoursStep] hour step
|
|
52
|
-
* @property {number} [minutesStep] minute step
|
|
53
|
-
* @property {number} [secondsStep] second step
|
|
41
|
+
* @property {"12h" | "24h"} [format] 표시 형식
|
|
42
|
+
* @property {boolean} [withSeconds] 초 field 노출 여부
|
|
43
|
+
* @property {boolean} [clearable] 삭제 액션 노출 여부
|
|
44
|
+
* @property {number} [hoursStep] 시 증감 단위
|
|
45
|
+
* @property {number} [minutesStep] 분 증감 단위
|
|
46
|
+
* @property {number} [secondsStep] 초 증감 단위
|
|
54
47
|
* @property {ReactNode} [icon] clock icon override
|
|
55
48
|
* @property {"left" | "right"} [iconPosition] clock icon 위치
|
|
56
49
|
* @property {string} [iconLabel] clock icon 접근성 라벨
|
|
57
|
-
* @property {
|
|
58
|
-
* @property {
|
|
59
|
-
* @property {
|
|
60
|
-
* @property {string} [
|
|
61
|
-
* @property {string} [
|
|
62
|
-
* @property {string} [
|
|
63
|
-
* @property {string} [
|
|
64
|
-
* @property {
|
|
65
|
-
* @property {string} [hoursPlaceholder] hour placeholder
|
|
66
|
-
* @property {string} [minutesPlaceholder] minute placeholder
|
|
67
|
-
* @property {string} [secondsPlaceholder] second placeholder
|
|
68
|
-
* @property {TimePickerPasteSplit} [pasteSplit] paste parser
|
|
69
|
-
* @property {TimePickerPresets} [presets] dropdown preset
|
|
70
|
-
* @property {number} [maxDropdownContentHeight] dropdown 최대 높이
|
|
71
|
-
* @property {ScrollAreaProps} [scrollAreaProps] dropdown scroll area 옵션
|
|
72
|
-
* @property {boolean} [reverseTimeControlsList] dropdown control 역순 여부
|
|
50
|
+
* @property {InputTimeHiddenInputProps} [hiddenInputProps] hidden input props
|
|
51
|
+
* @property {string} [hoursInputLabel] 시 input 접근성 라벨
|
|
52
|
+
* @property {string} [minutesInputLabel] 분 input 접근성 라벨
|
|
53
|
+
* @property {string} [secondsInputLabel] 초 input 접근성 라벨
|
|
54
|
+
* @property {string} [amPmInputLabel] 오전/오후 접근성 라벨
|
|
55
|
+
* @property {string} [hoursPlaceholder] 시 placeholder
|
|
56
|
+
* @property {string} [minutesPlaceholder] 분 placeholder
|
|
57
|
+
* @property {string} [secondsPlaceholder] 초 placeholder
|
|
73
58
|
* @property {(event:FocusEvent<HTMLInputElement>)=>void} [onFocus] focus 핸들러
|
|
74
59
|
* @property {(event:FocusEvent<HTMLDivElement>)=>void} [onBlur] blur 핸들러
|
|
75
60
|
*/
|
|
76
|
-
export interface
|
|
61
|
+
export interface InputTimeTemplateProps {
|
|
77
62
|
/**
|
|
78
|
-
* 제어형 시간
|
|
63
|
+
* 제어형 시간 값.
|
|
79
64
|
*/
|
|
80
65
|
value?: string;
|
|
81
66
|
/**
|
|
82
|
-
* 비제어 초기 시간
|
|
67
|
+
* 비제어 초기 시간 값.
|
|
83
68
|
*/
|
|
84
69
|
defaultValue?: string;
|
|
85
70
|
/**
|
|
86
|
-
* 값 변경
|
|
71
|
+
* 값 변경 핸들러.
|
|
87
72
|
*/
|
|
88
73
|
onChange?: (value: string) => void;
|
|
89
74
|
/**
|
|
90
|
-
* 값 변경
|
|
75
|
+
* 값 변경 별칭.
|
|
91
76
|
*/
|
|
92
77
|
onValueChange?: (value: string) => void;
|
|
93
78
|
/**
|
|
94
|
-
* form name
|
|
79
|
+
* form name.
|
|
95
80
|
*/
|
|
96
81
|
name?: string;
|
|
97
82
|
/**
|
|
98
|
-
* form id
|
|
83
|
+
* form id.
|
|
99
84
|
*/
|
|
100
85
|
form?: string;
|
|
101
86
|
/**
|
|
102
|
-
* react-hook-form register
|
|
87
|
+
* react-hook-form register 결과.
|
|
103
88
|
*/
|
|
104
89
|
register?: UseFormRegisterReturn;
|
|
105
90
|
/**
|
|
106
|
-
*
|
|
91
|
+
* "primary" | "secondary" | "tertiary" | "table".
|
|
107
92
|
*/
|
|
108
93
|
priority?: InputPriority;
|
|
109
94
|
/**
|
|
110
|
-
*
|
|
95
|
+
* "small" | "medium" | "large".
|
|
111
96
|
*/
|
|
112
97
|
size?: InputSize;
|
|
113
98
|
/**
|
|
114
|
-
*
|
|
99
|
+
* "default" | "active" | "focused" | "success" | "error" | "disabled" | "loading".
|
|
115
100
|
*/
|
|
116
101
|
state?: InputState;
|
|
117
102
|
/**
|
|
118
|
-
* width 100%
|
|
103
|
+
* width 100% 여부.
|
|
119
104
|
*/
|
|
120
105
|
block?: boolean;
|
|
121
106
|
/**
|
|
122
|
-
*
|
|
107
|
+
* "full" | "fit" | "fill" | "auto" | number | string.
|
|
123
108
|
*/
|
|
124
109
|
width?: FormFieldWidth;
|
|
125
110
|
/**
|
|
126
|
-
* root className
|
|
111
|
+
* root className.
|
|
127
112
|
*/
|
|
128
113
|
className?: string;
|
|
129
114
|
/**
|
|
130
|
-
* disabled
|
|
115
|
+
* disabled 여부.
|
|
131
116
|
*/
|
|
132
117
|
disabled?: boolean;
|
|
133
118
|
/**
|
|
134
|
-
* readOnly
|
|
119
|
+
* readOnly 여부.
|
|
135
120
|
*/
|
|
136
121
|
readOnly?: boolean;
|
|
137
122
|
/**
|
|
138
|
-
* required
|
|
123
|
+
* required 여부.
|
|
139
124
|
*/
|
|
140
125
|
required?: boolean;
|
|
141
126
|
/**
|
|
142
|
-
* root id
|
|
127
|
+
* root id.
|
|
143
128
|
*/
|
|
144
129
|
id?: string;
|
|
145
130
|
/**
|
|
146
|
-
*
|
|
131
|
+
* "12h" | "24h".
|
|
147
132
|
*/
|
|
148
133
|
format?: TimePickerFormat;
|
|
149
134
|
/**
|
|
150
|
-
*
|
|
135
|
+
* 초 field 노출 여부.
|
|
151
136
|
*/
|
|
152
137
|
withSeconds?: boolean;
|
|
153
138
|
/**
|
|
154
|
-
*
|
|
155
|
-
*/
|
|
156
|
-
withDropdown?: boolean;
|
|
157
|
-
/**
|
|
158
|
-
* clear 버튼 노출 여부
|
|
139
|
+
* 삭제 액션 노출 여부.
|
|
159
140
|
*/
|
|
160
141
|
clearable?: boolean;
|
|
161
142
|
/**
|
|
162
|
-
*
|
|
163
|
-
*/
|
|
164
|
-
min?: string;
|
|
165
|
-
/**
|
|
166
|
-
* 최대 시간 값
|
|
167
|
-
*/
|
|
168
|
-
max?: string;
|
|
169
|
-
/**
|
|
170
|
-
* hour step
|
|
143
|
+
* 시 증감 단위.
|
|
171
144
|
*/
|
|
172
145
|
hoursStep?: number;
|
|
173
146
|
/**
|
|
174
|
-
*
|
|
147
|
+
* 분 증감 단위.
|
|
175
148
|
*/
|
|
176
149
|
minutesStep?: number;
|
|
177
150
|
/**
|
|
178
|
-
*
|
|
151
|
+
* 초 증감 단위.
|
|
179
152
|
*/
|
|
180
153
|
secondsStep?: number;
|
|
181
154
|
/**
|
|
182
|
-
* clock icon override
|
|
155
|
+
* clock icon override.
|
|
183
156
|
*/
|
|
184
157
|
icon?: ReactNode;
|
|
185
158
|
/**
|
|
186
|
-
*
|
|
159
|
+
* "left" | "right".
|
|
187
160
|
*/
|
|
188
161
|
iconPosition?: InputTimeIconPosition;
|
|
189
162
|
/**
|
|
190
|
-
* clock icon 접근성
|
|
163
|
+
* clock icon 접근성 라벨.
|
|
191
164
|
*/
|
|
192
165
|
iconLabel?: string;
|
|
193
166
|
/**
|
|
194
|
-
*
|
|
195
|
-
*/
|
|
196
|
-
clearButtonLabel?: string;
|
|
197
|
-
/**
|
|
198
|
-
* Mantine Popover 옵션
|
|
199
|
-
*/
|
|
200
|
-
popoverProps?: PopoverProps;
|
|
201
|
-
/**
|
|
202
|
-
* hidden input 옵션
|
|
167
|
+
* hidden input props.
|
|
203
168
|
*/
|
|
204
169
|
hiddenInputProps?: InputTimeHiddenInputProps;
|
|
205
170
|
/**
|
|
206
|
-
*
|
|
171
|
+
* 시 input 접근성 라벨.
|
|
207
172
|
*/
|
|
208
173
|
hoursInputLabel?: string;
|
|
209
174
|
/**
|
|
210
|
-
*
|
|
175
|
+
* 분 input 접근성 라벨.
|
|
211
176
|
*/
|
|
212
177
|
minutesInputLabel?: string;
|
|
213
178
|
/**
|
|
214
|
-
*
|
|
179
|
+
* 초 input 접근성 라벨.
|
|
215
180
|
*/
|
|
216
181
|
secondsInputLabel?: string;
|
|
217
182
|
/**
|
|
218
|
-
*
|
|
183
|
+
* 오전/오후 접근성 라벨.
|
|
219
184
|
*/
|
|
220
185
|
amPmInputLabel?: string;
|
|
221
186
|
/**
|
|
222
|
-
*
|
|
223
|
-
*/
|
|
224
|
-
amPmLabels?: TimePickerAmPmLabels;
|
|
225
|
-
/**
|
|
226
|
-
* hour placeholder
|
|
187
|
+
* 시 placeholder.
|
|
227
188
|
*/
|
|
228
189
|
hoursPlaceholder?: string;
|
|
229
190
|
/**
|
|
230
|
-
*
|
|
191
|
+
* 분 placeholder.
|
|
231
192
|
*/
|
|
232
193
|
minutesPlaceholder?: string;
|
|
233
194
|
/**
|
|
234
|
-
*
|
|
195
|
+
* 초 placeholder.
|
|
235
196
|
*/
|
|
236
197
|
secondsPlaceholder?: string;
|
|
237
198
|
/**
|
|
238
|
-
*
|
|
239
|
-
*/
|
|
240
|
-
pasteSplit?: TimePickerPasteSplit;
|
|
241
|
-
/**
|
|
242
|
-
* dropdown preset
|
|
243
|
-
*/
|
|
244
|
-
presets?: TimePickerPresets;
|
|
245
|
-
/**
|
|
246
|
-
* dropdown 최대 높이
|
|
247
|
-
*/
|
|
248
|
-
maxDropdownContentHeight?: number;
|
|
249
|
-
/**
|
|
250
|
-
* dropdown scroll area 옵션
|
|
251
|
-
*/
|
|
252
|
-
scrollAreaProps?: ScrollAreaProps;
|
|
253
|
-
/**
|
|
254
|
-
* dropdown control 역순 여부
|
|
255
|
-
*/
|
|
256
|
-
reverseTimeControlsList?: boolean;
|
|
257
|
-
/**
|
|
258
|
-
* focus 핸들러
|
|
199
|
+
* focus 핸들러.
|
|
259
200
|
*/
|
|
260
201
|
onFocus?: (event: FocusEvent<HTMLInputElement>) => void;
|
|
261
202
|
/**
|
|
262
|
-
* blur
|
|
203
|
+
* blur 핸들러.
|
|
263
204
|
*/
|
|
264
205
|
onBlur?: (event: FocusEvent<HTMLDivElement>) => void;
|
|
265
206
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM8 11.2002C7.55817 11.2002 7.2002 11.5582 7.2002 12C7.2002 12.4418 7.55817 12.7998 8 12.7998H16C16.4418 12.7998 16.7998 12.4418 16.7998 12C16.7998 11.5582 16.4418 11.2002 16 11.2002H8Z" fill="#3A3C46"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12 7.20508C11.5582 7.20508 11.2002 7.56306 11.2002 8.00488V11.2051H8C7.55817 11.2051 7.2002 11.5631 7.2002 12.0049C7.2002 12.4467 7.55817 12.8047 8 12.8047H11.2002V16.0049C11.2002 16.4467 11.5582 16.8047 12 16.8047C12.4418 16.8047 12.7998 16.4467 12.7998 16.0049V12.8047H16C16.4418 12.8047 16.7998 12.4467 16.7998 12.0049C16.7998 11.5631 16.4418 11.2051 16 11.2051H12.7998V8.00488C12.7998 7.56306 12.4418 7.20508 12 7.20508Z" fill="#3A3C46"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use "./styles/index.scss";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimePicker; 시간 선택 패널 카테고리 배럴.
|
|
3
|
+
* @desc
|
|
4
|
+
* - `TimePicker.Template`: 시간·분 선택과 footer action을 제공한다.
|
|
5
|
+
* - `TimePickerUtils`: 시간 파싱·직렬화·증감 유틸리티다.
|
|
6
|
+
*/
|
|
7
|
+
import "./index.scss";
|
|
8
|
+
|
|
9
|
+
export * from "./markup";
|
|
10
|
+
export * as TimePickerUtils from "./utils";
|
|
11
|
+
export type * from "./types";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Button } from "../../button";
|
|
2
|
+
|
|
3
|
+
interface TimePickerFooterProps {
|
|
4
|
+
/**
|
|
5
|
+
* 삭제 action 노출 여부.
|
|
6
|
+
*/
|
|
7
|
+
clearable: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 비활성화 여부.
|
|
10
|
+
*/
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* 삭제 핸들러.
|
|
14
|
+
*/
|
|
15
|
+
onClear: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* 적용 핸들러.
|
|
18
|
+
*/
|
|
19
|
+
onApply: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TimePicker Footer; 삭제와 적용 action을 렌더한다.
|
|
24
|
+
* @component
|
|
25
|
+
* @param {TimePickerFooterProps} props
|
|
26
|
+
* @param {boolean} props.clearable 삭제 action 노출 여부
|
|
27
|
+
* @param {boolean} props.disabled 비활성화 여부
|
|
28
|
+
* @param {() => void} props.onClear 삭제 핸들러
|
|
29
|
+
* @param {() => void} props.onApply 적용 핸들러
|
|
30
|
+
* @returns {ReactNode} TimePicker footer
|
|
31
|
+
* @example
|
|
32
|
+
* <TimePickerFooter
|
|
33
|
+
* clearable
|
|
34
|
+
* disabled={false}
|
|
35
|
+
* onClear={onClear}
|
|
36
|
+
* onApply={onApply}
|
|
37
|
+
* />
|
|
38
|
+
*/
|
|
39
|
+
export default function TimePickerFooter({
|
|
40
|
+
clearable,
|
|
41
|
+
disabled,
|
|
42
|
+
onClear,
|
|
43
|
+
onApply,
|
|
44
|
+
}: TimePickerFooterProps) {
|
|
45
|
+
return (
|
|
46
|
+
<footer className="time-picker-template-footer">
|
|
47
|
+
{clearable ? (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
className="time-picker-clear-action"
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
onClick={onClear}
|
|
53
|
+
>
|
|
54
|
+
삭제하기
|
|
55
|
+
</button>
|
|
56
|
+
) : (
|
|
57
|
+
<span />
|
|
58
|
+
)}
|
|
59
|
+
<Button.Default
|
|
60
|
+
type="button"
|
|
61
|
+
priority="primary"
|
|
62
|
+
fill="solid"
|
|
63
|
+
size="large"
|
|
64
|
+
disabled={disabled}
|
|
65
|
+
onClick={onApply}
|
|
66
|
+
>
|
|
67
|
+
적용하기
|
|
68
|
+
</Button.Default>
|
|
69
|
+
</footer>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { SegmentedControl } from "../../segmented-control";
|
|
2
|
+
import type { TimePickerFormat, TimePickerPeriod } from "../types";
|
|
3
|
+
|
|
4
|
+
const HOURS_12 = Array.from({ length: 12 }, (_, index) => index + 1);
|
|
5
|
+
const HOURS_24 = Array.from({ length: 24 }, (_, index) => index);
|
|
6
|
+
const PERIOD_OPTIONS = [
|
|
7
|
+
{ value: "am", label: "오전" },
|
|
8
|
+
{ value: "pm", label: "오후" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
interface TimePickerHourSectionProps {
|
|
12
|
+
/**
|
|
13
|
+
* "12h" | "24h".
|
|
14
|
+
*/
|
|
15
|
+
format: TimePickerFormat;
|
|
16
|
+
/**
|
|
17
|
+
* 선택한 표시 시.
|
|
18
|
+
*/
|
|
19
|
+
selectedHour: number | null;
|
|
20
|
+
/**
|
|
21
|
+
* "am" | "pm".
|
|
22
|
+
*/
|
|
23
|
+
period: TimePickerPeriod;
|
|
24
|
+
/**
|
|
25
|
+
* 비활성화 여부.
|
|
26
|
+
*/
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 시 선택 핸들러.
|
|
30
|
+
*/
|
|
31
|
+
onHourSelect: (hours: number) => void;
|
|
32
|
+
/**
|
|
33
|
+
* 오전·오후 변경 핸들러.
|
|
34
|
+
*/
|
|
35
|
+
onPeriodChange: (period: TimePickerPeriod) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* TimePicker Hour Section; 오전·오후와 시 선택 grid를 렌더한다.
|
|
40
|
+
* @component
|
|
41
|
+
* @param {TimePickerHourSectionProps} props
|
|
42
|
+
* @param {"12h" | "24h"} props.format 표시 형식
|
|
43
|
+
* @param {number | null} props.selectedHour 선택한 표시 시
|
|
44
|
+
* @param {"am" | "pm"} props.period 오전·오후
|
|
45
|
+
* @param {boolean} props.disabled 비활성화 여부
|
|
46
|
+
* @param {(hours: number) => void} props.onHourSelect 시 선택 핸들러
|
|
47
|
+
* @param {(period: "am" | "pm") => void} props.onPeriodChange 오전·오후 변경 핸들러
|
|
48
|
+
* @returns {ReactNode} 시간 선택 section
|
|
49
|
+
* @example
|
|
50
|
+
* <TimePickerHourSection
|
|
51
|
+
* format="12h"
|
|
52
|
+
* selectedHour={9}
|
|
53
|
+
* period="am"
|
|
54
|
+
* disabled={false}
|
|
55
|
+
* onHourSelect={onHourSelect}
|
|
56
|
+
* onPeriodChange={onPeriodChange}
|
|
57
|
+
* />
|
|
58
|
+
*/
|
|
59
|
+
export default function TimePickerHourSection({
|
|
60
|
+
format,
|
|
61
|
+
selectedHour,
|
|
62
|
+
period,
|
|
63
|
+
disabled,
|
|
64
|
+
onHourSelect,
|
|
65
|
+
onPeriodChange,
|
|
66
|
+
}: TimePickerHourSectionProps) {
|
|
67
|
+
const hours = format === "12h" ? HOURS_12 : HOURS_24;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<section className="time-picker-template-section">
|
|
71
|
+
<h3>시간 선택</h3>
|
|
72
|
+
{format === "12h" ? (
|
|
73
|
+
<SegmentedControl
|
|
74
|
+
className="time-picker-period-control"
|
|
75
|
+
options={PERIOD_OPTIONS}
|
|
76
|
+
ariaLabel="오전 또는 오후 선택"
|
|
77
|
+
value={period}
|
|
78
|
+
onValueChange={nextPeriod =>
|
|
79
|
+
onPeriodChange(nextPeriod === "pm" ? "pm" : "am")
|
|
80
|
+
}
|
|
81
|
+
/>
|
|
82
|
+
) : null}
|
|
83
|
+
<div className="time-picker-option-grid">
|
|
84
|
+
{hours.map(hour => (
|
|
85
|
+
<button
|
|
86
|
+
key={`time-picker/hour/${hour}`}
|
|
87
|
+
type="button"
|
|
88
|
+
className="time-picker-option"
|
|
89
|
+
aria-pressed={selectedHour === hour}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
onClick={() => onHourSelect(hour)}
|
|
92
|
+
>
|
|
93
|
+
{hour}:00
|
|
94
|
+
</button>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import MinusIcon from "../img/minus.svg";
|
|
2
|
+
import PlusIcon from "../img/plus.svg";
|
|
3
|
+
|
|
4
|
+
const MINUTES = Array.from({ length: 12 }, (_, index) => index * 5);
|
|
5
|
+
|
|
6
|
+
interface TimePickerMinuteSectionProps {
|
|
7
|
+
/**
|
|
8
|
+
* 선택한 분.
|
|
9
|
+
*/
|
|
10
|
+
selectedMinute: number | null;
|
|
11
|
+
/**
|
|
12
|
+
* 비활성화 여부.
|
|
13
|
+
*/
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* 1분 증감 핸들러.
|
|
17
|
+
*/
|
|
18
|
+
onMinuteStep: (amount: 1 | -1) => void;
|
|
19
|
+
/**
|
|
20
|
+
* 분 선택 핸들러.
|
|
21
|
+
*/
|
|
22
|
+
onMinuteSelect: (minutes: number) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* TimePicker Minute Section; 1분 stepper와 5분 선택 grid를 렌더한다.
|
|
27
|
+
* @component
|
|
28
|
+
* @param {TimePickerMinuteSectionProps} props
|
|
29
|
+
* @param {number | null} props.selectedMinute 선택한 분
|
|
30
|
+
* @param {boolean} props.disabled 비활성화 여부
|
|
31
|
+
* @param {(amount: 1 | -1) => void} props.onMinuteStep 1분 증감 핸들러
|
|
32
|
+
* @param {(minutes: number) => void} props.onMinuteSelect 분 선택 핸들러
|
|
33
|
+
* @returns {ReactNode} 분 선택 section
|
|
34
|
+
* @example
|
|
35
|
+
* <TimePickerMinuteSection
|
|
36
|
+
* selectedMinute={30}
|
|
37
|
+
* disabled={false}
|
|
38
|
+
* onMinuteStep={onMinuteStep}
|
|
39
|
+
* onMinuteSelect={onMinuteSelect}
|
|
40
|
+
* />
|
|
41
|
+
*/
|
|
42
|
+
export default function TimePickerMinuteSection({
|
|
43
|
+
selectedMinute,
|
|
44
|
+
disabled,
|
|
45
|
+
onMinuteStep,
|
|
46
|
+
onMinuteSelect,
|
|
47
|
+
}: TimePickerMinuteSectionProps) {
|
|
48
|
+
return (
|
|
49
|
+
<section className="time-picker-template-section">
|
|
50
|
+
<h3>분 선택</h3>
|
|
51
|
+
<div className="time-picker-minute-stepper">
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
aria-label="1분 증가"
|
|
55
|
+
disabled={disabled}
|
|
56
|
+
onClick={() => onMinuteStep(1)}
|
|
57
|
+
>
|
|
58
|
+
<PlusIcon aria-hidden="true" />
|
|
59
|
+
</button>
|
|
60
|
+
<strong>{String(selectedMinute ?? 0).padStart(2, "0")}분</strong>
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
aria-label="1분 감소"
|
|
64
|
+
disabled={disabled}
|
|
65
|
+
onClick={() => onMinuteStep(-1)}
|
|
66
|
+
>
|
|
67
|
+
<MinusIcon aria-hidden="true" />
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="time-picker-option-grid">
|
|
71
|
+
{MINUTES.map(minute => (
|
|
72
|
+
<button
|
|
73
|
+
key={`time-picker/minute/${minute}`}
|
|
74
|
+
type="button"
|
|
75
|
+
className="time-picker-option"
|
|
76
|
+
aria-pressed={selectedMinute === minute}
|
|
77
|
+
disabled={disabled}
|
|
78
|
+
onClick={() => onMinuteSelect(minute)}
|
|
79
|
+
>
|
|
80
|
+
{String(minute).padStart(2, "0")}
|
|
81
|
+
</button>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</section>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface TimePickerSummaryProps {
|
|
2
|
+
/**
|
|
3
|
+
* 선택한 시간 표시값.
|
|
4
|
+
*/
|
|
5
|
+
summary: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TimePicker Summary; 현재 선택한 시간을 표시한다.
|
|
10
|
+
* @component
|
|
11
|
+
* @param {TimePickerSummaryProps} props
|
|
12
|
+
* @param {string} props.summary 선택한 시간 표시값
|
|
13
|
+
* @returns {ReactNode} 선택 시간 요약
|
|
14
|
+
* @example
|
|
15
|
+
* <TimePickerSummary summary="09:30 오전" />
|
|
16
|
+
*/
|
|
17
|
+
export default function TimePickerSummary({ summary }: TimePickerSummaryProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="time-picker-summary">
|
|
20
|
+
<span>선택한 시간</span>
|
|
21
|
+
<strong>{summary}</strong>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|