@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.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/dist/styles.css +299 -81
  3. package/package.json +16 -9
  4. package/src/components/{input/img/calendar → calendar/img}/calendar.svg +5 -0
  5. package/src/components/calendar/index.tsx +5 -3
  6. package/src/components/calendar/markup/Core.tsx +67 -0
  7. package/src/components/calendar/markup/Icon.tsx +20 -0
  8. package/src/components/calendar/markup/Root.tsx +126 -0
  9. package/src/components/calendar/markup/index.tsx +24 -2
  10. package/src/components/calendar/markup/layout/Body.tsx +12 -0
  11. package/src/components/calendar/markup/layout/Container.tsx +43 -0
  12. package/src/components/calendar/markup/layout/Footer.tsx +12 -0
  13. package/src/components/calendar/markup/layout/Header.tsx +12 -0
  14. package/src/components/calendar/styles/index.scss +2 -0
  15. package/src/components/calendar/styles/layout.scss +21 -0
  16. package/src/components/calendar/styles/mantine-calendar.scss +240 -0
  17. package/src/components/calendar/types/calendar.ts +208 -0
  18. package/src/components/calendar/types/index.ts +1 -4
  19. package/src/components/calendar/utils/index.ts +1 -4
  20. package/src/components/calendar/utils/value-mapper.ts +24 -0
  21. package/src/components/input/index.scss +1 -1
  22. package/src/components/input/markup/date/Template.tsx +181 -0
  23. package/src/components/input/markup/date/Trigger.tsx +79 -0
  24. package/src/components/input/markup/date/button/ApplyButton.tsx +38 -0
  25. package/src/components/input/markup/date/button/ClearButton.tsx +36 -0
  26. package/src/components/input/markup/date/button/TodayButton.tsx +36 -0
  27. package/src/components/input/markup/date/footer/Container.tsx +24 -0
  28. package/src/components/input/markup/date/footer/Template.tsx +36 -0
  29. package/src/components/input/markup/date/footer/UtilContainer.tsx +23 -0
  30. package/src/components/input/markup/date/footer/index.ts +3 -0
  31. package/src/components/input/markup/date/index.tsx +27 -0
  32. package/src/components/input/markup/index.tsx +2 -4
  33. package/src/components/input/styles/date.scss +45 -0
  34. package/src/components/input/types/date.ts +286 -0
  35. package/src/components/input/types/index.ts +1 -1
  36. package/src/components/input/utils/address.ts +2 -2
  37. package/src/components/input/utils/date.ts +61 -0
  38. package/src/components/input/utils/index.tsx +1 -0
  39. package/src/components/pop-over/index.scss +1 -0
  40. package/src/components/pop-over/index.tsx +4 -0
  41. package/src/components/pop-over/markup/Content.tsx +77 -0
  42. package/src/components/pop-over/markup/Root.tsx +28 -0
  43. package/src/components/pop-over/markup/Trigger.tsx +26 -0
  44. package/src/components/pop-over/markup/index.tsx +17 -0
  45. package/src/components/pop-over/styles/base.scss +5 -0
  46. package/src/components/pop-over/styles/content.scss +24 -0
  47. package/src/components/pop-over/styles/index.scss +2 -0
  48. package/src/components/pop-over/types/index.ts +1 -0
  49. package/src/components/pop-over/types/pop-over.ts +86 -0
  50. package/src/index.scss +1 -0
  51. package/src/index.tsx +3 -1
  52. package/src/init/mantine.css +5 -0
  53. package/src/init/mantine.ts +2 -0
  54. package/src/components/input/markup/calendar/Base.tsx +0 -329
  55. package/src/components/input/markup/calendar/index.tsx +0 -8
  56. package/src/components/input/styles/calendar.scss +0 -110
  57. package/src/components/input/types/calendar.ts +0 -208
  58. /package/src/components/{input/img/calendar → calendar/img}/chevron-down.svg +0 -0
  59. /package/src/components/{input/img/calendar → calendar/img}/chevron-left.svg +0 -0
  60. /package/src/components/{input/img/calendar → calendar/img}/chevron-right.svg +0 -0
  61. /package/src/components/{input/img/calendar → calendar/img}/chevron-up.svg +0 -0
@@ -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
- * TODO(calendar): SOT 및 사용자 제약에 따라 컴포넌트를 구현한다.
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,2 @@
1
+ @use "./layout.scss";
2
+ @use "./mantine-calendar.scss";
@@ -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
+ }
@@ -0,0 +1,240 @@
1
+ .calendar-root,
2
+ .calendar-grid {
3
+ // Calendar 기준 폭/패딩/컬럼 간격을 모듈 내부에서 고정한다.
4
+ --calendar-width: 375px;
5
+ --calendar-inline-padding: var(--spacing-padding-6);
6
+ --calendar-body-width: calc(
7
+ var(--calendar-width) - (var(--calendar-inline-padding) * 2)
8
+ );
9
+ }
10
+
11
+ .calendar-root {
12
+ // Calendar 내부 레이아웃 스킨(표면 스타일은 PopOver.Content가 담당).
13
+ padding: var(--spacing-padding-10) var(--spacing-padding-6)
14
+ var(--spacing-padding-6);
15
+ display: grid;
16
+ gap: var(--spacing-gap-3);
17
+ width: var(--calendar-width);
18
+ min-width: var(--calendar-width);
19
+ max-width: min(100vw - (var(--spacing-padding-5) * 2), 420px);
20
+ box-sizing: border-box;
21
+ z-index: 30;
22
+ }
23
+
24
+ .calendar-header {
25
+ margin-bottom: var(--spacing-gap-2);
26
+ }
27
+
28
+ .calendar-body {
29
+ display: grid;
30
+ gap: var(--spacing-gap-5);
31
+ }
32
+
33
+ .calendar-grid {
34
+ width: var(--calendar-body-width);
35
+ }
36
+
37
+ .calendar-grid table {
38
+ width: auto;
39
+ }
40
+
41
+ .calendar-month-level {
42
+ width: 100%;
43
+ }
44
+
45
+ .calendar-month-level > [data-month-level="true"],
46
+ .calendar-month-level > [data-year-level="true"],
47
+ .calendar-month-level > [data-decade-level="true"] {
48
+ width: 100%;
49
+ }
50
+
51
+ .calendar-header-row {
52
+ display: grid;
53
+ grid-template-columns: 44px 1fr 44px;
54
+ align-items: center;
55
+ width: 100%;
56
+ max-width: none;
57
+ column-gap: var(--spacing-gap-5);
58
+ padding: 0 var(--spacing-padding-9);
59
+ margin-bottom: var(--spacing-gap-5);
60
+ --dch-fz: var(--font-heading-small-size) !important;
61
+ }
62
+
63
+ .calendar-header-control {
64
+ width: 44px;
65
+ height: 44px;
66
+ border-radius: 999px;
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ color: var(--color-label-alternative);
71
+ transition:
72
+ background-color 0.2s ease,
73
+ color 0.2s ease;
74
+ }
75
+
76
+ .calendar-header-control[data-direction="previous"] {
77
+ justify-self: start;
78
+ }
79
+
80
+ .calendar-header-control[data-direction="next"] {
81
+ justify-self: end;
82
+ }
83
+
84
+ .calendar-header-level {
85
+ justify-self: center;
86
+ font-size: var(--font-heading-small-size);
87
+ font-weight: var(--font-heading-small-weight);
88
+ text-align: center;
89
+ white-space: nowrap;
90
+ writing-mode: horizontal-tb;
91
+ letter-spacing: 0.2px;
92
+ /* Month/Year 헤더 텍스트는 디자인 기준에 맞춰 strong 컬러를 사용한다. */
93
+ color: var(--color-label-strong);
94
+ }
95
+
96
+ .calendar-header-control:where(:not([data-disabled="true"])):hover {
97
+ background-color: var(--color-tertiary-default);
98
+ color: var(--color-label-standard);
99
+ }
100
+
101
+ .calendar-month-table {
102
+ width: 100%;
103
+ max-width: 100%;
104
+ margin: 0 auto;
105
+ table-layout: fixed;
106
+ border-collapse: collapse;
107
+ border-spacing: 0;
108
+ }
109
+
110
+ .calendar-month-cell {
111
+ width: 46px;
112
+ padding: 1px;
113
+ }
114
+
115
+ .calendar-months-list {
116
+ width: 100%;
117
+ max-width: 100%;
118
+ margin: 0 auto;
119
+ table-layout: fixed;
120
+ border-collapse: separate;
121
+ border-spacing: var(--spacing-gap-3);
122
+ }
123
+
124
+ .calendar-months-list-cell {
125
+ padding: 0;
126
+ }
127
+
128
+ .calendar-months-list-control {
129
+ width: 100%;
130
+ min-width: 0;
131
+ height: 44px;
132
+ padding: 0 var(--spacing-padding-2);
133
+ border: none;
134
+ border-radius: var(--theme-radius-medium-3);
135
+ font-size: var(--font-body-medium-size);
136
+ font-weight: var(--font-body-medium-weight);
137
+ line-height: 1.5;
138
+ color: var(--color-label-standard);
139
+ text-transform: none;
140
+ }
141
+
142
+ .calendar-months-list-control[data-selected="true"] {
143
+ background-color: var(--color-primary-default);
144
+ color: var(--color-common-100);
145
+ }
146
+
147
+ .calendar-months-list-control:where(
148
+ :not([data-disabled="true"], [data-selected="true"])
149
+ ):hover {
150
+ background-color: var(--color-secondary-default);
151
+ }
152
+
153
+ .calendar-years-list {
154
+ width: 100%;
155
+ max-width: 100%;
156
+ margin: 0 auto;
157
+ table-layout: fixed;
158
+ border-collapse: separate;
159
+ border-spacing: var(--spacing-gap-3);
160
+ }
161
+
162
+ .calendar-years-list-cell {
163
+ padding: 0;
164
+ }
165
+
166
+ .calendar-years-list-control {
167
+ width: 100%;
168
+ min-width: 0;
169
+ height: 44px;
170
+ padding: 0 var(--spacing-padding-2);
171
+ border: none;
172
+ border-radius: var(--theme-radius-medium-3);
173
+ font-size: var(--font-body-medium-size);
174
+ font-weight: var(--font-body-medium-weight);
175
+ line-height: 1.5;
176
+ color: var(--color-label-standard);
177
+ text-transform: none;
178
+ }
179
+
180
+ .calendar-years-list-control[data-selected="true"] {
181
+ background-color: var(--color-primary-default);
182
+ color: var(--color-common-100);
183
+ }
184
+
185
+ .calendar-years-list-control:where(
186
+ :not([data-disabled="true"], [data-selected="true"])
187
+ ):hover {
188
+ background-color: var(--color-secondary-default);
189
+ }
190
+
191
+ .calendar-weekdays {
192
+ text-transform: none;
193
+ --wr-fz: var(--font-heading-xxsmall-size) !important;
194
+ --wr-spacing: 10px !important;
195
+ }
196
+
197
+ .calendar-weekday {
198
+ text-transform: none;
199
+ width: 46px;
200
+ min-width: 46px;
201
+ height: 30px;
202
+ padding-bottom: 5px;
203
+ font-size: var(--font-heading-xxsmall-size);
204
+ font-weight: var(--font-heading-xxsmall-weight);
205
+ line-height: 1.5;
206
+ letter-spacing: 0.2px;
207
+ color: var(--color-label-alternative);
208
+ text-align: center;
209
+ vertical-align: middle;
210
+ }
211
+
212
+ .calendar-day {
213
+ width: 44px;
214
+ height: 44px;
215
+ padding: 0;
216
+ border: none;
217
+ border-radius: var(--theme-radius-large-1);
218
+ font-size: var(--font-body-medium-size);
219
+ color: var(--color-label-standard);
220
+ }
221
+
222
+ .calendar-day[data-outside="true"] {
223
+ color: var(--color-label-alternative);
224
+ }
225
+
226
+ .calendar-day[data-selected="true"],
227
+ .calendar-day[data-focused="true"] {
228
+ background-color: var(--color-primary-default);
229
+ color: var(--color-common-100);
230
+ }
231
+
232
+ .calendar-day:where(:not([data-disabled="true"])):hover {
233
+ background-color: var(--color-secondary-default);
234
+ color: var(--color-label-standard);
235
+ border: none;
236
+ }
237
+
238
+ .calendar-footer {
239
+ width: 100%;
240
+ }