@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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { string } from "@uniai-fe/util-functions";
|
|
2
|
+
import type { Address } from "react-daum-postcode";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
AddressResolvedValue,
|
|
6
|
+
AddressSearchResult,
|
|
7
|
+
AddressStructure,
|
|
8
|
+
} from "../types/address";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 객체 여부 체크 util.
|
|
12
|
+
*/
|
|
13
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
14
|
+
typeof value === "object" && value !== null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* react-daum-postcode AddressSearchResult 유사 객체인지 검사한다.
|
|
18
|
+
*/
|
|
19
|
+
const isAddressSearchResultLike = (
|
|
20
|
+
value: unknown,
|
|
21
|
+
): value is Partial<AddressSearchResult> =>
|
|
22
|
+
isObject(value) && typeof value.address === "string";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* AddressStructure 형태인지 검사한다.
|
|
26
|
+
*/
|
|
27
|
+
const isAddressStructureLike = (
|
|
28
|
+
value: unknown,
|
|
29
|
+
): value is Partial<AddressStructure> =>
|
|
30
|
+
isObject(value) &&
|
|
31
|
+
("main" in value || "extras" in value) &&
|
|
32
|
+
(typeof value.main === "string" || Array.isArray(value.extras));
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 문자열 파트 배열을 안전하게 변환한다.
|
|
36
|
+
*/
|
|
37
|
+
const toAddressParts = (items: unknown[] | undefined): string[] => {
|
|
38
|
+
if (!Array.isArray(items)) return [];
|
|
39
|
+
return items
|
|
40
|
+
.map(item => string(item, ""))
|
|
41
|
+
.filter((segment): segment is string => segment.length > 0);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* AddressStructure에서 main/extras를 순서대로 병합한다.
|
|
46
|
+
*/
|
|
47
|
+
const mergeStructureParts = (
|
|
48
|
+
structure: AddressStructure | Partial<AddressStructure>,
|
|
49
|
+
): string[] => {
|
|
50
|
+
const mainPart =
|
|
51
|
+
typeof structure.main === "string" ? structure.main : undefined;
|
|
52
|
+
const extras = toAddressParts(structure.extras);
|
|
53
|
+
return [mainPart, ...extras].filter(
|
|
54
|
+
(part): part is string => typeof part === "string" && part.length > 0,
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Template/Base 컴포넌트에서 사용하는 주소 문자열 정규화.
|
|
60
|
+
* @function
|
|
61
|
+
* @param {unknown} value RHF watch 값 혹은 addressInput.value override
|
|
62
|
+
* @returns {AddressResolvedValue} 문자열과 파트 배열
|
|
63
|
+
*/
|
|
64
|
+
export const resolveAddressValue = (value: unknown): AddressResolvedValue => {
|
|
65
|
+
if (typeof value === "string") {
|
|
66
|
+
return {
|
|
67
|
+
text: value,
|
|
68
|
+
parts: value.length > 0 ? [value] : [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
const parts = toAddressParts(value);
|
|
74
|
+
return {
|
|
75
|
+
text: parts.join(" "),
|
|
76
|
+
parts,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isAddressSearchResultLike(value)) {
|
|
81
|
+
const base = value.address ?? "";
|
|
82
|
+
const parts =
|
|
83
|
+
value.addressParts && value.addressParts.length > 0
|
|
84
|
+
? toAddressParts(value.addressParts)
|
|
85
|
+
: value.addressStructure
|
|
86
|
+
? mergeStructureParts(value.addressStructure)
|
|
87
|
+
: base
|
|
88
|
+
? [base]
|
|
89
|
+
: [];
|
|
90
|
+
return {
|
|
91
|
+
text: base,
|
|
92
|
+
parts,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (isAddressStructureLike(value)) {
|
|
97
|
+
const parts = mergeStructureParts(value);
|
|
98
|
+
return {
|
|
99
|
+
text: parts.join(" "),
|
|
100
|
+
parts,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isObject(value) && "text" in value) {
|
|
105
|
+
const textValue =
|
|
106
|
+
typeof (value as { text?: unknown }).text === "string"
|
|
107
|
+
? (value as { text: string }).text
|
|
108
|
+
: "";
|
|
109
|
+
const parts = Array.isArray((value as { parts?: unknown[] }).parts)
|
|
110
|
+
? toAddressParts((value as { parts: unknown[] }).parts)
|
|
111
|
+
: textValue
|
|
112
|
+
? [textValue]
|
|
113
|
+
: [];
|
|
114
|
+
return {
|
|
115
|
+
text: textValue,
|
|
116
|
+
parts,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { text: "", parts: [] };
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* react-daum-postcode Address 데이터를 AddressSearchResult 형식으로 변환한다.
|
|
125
|
+
* `address` 필드는 우편번호 서비스에서 내려준 최종 문자열(도로명 + 추가 항목)을 그대로 유지하고,
|
|
126
|
+
* 분해 정보(`addressParts`, `addressStructure`)를 추가로 제공한다.
|
|
127
|
+
* @function
|
|
128
|
+
* @see https://www.juso.go.kr/addrlink/devAddrLinkRequestGuide.do
|
|
129
|
+
* @param {Address} data 우편번호 서비스 선택 결과
|
|
130
|
+
* @returns {AddressSearchResult} 변환된 주소 정보 객체
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* const result = createAddressSearchResult(data);
|
|
134
|
+
* console.log(result.address); // "서울시 강남구 ... (OO동)"
|
|
135
|
+
* console.log(result.addressParts); // ["서울시 강남구 ...", "OO동"]
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export const createAddressSearchResult = (
|
|
139
|
+
data: Address,
|
|
140
|
+
): AddressSearchResult => {
|
|
141
|
+
const extraParts: string[] = [];
|
|
142
|
+
|
|
143
|
+
if (data.addressType === "R") {
|
|
144
|
+
if (data.bname) extraParts.push(data.bname);
|
|
145
|
+
if (data.buildingName) extraParts.push(data.buildingName);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const baseAddress = data.address;
|
|
149
|
+
const labelExtras =
|
|
150
|
+
extraParts.length > 0 ? ` (${extraParts.join(", ")})` : "";
|
|
151
|
+
const parts = [baseAddress, ...extraParts].filter(part => part.length > 0);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
address: `${baseAddress}${labelExtras}`,
|
|
155
|
+
addressParts: parts,
|
|
156
|
+
addressStructure: {
|
|
157
|
+
main: baseAddress,
|
|
158
|
+
extras: [...extraParts],
|
|
159
|
+
},
|
|
160
|
+
zipCode: data.zonecode,
|
|
161
|
+
buildingName: data.buildingName || undefined,
|
|
162
|
+
district: data.bname || undefined,
|
|
163
|
+
raw: data,
|
|
164
|
+
};
|
|
165
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { dayjs } from "../../../init/dayjs";
|
|
2
|
+
import type { CalendarColumns, CalendarValue } from "../../calendar";
|
|
3
|
+
|
|
4
|
+
const DATE_FORMAT = "YYYY-MM-DD";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calendar에 빈 값을 선언한다.
|
|
8
|
+
* @returns {CalendarValue} null 직렬화 값
|
|
9
|
+
*/
|
|
10
|
+
export const createEmptyValue = (): CalendarValue => null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Date 객체를 YYYY-MM-DD 문자열로 포맷한다.
|
|
14
|
+
* @param {Date | string | null} date 포맷 대상
|
|
15
|
+
* @returns {CalendarValue} 포맷 결과
|
|
16
|
+
*/
|
|
17
|
+
const formatDate = (date: Date | string | null) =>
|
|
18
|
+
date ? dayjs(date).format(DATE_FORMAT) : null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mantine DatePicker에 전달할 Date 객체를 만든다.
|
|
22
|
+
* @param {CalendarValue} value YYYY-MM-DD 문자열
|
|
23
|
+
* @returns {Date | null} DatePicker value
|
|
24
|
+
*/
|
|
25
|
+
export const mapValueToPicker = (value: CalendarValue) =>
|
|
26
|
+
value ? dayjs(value).toDate() : null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* DatePicker 반환 값을 문자열로 직렬화한다.
|
|
30
|
+
* @param {Date | null} value DatePicker 값
|
|
31
|
+
* @returns {CalendarValue} 직렬화 문자열
|
|
32
|
+
*/
|
|
33
|
+
export const parseValueFromPicker = (value: Date | null) =>
|
|
34
|
+
formatDate(value ?? null);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 오늘 날짜를 YYYY-MM-DD 문자열로 반환한다.
|
|
38
|
+
* @returns {CalendarValue} 오늘 값
|
|
39
|
+
*/
|
|
40
|
+
export const getTodayValue = (): CalendarValue => dayjs().format(DATE_FORMAT);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* hidden input 직렬화용 문자열.
|
|
44
|
+
* @param {CalendarValue} value 현재 값
|
|
45
|
+
* @returns {string} 직렬화된 문자열
|
|
46
|
+
*/
|
|
47
|
+
export const serializeCalendarValue = (value: CalendarValue) => value ?? "";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* trigger에 표시할 문자열을 계산한다.
|
|
51
|
+
* @param {CalendarValue} value 현재 값
|
|
52
|
+
* @returns {string} 표시 문자열
|
|
53
|
+
*/
|
|
54
|
+
export const formatTriggerValue = (value: CalendarValue) => value ?? "";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* columns 값을 Mantine numberOfColumns와 맞춘다.
|
|
58
|
+
* @param {CalendarColumns} columns 열 개수
|
|
59
|
+
* @returns {number} numberOfColumns 값
|
|
60
|
+
*/
|
|
61
|
+
export const mapColumnsToNumber = (columns: CalendarColumns) => columns;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use "./styles/index.scss";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as PopOverPrimitive from "@radix-ui/react-popover";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { forwardRef } from "react";
|
|
6
|
+
import type { PopOverContentProps } from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PopOver Content.
|
|
10
|
+
* @component
|
|
11
|
+
* @param {PopOverContentProps} props Content props
|
|
12
|
+
* @param {React.ReactNode} [props.children] Content 내부 콘텐츠
|
|
13
|
+
* @param {string} [props.className] Content className
|
|
14
|
+
* @param {boolean} [props.withPortal=true] Portal 사용 여부
|
|
15
|
+
* @param {HTMLElement | null} [props.portalContainer] Portal 컨테이너
|
|
16
|
+
* @param {number} [props.sideOffset=4] Trigger와 Content 간격
|
|
17
|
+
* @param {PopOverContentWidth} [props.width="fit-content"] Content width 옵션
|
|
18
|
+
* @example
|
|
19
|
+
* <PopOver.Content sideOffset={6}>popover content</PopOver.Content>
|
|
20
|
+
*/
|
|
21
|
+
const PopOverContent = forwardRef<HTMLDivElement, PopOverContentProps>(
|
|
22
|
+
(
|
|
23
|
+
{
|
|
24
|
+
children,
|
|
25
|
+
className,
|
|
26
|
+
withPortal = true,
|
|
27
|
+
portalContainer,
|
|
28
|
+
sideOffset = 4,
|
|
29
|
+
width = "fit-content",
|
|
30
|
+
style,
|
|
31
|
+
...restProps
|
|
32
|
+
},
|
|
33
|
+
ref,
|
|
34
|
+
) => {
|
|
35
|
+
const resolvedWidth =
|
|
36
|
+
width === "match"
|
|
37
|
+
? "var(--radix-popover-trigger-width)"
|
|
38
|
+
: typeof width === "number"
|
|
39
|
+
? `${width}px`
|
|
40
|
+
: width;
|
|
41
|
+
const resolvedWidthToken =
|
|
42
|
+
width === "match" || width === "fit-content" || width === "max-content"
|
|
43
|
+
? width
|
|
44
|
+
: "custom";
|
|
45
|
+
|
|
46
|
+
// Content 노드는 portal 여부에 따라 동일 노드를 재사용한다.
|
|
47
|
+
const contentNode = (
|
|
48
|
+
<PopOverPrimitive.Content
|
|
49
|
+
ref={ref}
|
|
50
|
+
className={clsx("pop-over-content", className)}
|
|
51
|
+
sideOffset={sideOffset}
|
|
52
|
+
data-width={resolvedWidthToken}
|
|
53
|
+
style={{
|
|
54
|
+
...style,
|
|
55
|
+
width: resolvedWidth,
|
|
56
|
+
}}
|
|
57
|
+
{...restProps}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</PopOverPrimitive.Content>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (!withPortal) {
|
|
64
|
+
return contentNode;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<PopOverPrimitive.Portal container={portalContainer ?? undefined}>
|
|
69
|
+
{contentNode}
|
|
70
|
+
</PopOverPrimitive.Portal>
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
PopOverContent.displayName = "PopOverContent";
|
|
76
|
+
|
|
77
|
+
export default PopOverContent;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as PopOverPrimitive from "@radix-ui/react-popover";
|
|
4
|
+
import type { PopOverRootProps } from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PopOver Root.
|
|
8
|
+
* @component
|
|
9
|
+
* @param {PopOverRootProps} props Root props
|
|
10
|
+
* @param {React.ReactNode} props.children PopOver 하위 노드
|
|
11
|
+
* @param {boolean} [props.open] 제어형 open 상태
|
|
12
|
+
* @param {boolean} [props.defaultOpen] 비제어 초기 open 상태
|
|
13
|
+
* @param {(open: boolean) => void} [props.onOpenChange] open 상태 변경 핸들러
|
|
14
|
+
* @param {boolean} [props.modal] Radix modal 모드
|
|
15
|
+
* @example
|
|
16
|
+
* <PopOver.Root>
|
|
17
|
+
* <PopOver.Trigger asChild><button>open</button></PopOver.Trigger>
|
|
18
|
+
* <PopOver.Content>content</PopOver.Content>
|
|
19
|
+
* </PopOver.Root>
|
|
20
|
+
*/
|
|
21
|
+
export default function PopOverRoot({
|
|
22
|
+
children,
|
|
23
|
+
...restProps
|
|
24
|
+
}: PopOverRootProps) {
|
|
25
|
+
return (
|
|
26
|
+
<PopOverPrimitive.Root {...restProps}>{children}</PopOverPrimitive.Root>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as PopOverPrimitive from "@radix-ui/react-popover";
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import type { PopOverTriggerProps } from "../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PopOver Trigger.
|
|
9
|
+
* @component
|
|
10
|
+
* @param {PopOverTriggerProps} props Trigger props
|
|
11
|
+
* @param {React.ReactNode} [props.children] Trigger 콘텐츠
|
|
12
|
+
* @param {boolean} [props.asChild] child 노드를 Trigger element로 사용할지 여부
|
|
13
|
+
* @example
|
|
14
|
+
* <PopOver.Trigger asChild><button>open</button></PopOver.Trigger>
|
|
15
|
+
*/
|
|
16
|
+
const PopOverTrigger = forwardRef<HTMLButtonElement, PopOverTriggerProps>(
|
|
17
|
+
({ children, ...restProps }, ref) => (
|
|
18
|
+
<PopOverPrimitive.Trigger ref={ref} {...restProps}>
|
|
19
|
+
{children}
|
|
20
|
+
</PopOverPrimitive.Trigger>
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
PopOverTrigger.displayName = "PopOverTrigger";
|
|
25
|
+
|
|
26
|
+
export default PopOverTrigger;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import PopOverContent from "./Content";
|
|
2
|
+
import PopOverRoot from "./Root";
|
|
3
|
+
import PopOverTrigger from "./Trigger";
|
|
4
|
+
|
|
5
|
+
export { PopOverRoot, PopOverTrigger, PopOverContent };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PopOver namespace.
|
|
9
|
+
* - Root
|
|
10
|
+
* - Trigger
|
|
11
|
+
* - Content
|
|
12
|
+
*/
|
|
13
|
+
export const PopOver = {
|
|
14
|
+
Root: PopOverRoot,
|
|
15
|
+
Trigger: PopOverTrigger,
|
|
16
|
+
Content: PopOverContent,
|
|
17
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.pop-over-content {
|
|
2
|
+
// PopOver 공통 surface 스킨(배경/라운드/그림자)은 Content에서 기본 제공한다.
|
|
3
|
+
width: fit-content;
|
|
4
|
+
max-width: min(100vw - 24px, max-content);
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
background-color: var(--color-common-100);
|
|
7
|
+
border-radius: var(--theme-radius-large-2);
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
box-shadow:
|
|
10
|
+
0 4px 20px rgba(0, 0, 0, 0.16),
|
|
11
|
+
0 0 2px rgba(0, 0, 0, 0.12);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.pop-over-content[data-width="match"] {
|
|
15
|
+
width: var(--radix-popover-trigger-width);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.pop-over-content[data-width="fit-content"] {
|
|
19
|
+
width: fit-content;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.pop-over-content[data-width="max-content"] {
|
|
23
|
+
width: max-content;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type * from "./pop-over";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PopoverContentProps as RadixPopOverContentProps,
|
|
3
|
+
PopoverProps as RadixPopOverProps,
|
|
4
|
+
PopoverTriggerProps as RadixPopOverTriggerProps,
|
|
5
|
+
} from "@radix-ui/react-popover";
|
|
6
|
+
import type { ReactNode } from "react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PopOver Content width 옵션.
|
|
10
|
+
* @property {"match"} width trigger width와 동일
|
|
11
|
+
* @property {"fit-content"} width 콘텐츠 기준 폭
|
|
12
|
+
* @property {"max-content"} width 최대 콘텐츠 폭
|
|
13
|
+
* @property {number} width px 단위 폭
|
|
14
|
+
* @property {string} width CSS width 문자열
|
|
15
|
+
*/
|
|
16
|
+
export type PopOverContentWidth =
|
|
17
|
+
| "match"
|
|
18
|
+
| "fit-content"
|
|
19
|
+
| "max-content"
|
|
20
|
+
| number
|
|
21
|
+
| string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* PopOver Root props.
|
|
25
|
+
* @property {ReactNode} children PopOver 하위 노드
|
|
26
|
+
* @property {boolean} [open] 제어형 open 상태
|
|
27
|
+
* @property {boolean} [defaultOpen] 비제어 초기 open 상태
|
|
28
|
+
* @property {(open: boolean) => void} [onOpenChange] open 상태 변경 핸들러
|
|
29
|
+
* @property {boolean} [modal] Radix modal 모드
|
|
30
|
+
* @see Radix PopoverProps
|
|
31
|
+
*/
|
|
32
|
+
export interface PopOverRootProps extends RadixPopOverProps {
|
|
33
|
+
/**
|
|
34
|
+
* PopOver 하위 노드
|
|
35
|
+
*/
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* PopOver Trigger props.
|
|
41
|
+
* @property {ReactNode} [children] Trigger 콘텐츠
|
|
42
|
+
* @property {boolean} [asChild] child 노드를 trigger로 사용할지 여부
|
|
43
|
+
* @see Radix PopoverTriggerProps
|
|
44
|
+
*/
|
|
45
|
+
export interface PopOverTriggerProps extends RadixPopOverTriggerProps {
|
|
46
|
+
/**
|
|
47
|
+
* Trigger 콘텐츠
|
|
48
|
+
*/
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* PopOver Content props.
|
|
54
|
+
* @property {ReactNode} [children] Content 내부 콘텐츠
|
|
55
|
+
* @property {string} [className] Content className
|
|
56
|
+
* @property {boolean} [withPortal=true] Portal 사용 여부
|
|
57
|
+
* @property {HTMLElement | null} [portalContainer] Portal 컨테이너
|
|
58
|
+
* @property {number} [sideOffset=4] trigger와 content 사이 간격
|
|
59
|
+
* @property {PopOverContentWidth} [width="fit-content"] content width 옵션
|
|
60
|
+
* @see Radix PopoverContentProps
|
|
61
|
+
*/
|
|
62
|
+
export interface PopOverContentProps extends Omit<
|
|
63
|
+
RadixPopOverContentProps,
|
|
64
|
+
"children"
|
|
65
|
+
> {
|
|
66
|
+
/**
|
|
67
|
+
* Content 내부 콘텐츠
|
|
68
|
+
*/
|
|
69
|
+
children?: ReactNode;
|
|
70
|
+
/**
|
|
71
|
+
* Content className
|
|
72
|
+
*/
|
|
73
|
+
className?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Portal 사용 여부
|
|
76
|
+
*/
|
|
77
|
+
withPortal?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Portal 컨테이너
|
|
80
|
+
*/
|
|
81
|
+
portalContainer?: HTMLElement | null;
|
|
82
|
+
/**
|
|
83
|
+
* content width 옵션
|
|
84
|
+
*/
|
|
85
|
+
width?: PopOverContentWidth;
|
|
86
|
+
}
|
|
@@ -26,7 +26,7 @@ const RADIO_HELPER_CLASSNAME = "radio-helper";
|
|
|
26
26
|
* @example
|
|
27
27
|
* <Radio value="option" />
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
const Radio = forwardRef<HTMLButtonElement, RadioProps>(function Radio(
|
|
30
30
|
{ size = "medium", className, disabled, ...restProps },
|
|
31
31
|
ref,
|
|
32
32
|
) {
|
|
@@ -49,6 +49,9 @@ export const Radio = forwardRef<HTMLButtonElement, RadioProps>(function Radio(
|
|
|
49
49
|
);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
// forwardRef Tooltip 유지를 위해 displayName 지정.
|
|
53
|
+
Radio.displayName = "Radio";
|
|
54
|
+
|
|
52
55
|
/**
|
|
53
56
|
* RadioField component; label/helper wrapper for single radioItem
|
|
54
57
|
* @component
|
|
@@ -66,7 +69,7 @@ export const Radio = forwardRef<HTMLButtonElement, RadioProps>(function Radio(
|
|
|
66
69
|
* @example
|
|
67
70
|
* <RadioField label="옵션" helperText="필수" value="option" />
|
|
68
71
|
*/
|
|
69
|
-
|
|
72
|
+
const RadioField = forwardRef<HTMLButtonElement, RadioFieldProps>(
|
|
70
73
|
function RadioField(
|
|
71
74
|
{
|
|
72
75
|
label,
|
|
@@ -133,3 +136,8 @@ export const RadioField = forwardRef<HTMLButtonElement, RadioFieldProps>(
|
|
|
133
136
|
);
|
|
134
137
|
},
|
|
135
138
|
);
|
|
139
|
+
|
|
140
|
+
// forwardRef Tooltip 유지를 위해 displayName 지정.
|
|
141
|
+
RadioField.displayName = "RadioField";
|
|
142
|
+
|
|
143
|
+
export { Radio, RadioField };
|
|
@@ -30,7 +30,7 @@ const RADIO_CARD_INDICATOR_CLASSNAME = "radio-card-indicator";
|
|
|
30
30
|
* @example
|
|
31
31
|
* <RadioCard title="행복1농장" value="farm-1" />
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
const RadioCard = forwardRef<HTMLButtonElement, RadioCardProps>(
|
|
34
34
|
function RadioCard(
|
|
35
35
|
{
|
|
36
36
|
title,
|
|
@@ -74,3 +74,8 @@ export const RadioCard = forwardRef<HTMLButtonElement, RadioCardProps>(
|
|
|
74
74
|
);
|
|
75
75
|
},
|
|
76
76
|
);
|
|
77
|
+
|
|
78
|
+
// forwardRef Tooltip 유지를 위해 displayName 지정.
|
|
79
|
+
RadioCard.displayName = "RadioCard";
|
|
80
|
+
|
|
81
|
+
export { RadioCard };
|
|
@@ -22,7 +22,7 @@ const RADIO_CARD_GROUP_CLASSNAME = "radio-card-group";
|
|
|
22
22
|
* @example
|
|
23
23
|
* <RadioCardGroup options={[{ id: "farm-1", title: "농장" }]} value="farm-1" />
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
const RadioCardGroup = forwardRef<
|
|
26
26
|
ElementRef<typeof RadixRadioGroup.Root>,
|
|
27
27
|
RadioCardGroupProps
|
|
28
28
|
>(function RadioCardGroup(
|
|
@@ -81,3 +81,8 @@ export const RadioCardGroup = forwardRef<
|
|
|
81
81
|
</RadixRadioGroup.Root>
|
|
82
82
|
);
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
// forwardRef Tooltip 유지를 위해 displayName 지정.
|
|
86
|
+
RadioCardGroup.displayName = "RadioCardGroup";
|
|
87
|
+
|
|
88
|
+
export { RadioCardGroup };
|
|
@@ -36,6 +36,7 @@ const SelectDefault = forwardRef<HTMLElement, SelectDefaultComponentProps>(
|
|
|
36
36
|
size = "medium",
|
|
37
37
|
state = "default",
|
|
38
38
|
block,
|
|
39
|
+
width,
|
|
39
40
|
isOpen,
|
|
40
41
|
disabled,
|
|
41
42
|
buttonType,
|
|
@@ -86,6 +87,7 @@ const SelectDefault = forwardRef<HTMLElement, SelectDefaultComponentProps>(
|
|
|
86
87
|
<Container
|
|
87
88
|
className={clsx("select-trigger-container", className)}
|
|
88
89
|
block={block}
|
|
90
|
+
width={width}
|
|
89
91
|
>
|
|
90
92
|
<Dropdown.Root
|
|
91
93
|
open={dropdownOpen}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
|
|
5
5
|
import type { SelectContainerProps } from "../../types/props";
|
|
6
|
+
import {
|
|
7
|
+
getFormFieldWidthAttr,
|
|
8
|
+
getFormFieldWidthValue,
|
|
9
|
+
} from "../../../form/utils/form-field";
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Select trigger/container wrapper
|
|
@@ -16,12 +20,31 @@ export default function SelectContainer({
|
|
|
16
20
|
className,
|
|
17
21
|
children,
|
|
18
22
|
block = false,
|
|
23
|
+
width,
|
|
24
|
+
style,
|
|
25
|
+
...restProps
|
|
19
26
|
}: SelectContainerProps) {
|
|
27
|
+
const widthAttr =
|
|
28
|
+
width !== undefined
|
|
29
|
+
? getFormFieldWidthAttr(width)
|
|
30
|
+
: block
|
|
31
|
+
? "full"
|
|
32
|
+
: undefined;
|
|
33
|
+
const widthValue =
|
|
34
|
+
width !== undefined ? getFormFieldWidthValue(width) : undefined;
|
|
35
|
+
const mergedStyle =
|
|
36
|
+
widthValue !== undefined
|
|
37
|
+
? { ...(style ?? {}), ["--select-width" as const]: widthValue }
|
|
38
|
+
: style;
|
|
39
|
+
|
|
20
40
|
return (
|
|
21
41
|
<div
|
|
22
42
|
className={clsx("select select-container", className, {
|
|
23
43
|
"select-block": block,
|
|
24
44
|
})}
|
|
45
|
+
data-width={widthAttr}
|
|
46
|
+
style={mergedStyle}
|
|
47
|
+
{...restProps}
|
|
25
48
|
>
|
|
26
49
|
{/** dropdown root 및 dropdown menu 등 포함 예정 */}
|
|
27
50
|
{children}
|
|
@@ -38,6 +38,7 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
38
38
|
size = "medium",
|
|
39
39
|
state = "default",
|
|
40
40
|
block,
|
|
41
|
+
width,
|
|
41
42
|
isOpen,
|
|
42
43
|
disabled,
|
|
43
44
|
tags,
|
|
@@ -113,6 +114,7 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
113
114
|
<Container
|
|
114
115
|
className={clsx("select-trigger-multiple", className)}
|
|
115
116
|
block={block}
|
|
117
|
+
width={width}
|
|
116
118
|
>
|
|
117
119
|
<Dropdown.Root
|
|
118
120
|
open={dropdownOpen}
|