@uniai-fe/uds-primitives 0.2.2 → 0.2.4
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 +1 -1
- package/dist/styles.css +299 -81
- package/package.json +16 -9
- package/src/components/{input/img/calendar → calendar/img}/calendar.svg +5 -0
- package/src/components/calendar/index.tsx +5 -3
- package/src/components/calendar/markup/Core.tsx +67 -0
- package/src/components/calendar/markup/Icon.tsx +20 -0
- package/src/components/calendar/markup/Root.tsx +126 -0
- package/src/components/calendar/markup/index.tsx +24 -2
- package/src/components/calendar/markup/layout/Body.tsx +12 -0
- package/src/components/calendar/markup/layout/Container.tsx +43 -0
- package/src/components/calendar/markup/layout/Footer.tsx +12 -0
- package/src/components/calendar/markup/layout/Header.tsx +12 -0
- package/src/components/calendar/styles/index.scss +2 -0
- package/src/components/calendar/styles/layout.scss +21 -0
- package/src/components/calendar/styles/mantine-calendar.scss +240 -0
- package/src/components/calendar/types/calendar.ts +208 -0
- package/src/components/calendar/types/index.ts +1 -4
- package/src/components/calendar/utils/index.ts +1 -4
- package/src/components/calendar/utils/value-mapper.ts +24 -0
- package/src/components/input/index.scss +1 -1
- package/src/components/input/markup/date/Template.tsx +181 -0
- package/src/components/input/markup/date/Trigger.tsx +79 -0
- package/src/components/input/markup/date/button/ApplyButton.tsx +38 -0
- package/src/components/input/markup/date/button/ClearButton.tsx +36 -0
- package/src/components/input/markup/date/button/TodayButton.tsx +36 -0
- package/src/components/input/markup/date/footer/Container.tsx +24 -0
- package/src/components/input/markup/date/footer/Template.tsx +36 -0
- package/src/components/input/markup/date/footer/UtilContainer.tsx +23 -0
- package/src/components/input/markup/date/footer/index.ts +3 -0
- package/src/components/input/markup/date/index.tsx +27 -0
- package/src/components/input/markup/index.tsx +2 -4
- package/src/components/input/styles/date.scss +45 -0
- package/src/components/input/types/date.ts +286 -0
- package/src/components/input/types/index.ts +1 -1
- package/src/components/input/utils/address.ts +2 -2
- package/src/components/input/utils/date.ts +61 -0
- package/src/components/input/utils/index.tsx +1 -0
- package/src/components/pop-over/index.scss +1 -0
- package/src/components/pop-over/index.tsx +4 -0
- package/src/components/pop-over/markup/Content.tsx +77 -0
- package/src/components/pop-over/markup/Root.tsx +28 -0
- package/src/components/pop-over/markup/Trigger.tsx +26 -0
- package/src/components/pop-over/markup/index.tsx +17 -0
- package/src/components/pop-over/styles/base.scss +5 -0
- package/src/components/pop-over/styles/content.scss +24 -0
- package/src/components/pop-over/styles/index.scss +2 -0
- package/src/components/pop-over/types/index.ts +1 -0
- package/src/components/pop-over/types/pop-over.ts +86 -0
- package/src/index.scss +1 -0
- package/src/index.tsx +3 -1
- package/src/init/mantine.css +5 -0
- package/src/init/mantine.ts +2 -0
- package/src/components/input/markup/calendar/Base.tsx +0 -329
- package/src/components/input/markup/calendar/index.tsx +0 -8
- package/src/components/input/styles/calendar.scss +0 -110
- package/src/components/input/types/calendar.ts +0 -208
- /package/src/components/{input/img/calendar → calendar/img}/chevron-down.svg +0 -0
- /package/src/components/{input/img/calendar → calendar/img}/chevron-left.svg +0 -0
- /package/src/components/{input/img/calendar → calendar/img}/chevron-right.svg +0 -0
- /package/src/components/{input/img/calendar → calendar/img}/chevron-up.svg +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DatePickerProps as MantineDatePickerProps,
|
|
3
|
+
DatePickerStylesNames,
|
|
4
|
+
} from "@mantine/dates";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calendar 모드.
|
|
9
|
+
* @property {"date" | "date-time" | "time"} mode 날짜/시간 표시 모드
|
|
10
|
+
*/
|
|
11
|
+
export type CalendarMode = "date" | "date-time" | "time";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Calendar 컬럼 수.
|
|
15
|
+
* @property {1 | 2} columns 단일/이중 컬럼 개수
|
|
16
|
+
*/
|
|
17
|
+
export type CalendarColumns = 1 | 2;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Calendar value 직렬화 타입.
|
|
21
|
+
* @property {string | null} value YYYY-MM-DD 문자열 또는 null
|
|
22
|
+
*/
|
|
23
|
+
export type CalendarValue = string | null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calendar 값 변경 핸들러.
|
|
27
|
+
* @param {CalendarValue} value 선택된 직렬화 값
|
|
28
|
+
* @example
|
|
29
|
+
* const onChange = (value: CalendarValue) => {
|
|
30
|
+
* console.log(value);
|
|
31
|
+
* };
|
|
32
|
+
*/
|
|
33
|
+
export type CalendarOnChange = (value: CalendarValue) => void;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Mantine DatePicker 공개 옵션.
|
|
37
|
+
* @property {Partial<Record<DatePickerStylesNames, string>>} [classNames] Mantine 내부 스타일 클래스 매핑
|
|
38
|
+
*/
|
|
39
|
+
export type CalendarDatePickerProps = Partial<
|
|
40
|
+
Omit<
|
|
41
|
+
MantineDatePickerProps<"default">,
|
|
42
|
+
| "value"
|
|
43
|
+
| "defaultValue"
|
|
44
|
+
| "onChange"
|
|
45
|
+
| "type"
|
|
46
|
+
| "numberOfColumns"
|
|
47
|
+
| "classNames"
|
|
48
|
+
| "styles"
|
|
49
|
+
>
|
|
50
|
+
> & {
|
|
51
|
+
/**
|
|
52
|
+
* Mantine 내부 스타일 classNames override.
|
|
53
|
+
*/
|
|
54
|
+
classNames?: Partial<Record<DatePickerStylesNames, string>>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Calendar Grid props.
|
|
59
|
+
* @property {CalendarValue} value 현재 선택 값
|
|
60
|
+
* @property {CalendarOnChange} onChange 값 변경 핸들러
|
|
61
|
+
* @property {CalendarDatePickerProps} [datePickerProps] Mantine DatePicker 옵션
|
|
62
|
+
*/
|
|
63
|
+
export interface CalendarGridProps {
|
|
64
|
+
/**
|
|
65
|
+
* 현재 선택 값
|
|
66
|
+
*/
|
|
67
|
+
value: CalendarValue;
|
|
68
|
+
/**
|
|
69
|
+
* 값 변경 핸들러
|
|
70
|
+
*/
|
|
71
|
+
onChange: CalendarOnChange;
|
|
72
|
+
/**
|
|
73
|
+
* Mantine DatePicker 옵션
|
|
74
|
+
*/
|
|
75
|
+
datePickerProps?: CalendarDatePickerProps;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Calendar Layout Container props.
|
|
80
|
+
* @property {ReactNode} [header] 상단 콘텐츠
|
|
81
|
+
* @property {ReactNode} body 본문(Calendar Grid) 콘텐츠
|
|
82
|
+
* @property {ReactNode} [footer] 하단 콘텐츠
|
|
83
|
+
* @property {CalendarMode} [mode="date"] calendar 모드
|
|
84
|
+
* @property {CalendarColumns} [columns=1] 동시 표시 컬럼 수
|
|
85
|
+
* @property {boolean} [disabled] 비활성화 상태
|
|
86
|
+
* @property {boolean} [readOnly] 읽기 전용 상태
|
|
87
|
+
*/
|
|
88
|
+
export interface CalendarContainerProps {
|
|
89
|
+
/**
|
|
90
|
+
* 상단 콘텐츠
|
|
91
|
+
*/
|
|
92
|
+
header?: ReactNode;
|
|
93
|
+
/**
|
|
94
|
+
* 본문(Calendar Grid) 콘텐츠
|
|
95
|
+
*/
|
|
96
|
+
body: ReactNode;
|
|
97
|
+
/**
|
|
98
|
+
* 하단 콘텐츠
|
|
99
|
+
*/
|
|
100
|
+
footer?: ReactNode;
|
|
101
|
+
/**
|
|
102
|
+
* calendar 모드
|
|
103
|
+
*/
|
|
104
|
+
mode?: CalendarMode;
|
|
105
|
+
/**
|
|
106
|
+
* 동시 표시 컬럼 수
|
|
107
|
+
*/
|
|
108
|
+
columns?: CalendarColumns;
|
|
109
|
+
/**
|
|
110
|
+
* 비활성화 상태
|
|
111
|
+
*/
|
|
112
|
+
disabled?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* 읽기 전용 상태
|
|
115
|
+
*/
|
|
116
|
+
readOnly?: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Calendar Root props.
|
|
121
|
+
* @extends CalendarContainerProps
|
|
122
|
+
* @property {string} [className] Trigger element className override
|
|
123
|
+
* @property {CalendarValue} value 현재 선택 값
|
|
124
|
+
* @property {CalendarOnChange} onChange 값 변경 핸들러
|
|
125
|
+
* @property {CalendarDatePickerProps} [datePickerProps] Mantine DatePicker 옵션
|
|
126
|
+
* @property {ReactNode} children Trigger 슬롯(children)
|
|
127
|
+
* @property {boolean} [open] 제어형 open 상태
|
|
128
|
+
* @property {boolean} [defaultOpen] 비제어 초기 open 상태
|
|
129
|
+
* @property {(open: boolean) => void} [onOpenChange] open 상태 변경 핸들러
|
|
130
|
+
* @property {"top" | "right" | "bottom" | "left"} [side="bottom"] content 배치 방향
|
|
131
|
+
* @property {"start" | "center" | "end"} [align="start"] content 정렬 기준
|
|
132
|
+
* @property {number} [sideOffset=4] trigger와 content 간격
|
|
133
|
+
* @property {number} [alignOffset] 정렬 보정값
|
|
134
|
+
* @property {boolean} [withPortal=true] Portal 사용 여부
|
|
135
|
+
* @property {HTMLElement | null} [portalContainer] Portal 컨테이너
|
|
136
|
+
*/
|
|
137
|
+
export interface CalendarRootProps extends Omit<
|
|
138
|
+
CalendarContainerProps,
|
|
139
|
+
"body"
|
|
140
|
+
> {
|
|
141
|
+
/**
|
|
142
|
+
* Trigger element className override
|
|
143
|
+
*/
|
|
144
|
+
className?: string;
|
|
145
|
+
/**
|
|
146
|
+
* 현재 선택 값
|
|
147
|
+
*/
|
|
148
|
+
value: CalendarValue;
|
|
149
|
+
/**
|
|
150
|
+
* 값 변경 핸들러
|
|
151
|
+
*/
|
|
152
|
+
onChange: CalendarOnChange;
|
|
153
|
+
/**
|
|
154
|
+
* Mantine DatePicker 옵션
|
|
155
|
+
*/
|
|
156
|
+
datePickerProps?: CalendarDatePickerProps;
|
|
157
|
+
/**
|
|
158
|
+
* Trigger 슬롯(children)
|
|
159
|
+
*/
|
|
160
|
+
children: ReactNode;
|
|
161
|
+
/**
|
|
162
|
+
* 제어형 open 상태
|
|
163
|
+
*/
|
|
164
|
+
open?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* 비제어 초기 open 상태
|
|
167
|
+
*/
|
|
168
|
+
defaultOpen?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* open 상태 변경 핸들러
|
|
171
|
+
*/
|
|
172
|
+
onOpenChange?: (open: boolean) => void;
|
|
173
|
+
/**
|
|
174
|
+
* content 배치 방향
|
|
175
|
+
*/
|
|
176
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
177
|
+
/**
|
|
178
|
+
* content 정렬 기준
|
|
179
|
+
*/
|
|
180
|
+
align?: "start" | "center" | "end";
|
|
181
|
+
/**
|
|
182
|
+
* trigger와 content 간격
|
|
183
|
+
*/
|
|
184
|
+
sideOffset?: number;
|
|
185
|
+
/**
|
|
186
|
+
* 정렬 보정값
|
|
187
|
+
*/
|
|
188
|
+
alignOffset?: number;
|
|
189
|
+
/**
|
|
190
|
+
* Portal 사용 여부
|
|
191
|
+
*/
|
|
192
|
+
withPortal?: boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Portal 컨테이너
|
|
195
|
+
*/
|
|
196
|
+
portalContainer?: HTMLElement | null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Calendar 섹션 공용 props.
|
|
201
|
+
* @property {ReactNode} [children] 섹션 내부 콘텐츠
|
|
202
|
+
*/
|
|
203
|
+
export interface CalendarSectionProps {
|
|
204
|
+
/**
|
|
205
|
+
* 섹션 내부 콘텐츠
|
|
206
|
+
*/
|
|
207
|
+
children?: ReactNode;
|
|
208
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { dayjs } from "../../../init/dayjs";
|
|
2
|
+
import type { CalendarValue } from "../types";
|
|
3
|
+
|
|
4
|
+
const DATE_FORMAT = "YYYY-MM-DD";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calendar value를 DatePicker value로 변환한다.
|
|
8
|
+
* @param {CalendarValue} value YYYY-MM-DD 문자열 값
|
|
9
|
+
* @returns {Date | null} DatePicker value
|
|
10
|
+
* @example
|
|
11
|
+
* mapValueToPicker("2026-02-13");
|
|
12
|
+
*/
|
|
13
|
+
export const mapValueToPicker = (value: CalendarValue) =>
|
|
14
|
+
value ? dayjs(value).toDate() : null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* DatePicker value를 Calendar 직렬화 값으로 변환한다.
|
|
18
|
+
* @param {Date | null} value DatePicker value
|
|
19
|
+
* @returns {CalendarValue} YYYY-MM-DD 직렬화 값
|
|
20
|
+
* @example
|
|
21
|
+
* parseValueFromPicker(new Date("2026-02-13"));
|
|
22
|
+
*/
|
|
23
|
+
export const parseValueFromPicker = (value: Date | null): CalendarValue =>
|
|
24
|
+
value ? dayjs(value).format(DATE_FORMAT) : null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@use "./styles/variables.scss" as inputVariables;
|
|
2
2
|
@use "./styles/foundation.scss" as inputFoundation;
|
|
3
3
|
@use "./styles/text.scss" as inputText;
|
|
4
|
-
@use "./styles/
|
|
4
|
+
@use "./styles/date.scss" as inputDate;
|
|
5
5
|
@use "./styles/address.scss" as inputAddress;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ChangeEvent, MouseEvent as ReactMouseEvent } from "react";
|
|
4
|
+
import { forwardRef, useCallback, useMemo } from "react";
|
|
5
|
+
import { useUncontrolled } from "@mantine/hooks";
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
import { Calendar } from "../../../calendar";
|
|
8
|
+
import type {
|
|
9
|
+
InputCalendarProps,
|
|
10
|
+
InputCalendarTriggerRenderProps,
|
|
11
|
+
} from "../../types";
|
|
12
|
+
import type { CalendarValue } from "../../../calendar";
|
|
13
|
+
import {
|
|
14
|
+
createEmptyValue,
|
|
15
|
+
formatTriggerValue,
|
|
16
|
+
getTodayValue,
|
|
17
|
+
serializeCalendarValue,
|
|
18
|
+
} from "../../utils/date";
|
|
19
|
+
import InputDateFooterTemplate from "./footer/Template";
|
|
20
|
+
import InputDateTrigger from "./Trigger";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Input Date Template; trigger + calendar 조합.
|
|
24
|
+
* @component
|
|
25
|
+
* @param {InputCalendarProps} props
|
|
26
|
+
* @param {CalendarMode} [props.mode="date"] 날짜/시간 모드
|
|
27
|
+
* @param {CalendarColumns} [props.columns=1] 동시 노출 달력 수
|
|
28
|
+
* @param {CalendarValue} [props.value] 제어형 값
|
|
29
|
+
* @param {CalendarValue} [props.defaultValue] 비제어 초기값
|
|
30
|
+
* @param {CalendarOnChange} [props.onChange] 값 변경 이벤트
|
|
31
|
+
* @param {CalendarOnChange} [props.onValueChange] onChange alias
|
|
32
|
+
* @param {boolean} [props.readOnly] 읽기 전용 여부
|
|
33
|
+
* @param {boolean} [props.disabled] 비활성화 여부
|
|
34
|
+
* @param {CalendarDatePickerProps} [props.datePickerProps] Mantine DatePicker 옵션
|
|
35
|
+
* @param {string} [props.name] form name/RHF name
|
|
36
|
+
* @param {UseFormRegisterReturn} [props.register] RHF register
|
|
37
|
+
* @param {string} [props.placeholder="YYYY-MM-DD"] placeholder
|
|
38
|
+
* @param {ReactNode} [props.header] 패널 header 콘텐츠
|
|
39
|
+
* @param {ReactNode} [props.footer] 패널 footer 콘텐츠
|
|
40
|
+
* @param {unknown} [props.timePicker] TimePicker 확장용 예약 슬롯(현재 미구현)
|
|
41
|
+
* @param {boolean} [props.calendarOpened] calendar 열림 제어 상태
|
|
42
|
+
* @param {(event: MouseEvent<Element>) => void} [props.onClick] trigger 클릭 핸들러
|
|
43
|
+
* @param {string} [props.className] root className
|
|
44
|
+
* @param {string} [props.id] trigger id
|
|
45
|
+
* @param {ReactNode} [props.trigger] 커스텀 trigger 슬롯
|
|
46
|
+
* @param {(props: InputCalendarTriggerRenderProps) => ReactNode} [props.renderTrigger] 커스텀 trigger 렌더 함수
|
|
47
|
+
*/
|
|
48
|
+
const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
49
|
+
(
|
|
50
|
+
{
|
|
51
|
+
mode = "date",
|
|
52
|
+
columns = 1,
|
|
53
|
+
value,
|
|
54
|
+
defaultValue,
|
|
55
|
+
onChange,
|
|
56
|
+
onValueChange,
|
|
57
|
+
readOnly,
|
|
58
|
+
disabled,
|
|
59
|
+
datePickerProps,
|
|
60
|
+
name,
|
|
61
|
+
register,
|
|
62
|
+
placeholder = "YYYY-MM-DD",
|
|
63
|
+
className,
|
|
64
|
+
header,
|
|
65
|
+
footer,
|
|
66
|
+
calendarOpened,
|
|
67
|
+
onClick: triggerOnClick,
|
|
68
|
+
id,
|
|
69
|
+
trigger,
|
|
70
|
+
renderTrigger,
|
|
71
|
+
},
|
|
72
|
+
ref,
|
|
73
|
+
) => {
|
|
74
|
+
// useUncontrolled로 제어형/비제어 값을 모두 허용한다.
|
|
75
|
+
const [calendarValue, setCalendarValue] = useUncontrolled<CalendarValue>({
|
|
76
|
+
value,
|
|
77
|
+
defaultValue,
|
|
78
|
+
finalValue: createEmptyValue(),
|
|
79
|
+
onChange: next => {
|
|
80
|
+
onChange?.(next as CalendarValue);
|
|
81
|
+
onValueChange?.(next as CalendarValue);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// react-hook-form register onChange를 수동 호출해 값 직렬화를 맞춘다.
|
|
86
|
+
const emitRegisterChange = useCallback(
|
|
87
|
+
(nextValue: CalendarValue) => {
|
|
88
|
+
if (!register?.onChange) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const serialized = serializeCalendarValue(nextValue);
|
|
92
|
+
const syntheticEvent = {
|
|
93
|
+
target: {
|
|
94
|
+
name: register.name,
|
|
95
|
+
value: serialized,
|
|
96
|
+
},
|
|
97
|
+
currentTarget: {
|
|
98
|
+
name: register.name,
|
|
99
|
+
value: serialized,
|
|
100
|
+
},
|
|
101
|
+
} as unknown as ChangeEvent<HTMLInputElement>;
|
|
102
|
+
register.onChange(syntheticEvent);
|
|
103
|
+
},
|
|
104
|
+
[register],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const updateValue = useCallback(
|
|
108
|
+
(nextValue: CalendarValue) => {
|
|
109
|
+
setCalendarValue(nextValue);
|
|
110
|
+
emitRegisterChange(nextValue);
|
|
111
|
+
},
|
|
112
|
+
[emitRegisterChange, setCalendarValue],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const triggerValue = useMemo(
|
|
116
|
+
() => formatTriggerValue(calendarValue) ?? "",
|
|
117
|
+
[calendarValue],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const handleTriggerClick = (event: ReactMouseEvent<Element>) => {
|
|
121
|
+
triggerOnClick?.(event);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleCalendarChange = (nextValue: CalendarValue) => {
|
|
125
|
+
updateValue(nextValue);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// footer 미지정 시 기본 FooterTemplate을 연결한다.
|
|
129
|
+
const footerContent = footer ?? (
|
|
130
|
+
<InputDateFooterTemplate
|
|
131
|
+
disabled={disabled}
|
|
132
|
+
onClear={() => updateValue(createEmptyValue())}
|
|
133
|
+
onToday={() => updateValue(getTodayValue())}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const triggerRenderProps: InputCalendarTriggerRenderProps = {
|
|
138
|
+
id,
|
|
139
|
+
name: name ?? register?.name,
|
|
140
|
+
placeholder,
|
|
141
|
+
displayValue: triggerValue,
|
|
142
|
+
disabled,
|
|
143
|
+
onClick: handleTriggerClick,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const triggerNode = trigger ?? renderTrigger?.(triggerRenderProps) ?? (
|
|
147
|
+
<InputDateTrigger
|
|
148
|
+
id={id}
|
|
149
|
+
name={name ?? register?.name}
|
|
150
|
+
register={register}
|
|
151
|
+
placeholder={placeholder}
|
|
152
|
+
displayValue={triggerValue}
|
|
153
|
+
disabled={disabled}
|
|
154
|
+
onClick={handleTriggerClick}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<Calendar.Root
|
|
160
|
+
ref={ref}
|
|
161
|
+
className={clsx("input-date-field", className)}
|
|
162
|
+
mode={mode}
|
|
163
|
+
columns={columns}
|
|
164
|
+
disabled={disabled}
|
|
165
|
+
readOnly={readOnly}
|
|
166
|
+
value={calendarValue}
|
|
167
|
+
onChange={handleCalendarChange}
|
|
168
|
+
datePickerProps={datePickerProps}
|
|
169
|
+
header={header}
|
|
170
|
+
footer={footerContent}
|
|
171
|
+
open={calendarOpened}
|
|
172
|
+
>
|
|
173
|
+
{triggerNode}
|
|
174
|
+
</Calendar.Root>
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
InputDateTemplate.displayName = "InputDateTemplate";
|
|
180
|
+
|
|
181
|
+
export default InputDateTemplate;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { MouseEvent } from "react";
|
|
5
|
+
import { forwardRef } from "react";
|
|
6
|
+
import { Calendar } from "../../../calendar";
|
|
7
|
+
import { InputFoundation } from "../foundation";
|
|
8
|
+
import type { InputCalendarTriggerViewProps } from "../../types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Input Date trigger field.
|
|
12
|
+
* @component
|
|
13
|
+
* @param {InputCalendarTriggerViewProps} props
|
|
14
|
+
* @param {string} [props.className] trigger className
|
|
15
|
+
* @param {string} [props.placeholder] placeholder
|
|
16
|
+
* @param {string} [props.displayValue] 표시 문자열
|
|
17
|
+
* @param {(event: MouseEvent<Element>) => void} [props.onClick] 클릭 핸들러
|
|
18
|
+
* @param {boolean} [props.disabled] disabled 여부
|
|
19
|
+
* @param {string} [props.id] input id
|
|
20
|
+
* @param {string} [props.name] form name
|
|
21
|
+
* @param {UseFormRegisterReturn} [props.register] RHF register
|
|
22
|
+
*/
|
|
23
|
+
const InputDateTrigger = forwardRef<
|
|
24
|
+
HTMLInputElement,
|
|
25
|
+
InputCalendarTriggerViewProps
|
|
26
|
+
>(
|
|
27
|
+
(
|
|
28
|
+
{
|
|
29
|
+
className,
|
|
30
|
+
placeholder,
|
|
31
|
+
displayValue,
|
|
32
|
+
onClick,
|
|
33
|
+
disabled,
|
|
34
|
+
id,
|
|
35
|
+
name,
|
|
36
|
+
register,
|
|
37
|
+
...restProps
|
|
38
|
+
},
|
|
39
|
+
ref,
|
|
40
|
+
) => {
|
|
41
|
+
/**
|
|
42
|
+
* Radix `asChild`가 주입한 onClick을 보존하기 위해 restProps.onClick을 병합한다.
|
|
43
|
+
* (Input Date 자체 onClick 계약도 함께 실행)
|
|
44
|
+
*/
|
|
45
|
+
const handleInputClick = (event: MouseEvent<HTMLInputElement>) => {
|
|
46
|
+
const rawOnClick = (restProps as Record<string, unknown>).onClick;
|
|
47
|
+
if (typeof rawOnClick === "function") {
|
|
48
|
+
(rawOnClick as (event: MouseEvent<HTMLInputElement>) => void)(event);
|
|
49
|
+
}
|
|
50
|
+
onClick?.(event as never);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<InputFoundation.Base
|
|
55
|
+
{...restProps}
|
|
56
|
+
ref={ref}
|
|
57
|
+
value={displayValue}
|
|
58
|
+
readOnly
|
|
59
|
+
disabled={disabled}
|
|
60
|
+
id={id}
|
|
61
|
+
name={name ?? register?.name}
|
|
62
|
+
register={register}
|
|
63
|
+
placeholder={placeholder}
|
|
64
|
+
className={clsx("input-date-trigger-input", className)}
|
|
65
|
+
onClick={handleInputClick}
|
|
66
|
+
right={
|
|
67
|
+
// 토글 인터랙션은 input 단일 경로로 유지하고, 오른쪽 아이콘은 장식 요소로만 둔다.
|
|
68
|
+
<figure className="input-date-trigger-icon" aria-hidden="true">
|
|
69
|
+
<Calendar.Icon.Calendar />
|
|
70
|
+
</figure>
|
|
71
|
+
}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
InputDateTrigger.displayName = "InputDateTrigger";
|
|
78
|
+
|
|
79
|
+
export default InputDateTrigger;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { Button } from "../../../../button";
|
|
3
|
+
import type { InputCalendarApplyButtonProps } from "../../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calendar footer apply 버튼.
|
|
7
|
+
* @component
|
|
8
|
+
* @param {InputCalendarApplyButtonProps} props
|
|
9
|
+
* @param {string} [props.className] 추가 className
|
|
10
|
+
* @param {React.ReactNode} [props.children] override 라벨
|
|
11
|
+
* @param {React.ReactNode} [props.label] fallback 라벨
|
|
12
|
+
* @param {boolean} [props.disabled] disabled 여부
|
|
13
|
+
* @param {() => void} [props.onClick] 클릭 핸들러
|
|
14
|
+
*/
|
|
15
|
+
export default function InputDateApplyButton({
|
|
16
|
+
className,
|
|
17
|
+
label,
|
|
18
|
+
disabled,
|
|
19
|
+
onClick,
|
|
20
|
+
children,
|
|
21
|
+
}: InputCalendarApplyButtonProps) {
|
|
22
|
+
return (
|
|
23
|
+
<Button.Default
|
|
24
|
+
className={clsx("input-date-apply-button", className)}
|
|
25
|
+
priority="primary"
|
|
26
|
+
fill="solid"
|
|
27
|
+
size="large"
|
|
28
|
+
disabled={disabled}
|
|
29
|
+
onClick={onClick}
|
|
30
|
+
>
|
|
31
|
+
{
|
|
32
|
+
children ??
|
|
33
|
+
label ??
|
|
34
|
+
"적용하기" /* children 우선, 없으면 label/기본 라벨 */
|
|
35
|
+
}
|
|
36
|
+
</Button.Default>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { Button } from "../../../../button";
|
|
3
|
+
import type { InputCalendarInlineButtonProps } from "../../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calendar footer clear 버튼.
|
|
7
|
+
* @component
|
|
8
|
+
* @param {InputCalendarInlineButtonProps} props
|
|
9
|
+
* @param {React.ReactNode} [props.children] 라벨
|
|
10
|
+
* @param {() => void} [props.onClick] 클릭 핸들러
|
|
11
|
+
* @param {boolean} [props.disabled] disabled 여부
|
|
12
|
+
* @param {string} [props.className] 확장 className
|
|
13
|
+
*/
|
|
14
|
+
export default function InputDateClearButton({
|
|
15
|
+
children,
|
|
16
|
+
onClick,
|
|
17
|
+
disabled,
|
|
18
|
+
className,
|
|
19
|
+
}: InputCalendarInlineButtonProps) {
|
|
20
|
+
return (
|
|
21
|
+
<Button.Default
|
|
22
|
+
className={clsx(
|
|
23
|
+
"input-date-action-button input-date-action-clear",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
priority="tertiary"
|
|
27
|
+
fill="outlined"
|
|
28
|
+
size="small"
|
|
29
|
+
onClick={onClick}
|
|
30
|
+
disabled={disabled}
|
|
31
|
+
type="button"
|
|
32
|
+
>
|
|
33
|
+
{children ?? "삭제" /* 텍스트 미지정 시 기본 라벨 */}
|
|
34
|
+
</Button.Default>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { Button } from "../../../../button";
|
|
3
|
+
import type { InputCalendarInlineButtonProps } from "../../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calendar footer today 버튼.
|
|
7
|
+
* @component
|
|
8
|
+
* @param {InputCalendarInlineButtonProps} props
|
|
9
|
+
* @param {React.ReactNode} [props.children] 라벨
|
|
10
|
+
* @param {() => void} [props.onClick] 클릭 핸들러
|
|
11
|
+
* @param {boolean} [props.disabled] disabled 여부
|
|
12
|
+
* @param {string} [props.className] className 확장
|
|
13
|
+
*/
|
|
14
|
+
export default function InputDateTodayButton({
|
|
15
|
+
children,
|
|
16
|
+
onClick,
|
|
17
|
+
disabled,
|
|
18
|
+
className,
|
|
19
|
+
}: InputCalendarInlineButtonProps) {
|
|
20
|
+
return (
|
|
21
|
+
<Button.Default
|
|
22
|
+
className={clsx(
|
|
23
|
+
"input-date-action-button input-date-action-today",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
priority="secondary"
|
|
27
|
+
fill="solid"
|
|
28
|
+
size="small"
|
|
29
|
+
onClick={onClick}
|
|
30
|
+
disabled={disabled}
|
|
31
|
+
type="button"
|
|
32
|
+
>
|
|
33
|
+
{children ?? "오늘" /* default 라벨 */}
|
|
34
|
+
</Button.Default>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import type { InputCalendarFooterTemplateContainerProps } from "../../../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* FooterTemplate root container.
|
|
6
|
+
* @component
|
|
7
|
+
* @param {InputCalendarFooterTemplateContainerProps} props
|
|
8
|
+
* @param {React.ReactNode} [props.children] footer 내부 children
|
|
9
|
+
* @param {string} [props.className] container className
|
|
10
|
+
* @example
|
|
11
|
+
* <InputDateFooterTemplateContainer>
|
|
12
|
+
* <div>...</div>
|
|
13
|
+
* </InputDateFooterTemplateContainer>
|
|
14
|
+
*/
|
|
15
|
+
export default function InputDateFooterTemplateContainer({
|
|
16
|
+
children,
|
|
17
|
+
className,
|
|
18
|
+
}: InputCalendarFooterTemplateContainerProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div className={clsx("input-date-footer-template", className)}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { InputCalendarFooterTemplateProps } from "../../../types";
|
|
2
|
+
import InputDateApplyButton from "../button/ApplyButton";
|
|
3
|
+
import InputDateClearButton from "../button/ClearButton";
|
|
4
|
+
import InputDateTodayButton from "../button/TodayButton";
|
|
5
|
+
import InputDateFooterTemplateContainer from "./Container";
|
|
6
|
+
import InputDateFooterUtilContainer from "./UtilContainer";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Calendar footer 기본 템플릿.
|
|
10
|
+
* @component
|
|
11
|
+
* @param {InputCalendarFooterTemplateProps} props
|
|
12
|
+
* @param {string} [props.className] root className
|
|
13
|
+
* @param {() => void} [props.onClear] clear 버튼 클릭 핸들러
|
|
14
|
+
* @param {() => void} [props.onToday] today 버튼 클릭 핸들러
|
|
15
|
+
* @param {() => void} [props.onApply] apply 버튼 클릭 핸들러
|
|
16
|
+
* @param {boolean} [props.disabled] 전체 버튼 disabled 여부
|
|
17
|
+
* @example
|
|
18
|
+
* <InputDateFooterTemplate onClear={onClear} onToday={onToday} onApply={onApply} />
|
|
19
|
+
*/
|
|
20
|
+
export default function InputDateFooterTemplate({
|
|
21
|
+
className,
|
|
22
|
+
onClear,
|
|
23
|
+
onToday,
|
|
24
|
+
onApply,
|
|
25
|
+
disabled,
|
|
26
|
+
}: InputCalendarFooterTemplateProps) {
|
|
27
|
+
return (
|
|
28
|
+
<InputDateFooterTemplateContainer className={className}>
|
|
29
|
+
<InputDateFooterUtilContainer>
|
|
30
|
+
<InputDateClearButton disabled={disabled} onClick={onClear} />
|
|
31
|
+
<InputDateTodayButton disabled={disabled} onClick={onToday} />
|
|
32
|
+
</InputDateFooterUtilContainer>
|
|
33
|
+
<InputDateApplyButton disabled={disabled} onClick={onApply} />
|
|
34
|
+
</InputDateFooterTemplateContainer>
|
|
35
|
+
);
|
|
36
|
+
}
|