@uniai-fe/uds-primitives 0.2.1 → 0.2.3
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 +391 -81
- package/package.json +17 -8
- package/src/components/button/index.tsx +0 -2
- package/src/components/button/markup/Base.tsx +22 -1
- package/src/components/button/styles/button.scss +24 -2
- package/src/components/button/styles/variables.scss +4 -0
- package/src/components/button/types/index.ts +7 -0
- 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/checkbox/markup/Checkbox.tsx +31 -25
- package/src/components/dropdown/markup/index.tsx +10 -1
- package/src/components/input/hooks/index.ts +1 -0
- package/src/components/input/hooks/useAddress.ts +247 -0
- package/src/components/input/index.scss +5 -1
- package/src/components/input/markup/address/Button.tsx +65 -0
- package/src/components/input/markup/address/Template.tsx +135 -0
- package/src/components/input/markup/address/index.ts +9 -0
- 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/foundation/Input.tsx +20 -1
- package/src/components/input/markup/index.tsx +4 -4
- package/src/components/input/styles/address.scss +24 -0
- package/src/components/input/styles/date.scss +45 -0
- package/src/components/input/styles/foundation.scss +28 -2
- package/src/components/input/styles/variables.scss +4 -0
- package/src/components/input/types/address.ts +249 -0
- package/src/components/input/types/date.ts +366 -0
- package/src/components/input/types/foundation.ts +6 -0
- package/src/components/input/types/index.ts +2 -1
- package/src/components/input/utils/address.ts +165 -0
- package/src/components/input/utils/date.ts +61 -0
- package/src/components/input/utils/index.tsx +2 -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/components/radio/markup/Radio.tsx +10 -2
- package/src/components/radio/markup/RadioCard.tsx +6 -1
- package/src/components/radio/markup/RadioCardGroup.tsx +6 -1
- package/src/components/select/markup/Default.tsx +2 -0
- package/src/components/select/markup/foundation/Container.tsx +23 -0
- package/src/components/select/markup/multiple/Multiple.tsx +2 -0
- package/src/components/select/styles/select.scss +25 -2
- package/src/components/select/styles/variables.scss +4 -0
- package/src/components/select/types/props.ts +24 -5
- 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/styles/index.scss +0 -4
- 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
|
@@ -150,7 +150,8 @@ $button-priorities: (
|
|
|
150
150
|
align-items: center;
|
|
151
151
|
justify-content: center;
|
|
152
152
|
gap: var(--button-default-gap-medium, var(--spacing-gap-2, 8px));
|
|
153
|
-
width:
|
|
153
|
+
width: var(--button-width);
|
|
154
|
+
flex: var(--button-flex);
|
|
154
155
|
min-width: var(
|
|
155
156
|
--button-default-width-min-base,
|
|
156
157
|
var(--theme-size-small-2, 24px)
|
|
@@ -216,8 +217,29 @@ $button-priorities: (
|
|
|
216
217
|
justify-content: center;
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
&[data-width="auto"] {
|
|
221
|
+
--button-width: auto;
|
|
222
|
+
--button-flex: 0 1 auto;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
&[data-width="fill"] {
|
|
226
|
+
--button-width: auto;
|
|
227
|
+
--button-flex: 1 1 0%;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&[data-width="full"],
|
|
219
231
|
&.button-block {
|
|
220
|
-
width: 100%;
|
|
232
|
+
--button-width: 100%;
|
|
233
|
+
--button-flex: 0 0 100%;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
&[data-width="fit"] {
|
|
237
|
+
--button-width: fit-content;
|
|
238
|
+
--button-flex: 0 0 auto;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
&[data-width="custom"] {
|
|
242
|
+
--button-flex: 0 0 auto;
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
&:not(.button-fill-solid):not(.button-fill-outlined) {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/* 버튼 전용 토큰은 theme root에서 한 번만 정의하며, size 기반 규칙(--button-{type}-{property}-{size})을 따른다. */
|
|
2
2
|
:root {
|
|
3
|
+
/* layout presets */
|
|
4
|
+
--button-width: fit-content;
|
|
5
|
+
--button-flex: 0 0 auto;
|
|
6
|
+
|
|
3
7
|
/* default button spacing (size 기반) */
|
|
4
8
|
--button-default-gap-small: var(--spacing-gap-1);
|
|
5
9
|
--button-default-gap-medium: var(--spacing-gap-2);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
|
|
2
|
+
import type { FormFieldWidth } from "../../form/types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Button; priority 옵션
|
|
@@ -91,6 +92,7 @@ type SharedElementProps = Omit<NativeButtonProps, "children"> &
|
|
|
91
92
|
* @property {ButtonPriority} priority semantic color priority.
|
|
92
93
|
* @property {ButtonState} [state] UI 상태. disabled prop과 조합된다.
|
|
93
94
|
* @property {boolean} [block] width:100% 확장 여부.
|
|
95
|
+
* @property {FormFieldWidth} [width] width preset. block보다 우선 적용된다.
|
|
94
96
|
* @property {boolean} [loading] true면 readonly 처리 + aria-busy.
|
|
95
97
|
* @property {ReactNode} [left] 라벨 왼쪽 커스텀 슬롯.
|
|
96
98
|
* @property {ReactNode} [right] 라벨 오른쪽 커스텀 슬롯.
|
|
@@ -145,6 +147,11 @@ export interface ButtonProps extends SharedElementProps {
|
|
|
145
147
|
* true면 버튼 폭을 100%로 확장한다.
|
|
146
148
|
*/
|
|
147
149
|
block?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* width preset.
|
|
152
|
+
* block보다 우선 적용된다.
|
|
153
|
+
*/
|
|
154
|
+
width?: FormFieldWidth;
|
|
148
155
|
/**
|
|
149
156
|
* true면 readonly 상태로 전환하고 aria-busy를 설정한다.
|
|
150
157
|
*/
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
<rect x="20.2" y="6.8" width="13.4" height="16.4" rx="1.2" transform="rotate(90 20.2 6.8)" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
3
3
|
<path d="M4 10C9.33333 10 20 10 20 10" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
4
4
|
<path d="M7 13L9 13" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
5
|
+
<path d="M11 13L13 13" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
6
|
+
<path d="M15 13L17 13" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
7
|
+
<path d="M7 17L9 17" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
8
|
+
<path d="M11 17L13 17" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
9
|
+
<path d="M15 17L17 17" stroke="#BCBEC2" stroke-width="1.6"/>
|
|
5
10
|
<path d="M8.8 4C8.8 3.55817 8.44183 3.2 8 3.2C7.55817 3.2 7.2 3.55817 7.2 4L8 4L8.8 4ZM8 4L7.2 4L7.2 6L8 6L8.8 6L8.8 4L8 4Z" fill="#BCBEC2"/>
|
|
6
11
|
<path d="M16.8 4C16.8 3.55817 16.4418 3.2 16 3.2C15.5582 3.2 15.2 3.55817 15.2 4L16 4L16.8 4ZM16 4L15.2 4L15.2 6L16 6L16.8 6L16.8 4L16 4Z" fill="#BCBEC2"/>
|
|
7
12
|
</svg>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { DatePicker } from "@mantine/dates";
|
|
4
|
+
import { CalendarIcon } from "./Icon";
|
|
5
|
+
import type { CalendarGridProps } from "../types";
|
|
6
|
+
import { mapValueToPicker, parseValueFromPicker } from "../utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Calendar Grid; Mantine DatePicker 래퍼.
|
|
10
|
+
* @component
|
|
11
|
+
* @param {CalendarGridProps} props grid props
|
|
12
|
+
* @param {CalendarValue} props.value 현재 선택 값(YYYY-MM-DD)
|
|
13
|
+
* @param {CalendarOnChange} props.onChange 값 변경 핸들러
|
|
14
|
+
* @param {CalendarDatePickerProps} [props.datePickerProps] Mantine DatePicker 옵션
|
|
15
|
+
* @example
|
|
16
|
+
* <Calendar.Grid value={value} onChange={setValue} />
|
|
17
|
+
*/
|
|
18
|
+
export default function CalendarCore({
|
|
19
|
+
value,
|
|
20
|
+
onChange,
|
|
21
|
+
datePickerProps,
|
|
22
|
+
}: CalendarGridProps) {
|
|
23
|
+
// 기본 DatePicker 옵션/스타일 책임을 Calendar.Core에 고정한다.
|
|
24
|
+
const resolvedDatePickerProps = {
|
|
25
|
+
size: "sm" as const,
|
|
26
|
+
firstDayOfWeek: 0 as const,
|
|
27
|
+
weekendDays: [] as Array<0 | 1 | 2 | 3 | 4 | 5 | 6>,
|
|
28
|
+
nextIcon: <CalendarIcon.Chevron.right />,
|
|
29
|
+
previousIcon: <CalendarIcon.Chevron.left />,
|
|
30
|
+
...datePickerProps,
|
|
31
|
+
classNames: {
|
|
32
|
+
levelsGroup: "calendar-month-level",
|
|
33
|
+
month: "calendar-month-table",
|
|
34
|
+
monthCell: "calendar-month-cell",
|
|
35
|
+
calendarHeader: "calendar-header-row",
|
|
36
|
+
calendarHeaderControl: "calendar-header-control",
|
|
37
|
+
calendarHeaderLevel: "calendar-header-level",
|
|
38
|
+
day: "calendar-day",
|
|
39
|
+
weekday: "calendar-weekday",
|
|
40
|
+
weekdaysRow: "calendar-weekdays",
|
|
41
|
+
monthsList: "calendar-months-list",
|
|
42
|
+
monthsListCell: "calendar-months-list-cell",
|
|
43
|
+
monthsListControl: "calendar-months-list-control",
|
|
44
|
+
yearsList: "calendar-years-list",
|
|
45
|
+
yearsListCell: "calendar-years-list-cell",
|
|
46
|
+
yearsListControl: "calendar-years-list-control",
|
|
47
|
+
...(datePickerProps?.classNames ?? {}),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleChange = (nextValue: unknown) => {
|
|
52
|
+
const parsed = parseValueFromPicker(nextValue as Date | null);
|
|
53
|
+
onChange(parsed);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<DatePicker
|
|
58
|
+
className="calendar-grid"
|
|
59
|
+
// Core는 SOT 단일 달력으로 고정한다.
|
|
60
|
+
numberOfColumns={1}
|
|
61
|
+
type="default"
|
|
62
|
+
value={mapValueToPicker(value) as never}
|
|
63
|
+
onChange={handleChange as never}
|
|
64
|
+
{...resolvedDatePickerProps}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import CalendarInputIcon from "../img/calendar.svg";
|
|
2
|
+
import ChevronDownIcon from "../img/chevron-down.svg";
|
|
3
|
+
import ChevronLeftIcon from "../img/chevron-left.svg";
|
|
4
|
+
import ChevronRightIcon from "../img/chevron-right.svg";
|
|
5
|
+
import ChevronUpIcon from "../img/chevron-up.svg";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calendar; 아이콘
|
|
9
|
+
* - Calendar
|
|
10
|
+
* - Chevron (left, right, up, down)
|
|
11
|
+
*/
|
|
12
|
+
export const CalendarIcon = {
|
|
13
|
+
Calendar: CalendarInputIcon,
|
|
14
|
+
Chevron: {
|
|
15
|
+
left: ChevronLeftIcon,
|
|
16
|
+
right: ChevronRightIcon,
|
|
17
|
+
up: ChevronUpIcon,
|
|
18
|
+
down: ChevronDownIcon,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { PopOver } from "../../pop-over";
|
|
6
|
+
import type { CalendarRootProps } from "../types";
|
|
7
|
+
import CalendarContainer from "./layout/Container";
|
|
8
|
+
import CalendarCore from "./Core";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Calendar Root; Header/Body/Footer + Grid 조합 템플릿.
|
|
12
|
+
* @component
|
|
13
|
+
* @param {CalendarRootProps} props root props
|
|
14
|
+
* @param {string} [props.className] Trigger element className override
|
|
15
|
+
* @param {CalendarValue} props.value 현재 선택 값(YYYY-MM-DD)
|
|
16
|
+
* @param {CalendarOnChange} props.onChange 값 변경 핸들러
|
|
17
|
+
* @param {CalendarColumns} [props.columns=1] Root 레이아웃 컬럼 상태
|
|
18
|
+
* @param {CalendarDatePickerProps} [props.datePickerProps] Mantine DatePicker 옵션
|
|
19
|
+
* @param {React.ReactNode} [props.header] Calendar Header 슬롯
|
|
20
|
+
* @param {React.ReactNode} [props.footer] Calendar Footer 슬롯
|
|
21
|
+
* @param {CalendarMode} [props.mode="date"] calendar 모드
|
|
22
|
+
* @param {boolean} [props.disabled] 비활성화 상태
|
|
23
|
+
* @param {boolean} [props.readOnly] 읽기 전용 상태
|
|
24
|
+
* @param {React.ReactNode} props.children Trigger 슬롯(children)
|
|
25
|
+
* @param {boolean} [props.open] 제어형 open 상태
|
|
26
|
+
* @param {boolean} [props.defaultOpen] 비제어 초기 open 상태
|
|
27
|
+
* @param {(open: boolean) => void} [props.onOpenChange] open 상태 변경 핸들러
|
|
28
|
+
* @param {"top" | "right" | "bottom" | "left"} [props.side="bottom"] Content 배치 방향
|
|
29
|
+
* @param {"start" | "center" | "end"} [props.align="start"] Content 정렬 기준
|
|
30
|
+
* @param {number} [props.sideOffset=4] Trigger와 Content 간격
|
|
31
|
+
* @param {number} [props.alignOffset] Content 정렬 보정값
|
|
32
|
+
* @param {boolean} [props.withPortal=true] Portal 사용 여부
|
|
33
|
+
* @param {HTMLElement | null} [props.portalContainer] Portal 컨테이너
|
|
34
|
+
* @example
|
|
35
|
+
* <Calendar.Root value={value} onChange={setValue} />
|
|
36
|
+
*/
|
|
37
|
+
const CalendarRoot = forwardRef<HTMLElement, CalendarRootProps>(
|
|
38
|
+
(
|
|
39
|
+
{
|
|
40
|
+
className,
|
|
41
|
+
value,
|
|
42
|
+
onChange,
|
|
43
|
+
columns = 1,
|
|
44
|
+
datePickerProps,
|
|
45
|
+
header,
|
|
46
|
+
footer,
|
|
47
|
+
mode = "date",
|
|
48
|
+
disabled,
|
|
49
|
+
readOnly,
|
|
50
|
+
children,
|
|
51
|
+
open,
|
|
52
|
+
defaultOpen,
|
|
53
|
+
onOpenChange,
|
|
54
|
+
side = "bottom",
|
|
55
|
+
align = "start",
|
|
56
|
+
sideOffset = 4,
|
|
57
|
+
alignOffset,
|
|
58
|
+
withPortal = true,
|
|
59
|
+
portalContainer,
|
|
60
|
+
},
|
|
61
|
+
ref,
|
|
62
|
+
) => {
|
|
63
|
+
// disabled/readOnly 상태에서는 PopOver 토글을 막는다.
|
|
64
|
+
const isInteractive = !disabled && !readOnly;
|
|
65
|
+
|
|
66
|
+
const calendarNode = (
|
|
67
|
+
<CalendarContainer
|
|
68
|
+
ref={ref}
|
|
69
|
+
header={header}
|
|
70
|
+
footer={footer}
|
|
71
|
+
mode={mode}
|
|
72
|
+
columns={columns}
|
|
73
|
+
disabled={disabled}
|
|
74
|
+
readOnly={readOnly}
|
|
75
|
+
body={
|
|
76
|
+
<CalendarCore
|
|
77
|
+
value={value}
|
|
78
|
+
onChange={onChange}
|
|
79
|
+
datePickerProps={datePickerProps}
|
|
80
|
+
/>
|
|
81
|
+
}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<PopOver.Root
|
|
87
|
+
open={isInteractive ? open : false}
|
|
88
|
+
defaultOpen={isInteractive ? defaultOpen : false}
|
|
89
|
+
onOpenChange={nextOpen => {
|
|
90
|
+
if (!isInteractive) {
|
|
91
|
+
onOpenChange?.(false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
onOpenChange?.(nextOpen);
|
|
95
|
+
}}
|
|
96
|
+
modal={false}
|
|
97
|
+
>
|
|
98
|
+
{/* Trigger 래퍼 div를 두지 않고 children 노드에 직접 trigger props를 주입한다. */}
|
|
99
|
+
<PopOver.Trigger
|
|
100
|
+
asChild
|
|
101
|
+
disabled={!isInteractive}
|
|
102
|
+
className={clsx("calendar-trigger", className)}
|
|
103
|
+
>
|
|
104
|
+
{/* children은 asChild 계약상 단일 element여야 하며, 해당 요소가 props를 DOM으로 전달해야 한다. */}
|
|
105
|
+
{children}
|
|
106
|
+
</PopOver.Trigger>
|
|
107
|
+
<PopOver.Content
|
|
108
|
+
className="calendar-pop-over-content"
|
|
109
|
+
side={side}
|
|
110
|
+
align={align}
|
|
111
|
+
sideOffset={sideOffset}
|
|
112
|
+
alignOffset={alignOffset}
|
|
113
|
+
withPortal={withPortal}
|
|
114
|
+
portalContainer={portalContainer}
|
|
115
|
+
>
|
|
116
|
+
{/* calendar 스킨 class는 content 내부에 고정한다. */}
|
|
117
|
+
<div className="calendar-root">{calendarNode}</div>
|
|
118
|
+
</PopOver.Content>
|
|
119
|
+
</PopOver.Root>
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
CalendarRoot.displayName = "CalendarRoot";
|
|
125
|
+
|
|
126
|
+
export default CalendarRoot;
|
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
import CalendarContainer from "./layout/Container";
|
|
2
|
+
import CalendarHeader from "./layout/Header";
|
|
3
|
+
import CalendarBody from "./layout/Body";
|
|
4
|
+
import CalendarFooter from "./layout/Footer";
|
|
5
|
+
import CalendarCore from "./Core";
|
|
6
|
+
import CalendarRoot from "./Root";
|
|
7
|
+
import { CalendarIcon } from "./Icon";
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
|
-
*
|
|
10
|
+
* Calendar namespace.
|
|
11
|
+
* - Root
|
|
12
|
+
* - Container
|
|
13
|
+
* - Header
|
|
14
|
+
* - Body
|
|
15
|
+
* - Footer
|
|
16
|
+
* - Core
|
|
3
17
|
*/
|
|
4
|
-
export {
|
|
18
|
+
export const Calendar = {
|
|
19
|
+
Root: CalendarRoot,
|
|
20
|
+
Container: CalendarContainer,
|
|
21
|
+
Header: CalendarHeader,
|
|
22
|
+
Body: CalendarBody,
|
|
23
|
+
Footer: CalendarFooter,
|
|
24
|
+
Core: CalendarCore,
|
|
25
|
+
Icon: CalendarIcon,
|
|
26
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CalendarSectionProps } from "../../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calendar Body.
|
|
5
|
+
* @component
|
|
6
|
+
* @param {CalendarSectionProps} props body children
|
|
7
|
+
* @example
|
|
8
|
+
* <Calendar.Body>{grid}</Calendar.Body>
|
|
9
|
+
*/
|
|
10
|
+
export default function CalendarBody({ children }: CalendarSectionProps) {
|
|
11
|
+
return <div className="calendar-body">{children}</div>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import type { CalendarContainerProps } from "../../types";
|
|
3
|
+
import CalendarBody from "./Body";
|
|
4
|
+
import CalendarFooter from "./Footer";
|
|
5
|
+
import CalendarHeader from "./Header";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calendar Layout Container.
|
|
9
|
+
* @component
|
|
10
|
+
* @param {CalendarContainerProps} props layout props
|
|
11
|
+
* @param {React.ReactNode} [props.header] 상단 header 콘텐츠
|
|
12
|
+
* @param {React.ReactNode} props.body 본문 콘텐츠(Calendar Core 포함)
|
|
13
|
+
* @param {React.ReactNode} [props.footer] 하단 footer 콘텐츠
|
|
14
|
+
* @param {CalendarMode} [props.mode="date"] calendar 모드
|
|
15
|
+
* @param {CalendarColumns} [props.columns=1] 동시 렌더링 컬럼 수
|
|
16
|
+
* @param {boolean} [props.disabled] 비활성화 상태
|
|
17
|
+
* @param {boolean} [props.readOnly] 읽기 전용 상태
|
|
18
|
+
* @example
|
|
19
|
+
* <Calendar.Container body={<Calendar.Grid value={value} onChange={setValue} />} />
|
|
20
|
+
*/
|
|
21
|
+
const CalendarContainer = forwardRef<HTMLElement, CalendarContainerProps>(
|
|
22
|
+
(
|
|
23
|
+
{ header, body, footer, mode = "date", columns = 1, disabled, readOnly },
|
|
24
|
+
ref,
|
|
25
|
+
) => (
|
|
26
|
+
<section
|
|
27
|
+
ref={ref}
|
|
28
|
+
className="calendar-container"
|
|
29
|
+
data-mode={mode}
|
|
30
|
+
data-columns={columns}
|
|
31
|
+
data-disabled={disabled ? "true" : undefined}
|
|
32
|
+
data-readonly={readOnly ? "true" : undefined}
|
|
33
|
+
>
|
|
34
|
+
{header ? <CalendarHeader>{header}</CalendarHeader> : null}
|
|
35
|
+
<CalendarBody>{body}</CalendarBody>
|
|
36
|
+
{footer ? <CalendarFooter>{footer}</CalendarFooter> : null}
|
|
37
|
+
</section>
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CalendarContainer.displayName = "CalendarContainer";
|
|
42
|
+
|
|
43
|
+
export default CalendarContainer;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CalendarSectionProps } from "../../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calendar Footer.
|
|
5
|
+
* @component
|
|
6
|
+
* @param {CalendarSectionProps} props footer children
|
|
7
|
+
* @example
|
|
8
|
+
* <Calendar.Footer>{footerNode}</Calendar.Footer>
|
|
9
|
+
*/
|
|
10
|
+
export default function CalendarFooter({ children }: CalendarSectionProps) {
|
|
11
|
+
return <footer className="calendar-footer">{children}</footer>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CalendarSectionProps } from "../../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calendar Header.
|
|
5
|
+
* @component
|
|
6
|
+
* @param {CalendarSectionProps} props header children
|
|
7
|
+
* @example
|
|
8
|
+
* <Calendar.Header>Custom Header</Calendar.Header>
|
|
9
|
+
*/
|
|
10
|
+
export default function CalendarHeader({ children }: CalendarSectionProps) {
|
|
11
|
+
return <header className="calendar-header">{children}</header>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.calendar-root {
|
|
2
|
+
width: fit-content;
|
|
3
|
+
max-width: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.calendar-trigger {
|
|
7
|
+
width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.calendar-container {
|
|
11
|
+
display: grid;
|
|
12
|
+
gap: var(--spacing-gap-3);
|
|
13
|
+
width: fit-content;
|
|
14
|
+
max-width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.calendar-body,
|
|
18
|
+
.calendar-grid {
|
|
19
|
+
width: fit-content;
|
|
20
|
+
max-width: 100%;
|
|
21
|
+
}
|