@uniai-fe/uds-primitives 0.12.5 → 0.12.7
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 +17 -0
- package/dist/styles.css +253 -840
- package/package.json +9 -9
- package/src/components/button/markup/Base.tsx +3 -2
- package/src/components/button/styles/button.scss +99 -326
- package/src/components/button/styles/round-button.scss +12 -39
- package/src/components/button/styles/text-button.scss +16 -52
- package/src/components/button/styles/variables.scss +33 -55
- package/src/components/button/types/options.ts +7 -4
- package/src/components/button/types/props.ts +11 -14
- package/src/components/button/utils/index.ts +14 -3
- package/src/components/form/markup/Provider.tsx +7 -3
- package/src/components/form/markup/form-field/Body.tsx +1 -0
- package/src/components/form/markup/form-field/Container.tsx +3 -1
- package/src/components/form/markup/form-field/Footer.tsx +1 -0
- package/src/components/form/markup/form-field/Header.tsx +2 -0
- package/src/components/form/markup/form-field/Template.tsx +3 -2
- package/src/components/form/markup/form-field/index.tsx +1 -1
- package/src/components/form/markup/index.tsx +1 -0
- package/src/components/form/styles/form-field/variables.scss +2 -2
- package/src/components/form/types/props.ts +21 -19
- package/src/components/form/utils/form-field.ts +6 -4
- package/src/components/input/styles/date.scss +1 -1
- package/src/components/input/styles/text.scss +1 -1
- package/src/components/slot/index.tsx +3 -6
- package/src/components/slot/markup/Base.tsx +146 -9
- package/src/components/slot/markup/index.tsx +2 -4
- package/src/components/slot/types/props.ts +119 -23
- package/src/components/table/types/foundation.ts +9 -7
- package/src/index.scss +0 -2
- package/src/index.tsx +8 -2
- package/src/types/index.ts +1 -4
- package/src/utils/index.ts +8 -4
- package/src/utils/selected-values.ts +20 -14
- package/src/components/scrollbar/hooks/index.ts +0 -4
- package/src/components/scrollbar/img/.gitkeep +0 -0
- package/src/components/scrollbar/index.scss +0 -1
- package/src/components/scrollbar/index.tsx +0 -4
- package/src/components/scrollbar/markup/index.tsx +0 -4
- package/src/components/scrollbar/styles/index.scss +0 -0
- package/src/components/scrollbar/types/index.ts +0 -4
- package/src/components/scrollbar/utils/index.ts +0 -4
- package/src/components/spinner/hooks/index.ts +0 -4
- package/src/components/spinner/img/.gitkeep +0 -0
- package/src/components/spinner/index.scss +0 -1
- package/src/components/spinner/index.tsx +0 -4
- package/src/components/spinner/markup/index.tsx +0 -4
- package/src/components/spinner/styles/index.scss +0 -0
- package/src/components/spinner/types/index.ts +0 -4
- package/src/components/spinner/utils/index.ts +0 -4
- package/src/hooks/index.ts +0 -4
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import type { ElementType } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Form; field container width option
|
|
5
|
-
*
|
|
6
|
-
* - fit: 콘텐츠 너비에 맞춤
|
|
7
|
-
* - fill: flex-grow 1 분배
|
|
8
|
-
* - auto: 기본 width(auto)
|
|
9
|
-
* - number: px 단위 입력, rem으로 환산
|
|
10
|
-
* - string: CSS width 문자열
|
|
4
|
+
* Form Types; field container width option
|
|
5
|
+
* @desc `"full" | "fit" | "fill" | "auto" | number | string`을 허용한다. number는 px 기준 rem으로 환산하고 preset이 아닌 string은 CSS width로 사용한다.
|
|
11
6
|
*/
|
|
12
7
|
export type FormFieldWidth = "full" | "fit" | "fill" | "auto" | number | string;
|
|
13
8
|
|
|
14
9
|
/**
|
|
15
|
-
* Form; field state option
|
|
10
|
+
* Form Types; field state option
|
|
11
|
+
* @desc `"default" | "error"`를 허용하며 field helper의 visual state를 지정한다.
|
|
16
12
|
*/
|
|
17
13
|
export type FormFieldState = "default" | "error";
|
|
18
14
|
|
|
@@ -26,6 +22,7 @@ export type FormFieldState = "default" | "error";
|
|
|
26
22
|
* @property {React.HTMLAttributes<HTMLElement>} [labelProps] label 태그 속성
|
|
27
23
|
* @property {boolean} [required] 필수 표시
|
|
28
24
|
* @property {React.ReactNode} [children] description 등 추가 요소
|
|
25
|
+
* @desc field label, 필수 표시와 header 추가 콘텐츠를 구성한다.
|
|
29
26
|
*/
|
|
30
27
|
export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement> {
|
|
31
28
|
/**
|
|
@@ -33,7 +30,7 @@ export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement>
|
|
|
33
30
|
*/
|
|
34
31
|
className?: string;
|
|
35
32
|
/**
|
|
36
|
-
* label heading
|
|
33
|
+
* label heading 태그를 지정한다.
|
|
37
34
|
*/
|
|
38
35
|
labelAs?: ElementType;
|
|
39
36
|
/**
|
|
@@ -41,11 +38,11 @@ export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement>
|
|
|
41
38
|
*/
|
|
42
39
|
labelId?: string;
|
|
43
40
|
/**
|
|
44
|
-
* 기본 label
|
|
41
|
+
* 기본 label 텍스트를 지정한다.
|
|
45
42
|
*/
|
|
46
43
|
label?: string;
|
|
47
44
|
/**
|
|
48
|
-
* 커스텀 label JSX
|
|
45
|
+
* label보다 우선하는 커스텀 label JSX를 지정한다.
|
|
49
46
|
*/
|
|
50
47
|
labelJsx?: React.ReactNode;
|
|
51
48
|
/**
|
|
@@ -66,6 +63,7 @@ export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement>
|
|
|
66
63
|
* Form.Field Footer props; `Footer.tsx`에서 helper/action 영역을 담당한다.
|
|
67
64
|
* @property {string} [className] footer className
|
|
68
65
|
* @property {React.ReactNode} [children] footer 내용
|
|
66
|
+
* @desc field helper 또는 action을 footer 영역에 렌더링한다.
|
|
69
67
|
*/
|
|
70
68
|
export interface FormFieldFooterProps extends React.HTMLAttributes<HTMLElement> {
|
|
71
69
|
/**
|
|
@@ -81,10 +79,12 @@ export interface FormFieldFooterProps extends React.HTMLAttributes<HTMLElement>
|
|
|
81
79
|
/**
|
|
82
80
|
* Form.Field Container props; `Container.tsx`에서 wrapper와 width 옵션을 제어한다.
|
|
83
81
|
* @property {string} [className] container className
|
|
84
|
-
* @property {
|
|
85
|
-
* @property {
|
|
86
|
-
* @property {
|
|
82
|
+
* @property {ElementType} [as] container 태그
|
|
83
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
84
|
+
* @property {"default" | "error"} [state] visual state
|
|
87
85
|
* @property {React.ReactNode} children Body에 전달될 콘텐츠
|
|
86
|
+
* @see React.HTMLAttributes<HTMLElement>
|
|
87
|
+
* @desc semantic wrapper, width와 helper visual state를 제어한다.
|
|
88
88
|
*/
|
|
89
89
|
export interface FormFieldContainerProps extends React.HTMLAttributes<HTMLElement> {
|
|
90
90
|
/**
|
|
@@ -96,11 +96,11 @@ export interface FormFieldContainerProps extends React.HTMLAttributes<HTMLElemen
|
|
|
96
96
|
*/
|
|
97
97
|
as?: ElementType;
|
|
98
98
|
/**
|
|
99
|
-
* width
|
|
99
|
+
* `"full" | "fit" | "fill" | "auto" | number | string` width 옵션을 지정한다.
|
|
100
100
|
*/
|
|
101
101
|
width?: FormFieldWidth;
|
|
102
102
|
/**
|
|
103
|
-
* visual state
|
|
103
|
+
* `"default" | "error"` visual state를 지정한다.
|
|
104
104
|
*/
|
|
105
105
|
state?: FormFieldState;
|
|
106
106
|
/**
|
|
@@ -115,10 +115,12 @@ export interface FormFieldContainerProps extends React.HTMLAttributes<HTMLElemen
|
|
|
115
115
|
* @property {FormFieldHeaderProps} [headerProps] Header props
|
|
116
116
|
* @property {FormFieldFooterProps} [footerProps] Footer props
|
|
117
117
|
* @property {string} [className] container className
|
|
118
|
-
* @property {
|
|
119
|
-
* @property {
|
|
120
|
-
* @property {
|
|
118
|
+
* @property {ElementType} [as] container 태그
|
|
119
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
120
|
+
* @property {"default" | "error"} [state] visual state
|
|
121
121
|
* @property {React.ReactNode} children Body에 전달될 콘텐츠
|
|
122
|
+
* @see FormFieldContainerProps
|
|
123
|
+
* @desc Container props를 상속하고 Header, Body와 선택적 Footer를 조합한다.
|
|
122
124
|
*/
|
|
123
125
|
export interface FormFieldTemplateProps extends FormFieldContainerProps {
|
|
124
126
|
/**
|
|
@@ -6,8 +6,9 @@ const WIDTH_PRESETS = new Set<FormFieldWidth>(["full", "fit", "fill", "auto"]);
|
|
|
6
6
|
/**
|
|
7
7
|
* Form.Field width attr helper; preset이면 그대로 반환하고 custom이면 "custom"/기본값은 "auto".
|
|
8
8
|
* @function
|
|
9
|
-
* @param {
|
|
10
|
-
* @
|
|
9
|
+
* @param {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
10
|
+
* @desc Form.Field width를 data-width attribute 값으로 변환한다.
|
|
11
|
+
* @return {string} preset, "custom" 또는 "auto"
|
|
11
12
|
*/
|
|
12
13
|
export const getFormFieldWidthAttr = (width?: FormFieldWidth): string => {
|
|
13
14
|
if (typeof width === "string" && WIDTH_PRESETS.has(width)) {
|
|
@@ -24,8 +25,9 @@ export const getFormFieldWidthAttr = (width?: FormFieldWidth): string => {
|
|
|
24
25
|
/**
|
|
25
26
|
* Form.Field width style helper; number/string custom 값을 CSS width 문자열로 환산한다.
|
|
26
27
|
* @function
|
|
27
|
-
* @param {
|
|
28
|
-
* @
|
|
28
|
+
* @param {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
29
|
+
* @desc 유한한 number는 rem으로 환산하고 preset이 아닌 string은 그대로 반환한다.
|
|
30
|
+
* @return {string | undefined} custom CSS width 또는 preset/미지정일 때 undefined
|
|
29
31
|
*/
|
|
30
32
|
export const getFormFieldWidthValue = (
|
|
31
33
|
width?: FormFieldWidth,
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* Slot
|
|
3
|
-
*
|
|
4
|
-
* - `Slot.Base`: as 기반 polymorphic 마크업 래퍼다.
|
|
5
|
-
* - `Slot.Text`: 텍스트 children만 래핑하는 공통 슬롯이다.
|
|
6
|
-
* - `SlotComponentProps`, `SlotTextProps`: public type 계약이다.
|
|
1
|
+
/*
|
|
2
|
+
* Slot public entry. Runtime namespace는 ./markup, public prop type은 ./types의
|
|
3
|
+
* 원본 선언이 계약을 소유한다.
|
|
7
4
|
*/
|
|
8
5
|
export * from "./markup";
|
|
9
6
|
export type * from "./types";
|
|
@@ -1,23 +1,158 @@
|
|
|
1
|
-
import
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import {
|
|
3
|
+
Children,
|
|
4
|
+
cloneElement,
|
|
5
|
+
createElement,
|
|
6
|
+
Fragment,
|
|
7
|
+
forwardRef,
|
|
8
|
+
type ElementType,
|
|
9
|
+
type ReactElement,
|
|
10
|
+
type Ref,
|
|
11
|
+
} from "react";
|
|
2
12
|
import type {
|
|
3
13
|
PolymorphicRef,
|
|
4
14
|
SlotComponentProps,
|
|
5
15
|
SlotComponentType,
|
|
16
|
+
SlotElementProps,
|
|
6
17
|
} from "../types/props";
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
|
-
*
|
|
20
|
+
* Ref utility; callback 또는 object ref에 현재 node를 전달한다.
|
|
21
|
+
* @function
|
|
22
|
+
* @param {React.Ref<unknown> | undefined} ref 갱신할 callback 또는 object ref.
|
|
23
|
+
* @param {unknown} node ref에 전달할 현재 node 또는 null.
|
|
24
|
+
* @desc callback ref가 반환한 React 19 cleanup을 그대로 보존하고 object ref는 current를 직접 갱신한다.
|
|
25
|
+
* @return {void | (() => void)} callback cleanup이 있으면 해당 함수, 그 외에는 void.
|
|
26
|
+
*/
|
|
27
|
+
const setRef = (
|
|
28
|
+
ref: Ref<unknown> | undefined,
|
|
29
|
+
node: unknown,
|
|
30
|
+
): void | (() => void) => {
|
|
31
|
+
if (typeof ref === "function") return ref(node);
|
|
32
|
+
if (ref && typeof ref === "object") {
|
|
33
|
+
(ref as { current: unknown }).current = node;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ref utility; child ref와 전달된 ref를 하나의 callback ref로 병합한다.
|
|
39
|
+
* @function
|
|
40
|
+
* @param {Array<React.Ref<unknown> | undefined>} refs 함께 갱신할 callback 또는 object ref 목록.
|
|
41
|
+
* @desc React 19 callback cleanup이 있으면 cleanup 없는 나머지 ref도 같은
|
|
42
|
+
* unmount에서 null로 해제한다.
|
|
43
|
+
* @return {React.RefCallback<unknown> | undefined} ref가 없으면 undefined, 있으면 병합된 callback ref.
|
|
44
|
+
*/
|
|
45
|
+
const mergeRefs = (...refs: (Ref<unknown> | undefined)[]) => {
|
|
46
|
+
if (refs.every(ref => ref == null)) return undefined;
|
|
47
|
+
|
|
48
|
+
return (node: unknown) => {
|
|
49
|
+
const cleanups = refs.map(ref => setRef(ref, node));
|
|
50
|
+
|
|
51
|
+
// React 19 callback ref 하나라도 cleanup을 반환하면 나머지 ref도 같은 unmount에서 해제한다.
|
|
52
|
+
if (!cleanups.some(cleanup => typeof cleanup === "function")) return;
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
cleanups.forEach((cleanup, index) => {
|
|
56
|
+
if (typeof cleanup === "function") cleanup();
|
|
57
|
+
else setRef(refs[index], null);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Props utility; Slot과 child가 소유한 props를 asChild 계약에 따라 병합한다.
|
|
65
|
+
* @function
|
|
66
|
+
* @param {SlotElementProps} slotProps Slot.Base에 전달된 props.
|
|
67
|
+
* @param {SlotElementProps} childProps 단일 child element가 소유한 props.
|
|
68
|
+
* @desc
|
|
69
|
+
* - 일반 props와 같은 style property는 child가 우선하고, className은 Slot 다음
|
|
70
|
+
* child 순서로 합친다.
|
|
71
|
+
* - 같은 event handler는 모든 인자를 child, Slot 순서로 전달하고 child 반환값을
|
|
72
|
+
* 보존한다. 첫 번째 인자의 defaultPrevented가 true일 때만 Slot handler를 생략한다.
|
|
73
|
+
* @return {SlotElementProps} child에 전달할 병합 props.
|
|
74
|
+
*/
|
|
75
|
+
const mergeSlotProps = (
|
|
76
|
+
slotProps: SlotElementProps,
|
|
77
|
+
childProps: SlotElementProps,
|
|
78
|
+
): SlotElementProps => {
|
|
79
|
+
const mergedProps: SlotElementProps = { ...slotProps, ...childProps };
|
|
80
|
+
|
|
81
|
+
Object.keys(slotProps).forEach(key => {
|
|
82
|
+
const slotHandler = slotProps[key];
|
|
83
|
+
const childHandler = childProps[key];
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
/^on[A-Z]/.test(key) &&
|
|
87
|
+
typeof slotHandler === "function" &&
|
|
88
|
+
typeof childHandler === "function"
|
|
89
|
+
) {
|
|
90
|
+
mergedProps[key] = (...args: unknown[]) => {
|
|
91
|
+
const result = childHandler(...args);
|
|
92
|
+
const event = args[0];
|
|
93
|
+
const defaultPrevented =
|
|
94
|
+
typeof event === "object" &&
|
|
95
|
+
event !== null &&
|
|
96
|
+
"defaultPrevented" in event &&
|
|
97
|
+
event.defaultPrevented === true;
|
|
98
|
+
|
|
99
|
+
if (!defaultPrevented) slotHandler(...args);
|
|
100
|
+
return result;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
mergedProps.className =
|
|
106
|
+
clsx(slotProps.className, childProps.className) || undefined;
|
|
107
|
+
if (slotProps.style || childProps.style) {
|
|
108
|
+
mergedProps.style = { ...slotProps.style, ...childProps.style };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return mergedProps;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* SlotComponent; as로 요소를 교체하거나 asChild로 단일 child를 직접 렌더링하는 추상화.
|
|
10
116
|
* @component
|
|
11
|
-
* @
|
|
12
|
-
* @
|
|
13
|
-
* @
|
|
14
|
-
* @
|
|
117
|
+
* @property {ElementType} [as="div"] asChild가 false일 때 교체할 요소.
|
|
118
|
+
* @property {true | false} [asChild=false] true이면 단일 child를 wrapper 없이 렌더링하고, false이면 as 요소를 렌더링한다.
|
|
119
|
+
* @property {React.ReactElement | React.ReactNode} [children] asChild가 true이면 필수인 단일 non-Fragment ReactElement, false이면 선택적인 ReactNode.
|
|
120
|
+
* @property {string} [className] 추가 className.
|
|
121
|
+
* @param {React.Ref<unknown>} [ref] 렌더링된 요소에 병합할 ref.
|
|
122
|
+
* @desc
|
|
123
|
+
* - asChild에서는 child의 일반 props를 우선하고 Slot className 다음에 child className을 합치며, 같은 style property는 child가 우선한다.
|
|
124
|
+
* - 같은 event handler는 모든 인자를 child, Slot 순서로 전달하고 child 반환값을 보존하며, 첫 번째 인자의 defaultPrevented가 true일 때만 Slot handler를 생략한다.
|
|
125
|
+
* - child ref와 전달된 ref를 합치며 React 19 callback cleanup이 있으면 cleanup 없는 나머지 ref는 null로 해제한다.
|
|
126
|
+
* - asChild의 child가 단일 non-Fragment ReactElement가 아니면 예외를 던진다.
|
|
127
|
+
* @return {React.ReactNode} 렌더링된 노드.
|
|
15
128
|
*/
|
|
16
129
|
const SlotComponent = forwardRef(
|
|
17
130
|
(
|
|
18
|
-
{
|
|
131
|
+
{
|
|
132
|
+
as,
|
|
133
|
+
asChild,
|
|
134
|
+
children,
|
|
135
|
+
className,
|
|
136
|
+
...rest
|
|
137
|
+
}: SlotComponentProps<ElementType>,
|
|
19
138
|
ref?: PolymorphicRef<ElementType>,
|
|
20
139
|
) => {
|
|
140
|
+
if (asChild) {
|
|
141
|
+
const child = Children.only(children) as ReactElement<SlotElementProps>;
|
|
142
|
+
|
|
143
|
+
if (child.type === Fragment) {
|
|
144
|
+
throw new TypeError(
|
|
145
|
+
"Slot.Base asChild requires one non-Fragment React element.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const childProps = child.props as SlotElementProps;
|
|
150
|
+
return cloneElement(child, {
|
|
151
|
+
...mergeSlotProps({ ...rest, className }, childProps),
|
|
152
|
+
ref: mergeRefs(childProps.ref, ref as Ref<unknown>),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
21
156
|
const Component = (as ?? "div") as ElementType;
|
|
22
157
|
return createElement(
|
|
23
158
|
Component,
|
|
@@ -29,6 +164,8 @@ const SlotComponent = forwardRef(
|
|
|
29
164
|
children,
|
|
30
165
|
);
|
|
31
166
|
},
|
|
32
|
-
)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
SlotComponent.displayName = "SlotBase";
|
|
33
170
|
|
|
34
|
-
export default SlotComponent;
|
|
171
|
+
export default SlotComponent as unknown as SlotComponentType;
|
|
@@ -2,10 +2,8 @@ import SlotBase from "./Base";
|
|
|
2
2
|
import SlotText from "./Text";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Slot;
|
|
6
|
-
* @desc
|
|
7
|
-
* - `Slot.Base`: as 기반 polymorphic 마크업 래퍼다.
|
|
8
|
-
* - `Slot.Text`: 텍스트 children만 래핑하는 공통 슬롯이다.
|
|
5
|
+
* Slot; public markup namespace
|
|
6
|
+
* @desc Slot markup component의 public entry다.
|
|
9
7
|
*/
|
|
10
8
|
export const Slot = {
|
|
11
9
|
Base: SlotBase,
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
CSSProperties,
|
|
2
3
|
ComponentPropsWithRef,
|
|
3
4
|
ComponentPropsWithoutRef,
|
|
4
5
|
ElementType,
|
|
6
|
+
HTMLAttributes,
|
|
7
|
+
ReactElement,
|
|
5
8
|
ReactNode,
|
|
9
|
+
Ref,
|
|
6
10
|
} from "react";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
|
-
* SlotComponentRestProps; as/children/className을 제외한 원본 요소 props.
|
|
13
|
+
* SlotComponentRestProps; as/asChild/children/className을 제외한 원본 요소 props.
|
|
10
14
|
* @typedef SlotComponentRestProps
|
|
11
15
|
* @template C
|
|
12
16
|
* @desc
|
|
13
17
|
* - HTML/React 공통 속성과 data-* 커스텀 속성을 그대로 포함한다.
|
|
14
|
-
* - SlotComponent 내부에서 as/children/className을 재정의하기 위해 제외한다.
|
|
18
|
+
* - SlotComponent 내부에서 as/asChild/children/className을 재정의하기 위해 제외한다.
|
|
15
19
|
*/
|
|
16
20
|
export type SlotComponentRestProps<C extends ElementType> = Omit<
|
|
17
21
|
ComponentPropsWithoutRef<C>,
|
|
18
|
-
"as" | "children" | "className"
|
|
22
|
+
"as" | "asChild" | "children" | "className"
|
|
19
23
|
>;
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
|
-
* PolymorphicRef;
|
|
26
|
+
* PolymorphicRef; as로 렌더링된 요소의 ref 타입.
|
|
23
27
|
* @typedef PolymorphicRef
|
|
24
28
|
* @template C
|
|
25
29
|
*/
|
|
@@ -27,40 +31,132 @@ export type PolymorphicRef<C extends ElementType> =
|
|
|
27
31
|
ComponentPropsWithRef<C>["ref"];
|
|
28
32
|
|
|
29
33
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @
|
|
34
|
+
* SlotElementProps; asChild 병합에 사용하는 element props
|
|
35
|
+
* @property {string} [className] 병합할 className.
|
|
36
|
+
* @property {React.Ref<unknown>} [ref] 병합할 callback 또는 object ref.
|
|
37
|
+
* @property {React.CSSProperties} [style] 병합할 inline style.
|
|
38
|
+
* @desc Slot과 child의 임의 props를 보존하면서 className, ref와 style 병합 계약을 명시한다.
|
|
39
|
+
*/
|
|
40
|
+
export type SlotElementProps = Record<string, unknown> & {
|
|
41
|
+
/**
|
|
42
|
+
* 병합할 className.
|
|
43
|
+
*/
|
|
44
|
+
className?: string;
|
|
45
|
+
/**
|
|
46
|
+
* 병합할 callback 또는 object ref.
|
|
47
|
+
*/
|
|
48
|
+
ref?: Ref<unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* 병합할 inline style.
|
|
51
|
+
*/
|
|
52
|
+
style?: CSSProperties;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* SlotAsChildProps; 단일 child를 wrapper 없이 렌더링하는 Slot props
|
|
57
|
+
* @property {true} asChild wrapper 제거를 활성화하는 discriminator.
|
|
58
|
+
* @property {never} [as] asChild variant에서 허용하지 않는 렌더 요소 override.
|
|
59
|
+
* @property {React.ReactElement} children props와 ref를 병합할 단일 non-Fragment ReactElement.
|
|
60
|
+
* @property {string} [className] Slot className 다음에 child className을 합칠 값.
|
|
61
|
+
* @desc React.HTMLAttributes<HTMLElement>를 Slot-side 공통 속성으로 제공하고 element 전용 속성은 child가 소유한다.
|
|
62
|
+
*/
|
|
63
|
+
export type SlotAsChildProps = Omit<
|
|
64
|
+
HTMLAttributes<HTMLElement>,
|
|
65
|
+
"children" | "className"
|
|
66
|
+
> & {
|
|
67
|
+
/**
|
|
68
|
+
* literal true이면 단일 ReactElement를 wrapper 없이 렌더링한다.
|
|
69
|
+
*/
|
|
70
|
+
asChild: true;
|
|
71
|
+
/**
|
|
72
|
+
* asChild가 true이면 as를 함께 사용할 수 없다.
|
|
73
|
+
*/
|
|
74
|
+
as?: never;
|
|
75
|
+
/**
|
|
76
|
+
* props를 병합할 단일 non-Fragment ReactElement. 그 외 입력은 runtime에서 예외를 던진다.
|
|
77
|
+
*/
|
|
78
|
+
children: ReactElement;
|
|
79
|
+
/**
|
|
80
|
+
* Slot className 다음에 child className을 합친다.
|
|
81
|
+
*/
|
|
82
|
+
className?: string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* SlotAsElementProps; as 요소를 렌더링하는 Slot props
|
|
32
87
|
* @template C
|
|
33
|
-
* @property {
|
|
34
|
-
* @property {
|
|
35
|
-
* @property {
|
|
36
|
-
* @
|
|
37
|
-
*
|
|
38
|
-
* - as/children/className은 Slot에서 직접 제어하기 위해 재정의한다.
|
|
88
|
+
* @property {false} [asChild] as 기반 렌더링을 유지하는 discriminator.
|
|
89
|
+
* @property {C} [as="div"] 렌더링할 요소.
|
|
90
|
+
* @property {React.ReactNode} [children] 렌더링할 children.
|
|
91
|
+
* @property {string} [className] 렌더링한 요소에 전달할 className.
|
|
92
|
+
* @desc SlotComponentRestProps<C>를 확장해 as 요소의 native props를 보존한다.
|
|
39
93
|
*/
|
|
40
|
-
export type
|
|
94
|
+
export type SlotAsElementProps<C extends ElementType> =
|
|
41
95
|
SlotComponentRestProps<C> & {
|
|
42
96
|
/**
|
|
43
|
-
*
|
|
97
|
+
* literal false 또는 생략이면 as 요소를 렌더링한다.
|
|
98
|
+
*/
|
|
99
|
+
asChild?: false;
|
|
100
|
+
/**
|
|
101
|
+
* 교체할 요소. 기본은 div.
|
|
44
102
|
*/
|
|
45
103
|
as?: C;
|
|
46
104
|
/**
|
|
47
|
-
* 전달할
|
|
105
|
+
* 전달할 ReactNode.
|
|
48
106
|
*/
|
|
49
107
|
children?: ReactNode;
|
|
50
108
|
/**
|
|
51
|
-
*
|
|
109
|
+
* 렌더링한 요소에 전달할 className.
|
|
52
110
|
*/
|
|
53
111
|
className?: string;
|
|
54
112
|
};
|
|
55
113
|
|
|
114
|
+
/**
|
|
115
|
+
* SlotComponentProps; as 또는 asChild 기반 슬롯 렌더링 props.
|
|
116
|
+
* @typedef SlotComponentProps
|
|
117
|
+
* @template C
|
|
118
|
+
* @property {ElementType} [as] asChild가 false일 때 렌더링할 요소. 기본값은 div이며 true variant에서는 허용하지 않는다.
|
|
119
|
+
* @property {true | false} [asChild] true이면 단일 ReactElement를 wrapper 없이 렌더링하고, false 또는 생략이면 as 요소를 렌더링한다.
|
|
120
|
+
* @property {React.ReactElement | React.ReactNode} [children] asChild가 true이면 필수인 단일 non-Fragment ReactElement, false 또는 생략이면 선택적인 ReactNode.
|
|
121
|
+
* @property {string} [className] 두 variant의 공통 className. asChild에서는 Slot className 다음에 child className을 합친다.
|
|
122
|
+
* @desc
|
|
123
|
+
* - asChild가 true이면 Slot-side props는 React.HTMLAttributes<HTMLElement>로 제한하고, anchor/button 같은 element 전용 props는 child에 둔다.
|
|
124
|
+
* - asChild가 false이면 ComponentPropsWithoutRef<C>를 확장하므로 as 요소의 React/HTML 속성을 전달한다.
|
|
125
|
+
* - as/asChild/children/className은 Slot에서 직접 제어하기 위해 재정의한다.
|
|
126
|
+
* - asChild와 as는 함께 사용할 수 없다.
|
|
127
|
+
* - asChild의 일반 props와 같은 style property는 child가 우선한다.
|
|
128
|
+
* - 같은 event handler는 모든 인자를 child, Slot 순서로 전달하고 child 반환값을 보존하며, 첫 번째 인자의 defaultPrevented가 true일 때만 Slot handler를 생략한다.
|
|
129
|
+
* - child ref와 전달된 ref를 합치며 React 19 callback cleanup이 있으면 cleanup 없는 나머지 ref는 null로 해제한다.
|
|
130
|
+
*/
|
|
131
|
+
export type SlotComponentProps<C extends ElementType> =
|
|
132
|
+
SlotAsChildProps | SlotAsElementProps<C>;
|
|
133
|
+
|
|
56
134
|
/**
|
|
57
135
|
* SlotComponentType; SlotComponent의 forwardRef 타입 시그니처.
|
|
58
136
|
* @typedef SlotComponentType
|
|
59
|
-
* @
|
|
60
|
-
* @param {SlotComponentProps<C>} props 공통 슬롯 props.
|
|
61
|
-
* @param {PolymorphicRef<C>} [props.ref] 전달될 ref.
|
|
62
|
-
* @returns {React.ReactNode} 렌더링된 노드.
|
|
137
|
+
* @desc asChild와 as variant의 public callable contract를 각각 제공한다.
|
|
63
138
|
*/
|
|
64
|
-
export type SlotComponentType =
|
|
65
|
-
|
|
66
|
-
|
|
139
|
+
export type SlotComponentType = {
|
|
140
|
+
/**
|
|
141
|
+
* SlotComponentType; asChild로 단일 HTML child를 렌더링하는 signature.
|
|
142
|
+
* @property {true} asChild wrapper 제거를 활성화한다.
|
|
143
|
+
* @property {React.ReactElement} children props와 ref를 병합할 단일 non-Fragment ReactElement.
|
|
144
|
+
* @property {React.Ref<HTMLElement>} [ref] 실제 HTML element까지 전달할 ref.
|
|
145
|
+
* @desc Slot-side props는 표준 HTMLAttributes로 제한하고 element 전용 props는 child가 소유한다.
|
|
146
|
+
* @return {React.ReactNode} 병합된 단일 child.
|
|
147
|
+
*/
|
|
148
|
+
(props: SlotAsChildProps & { ref?: Ref<HTMLElement> }): ReactNode;
|
|
149
|
+
/**
|
|
150
|
+
* SlotComponentType; as로 렌더링 요소를 교체하는 signature.
|
|
151
|
+
* @template C
|
|
152
|
+
* @property {C} [as="div"] 렌더링할 요소.
|
|
153
|
+
* @property {false} [asChild=false] as 기반 렌더링을 유지한다.
|
|
154
|
+
* @property {React.ReactNode} [children] 렌더링할 children.
|
|
155
|
+
* @property {PolymorphicRef<C>} [ref] as 요소에 전달할 ref.
|
|
156
|
+
* @desc as 요소의 ComponentPropsWithoutRef<C>와 ref type을 보존한다.
|
|
157
|
+
* @return {React.ReactNode} as 요소로 렌더링된 노드.
|
|
158
|
+
*/
|
|
159
|
+
<C extends ElementType = "div">(
|
|
160
|
+
props: SlotAsElementProps<C> & { ref?: PolymorphicRef<C> },
|
|
161
|
+
): ReactNode;
|
|
162
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ElementType } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SlotComponentRestProps, SlotTextProps } from "../../slot";
|
|
3
3
|
|
|
4
4
|
export const TABLE_CELL_ALIGN_OPTIONS = ["left", "center", "right"] as const;
|
|
5
5
|
export const TABLE_CELL_ALIGN_Y_OPTIONS = ["top", "center", "bottom"] as const;
|
|
@@ -155,7 +155,7 @@ export interface TableColumnData<
|
|
|
155
155
|
* @property {boolean} [scrollable=false] true면 외부 스크롤 래퍼를 추가한다.
|
|
156
156
|
* @property {"x" | "y" | "both"} [scrollAxis="x"] scrollable일 때 스크롤 축
|
|
157
157
|
* @property {ElementType} [scrollAs="div"] scrollable wrapper element
|
|
158
|
-
* @property {
|
|
158
|
+
* @property {SlotComponentRestProps<ElementType> & { asChild?: never }} [scrollProps] asChild를 허용하지 않아 wrapper element를 보존하는 scrollable wrapper native props
|
|
159
159
|
* @property {string} [scrollClassName] 스크롤 래퍼 className
|
|
160
160
|
* @property {React.ReactNode} [footer] footer 노드
|
|
161
161
|
* @property {React.ReactNode} [children] body 콘텐츠
|
|
@@ -184,12 +184,14 @@ export interface TableContainerProps<
|
|
|
184
184
|
*/
|
|
185
185
|
scrollAs?: ElementType;
|
|
186
186
|
/**
|
|
187
|
-
* scrollable wrapper native props
|
|
187
|
+
* asChild를 허용하지 않아 wrapper element를 보존하는 scrollable wrapper native props
|
|
188
188
|
*/
|
|
189
|
-
scrollProps?:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
scrollProps?: SlotComponentRestProps<ElementType> & {
|
|
190
|
+
/**
|
|
191
|
+
* Table scroll wrapper에서는 wrapper 제거를 허용하지 않는다.
|
|
192
|
+
*/
|
|
193
|
+
asChild?: never;
|
|
194
|
+
};
|
|
193
195
|
/**
|
|
194
196
|
* 스크롤 래퍼 className
|
|
195
197
|
*/
|
package/src/index.scss
CHANGED
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
@use "./components/pagination";
|
|
16
16
|
@use "./components/pop-over";
|
|
17
17
|
@use "./components/radio";
|
|
18
|
-
@use "./components/scrollbar";
|
|
19
18
|
@use "./components/segmented-control";
|
|
20
19
|
@use "./components/select";
|
|
21
|
-
@use "./components/spinner";
|
|
22
20
|
@use "./components/switch";
|
|
23
21
|
@use "./components/tab";
|
|
24
22
|
@use "./components/table";
|
package/src/index.tsx
CHANGED
|
@@ -20,15 +20,21 @@ export * from "./components/navigation";
|
|
|
20
20
|
export * from "./components/pagination";
|
|
21
21
|
export * from "./components/pop-over";
|
|
22
22
|
export * from "./components/radio";
|
|
23
|
-
export * from "./components/scrollbar";
|
|
24
23
|
export * from "./components/segmented-control";
|
|
25
24
|
export * from "./components/select";
|
|
26
25
|
export * from "./components/slot";
|
|
27
|
-
export * from "./components/spinner";
|
|
28
26
|
export * from "./components/switch";
|
|
29
27
|
export * from "./components/tab";
|
|
30
28
|
export * from "./components/table";
|
|
31
29
|
export * from "./components/time-picker";
|
|
32
30
|
export * from "./components/toast";
|
|
33
31
|
export * from "./components/tooltip";
|
|
32
|
+
export {
|
|
33
|
+
isSameSelectedValue,
|
|
34
|
+
isSameSelectedValueList,
|
|
35
|
+
normalizeSelectedValuesByMode,
|
|
36
|
+
normalizeSingleSelectedValue,
|
|
37
|
+
toSelectedValueKey,
|
|
38
|
+
} from "./utils";
|
|
34
39
|
export type * from "./types";
|
|
40
|
+
export type { SelectedValue } from "./utils";
|
package/src/types/index.ts
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export {
|
|
2
|
+
isSameSelectedValue,
|
|
3
|
+
isSameSelectedValueList,
|
|
4
|
+
normalizeSelectedValuesByMode,
|
|
5
|
+
normalizeSingleSelectedValue,
|
|
6
|
+
toSelectedValueKey,
|
|
7
|
+
} from "./selected-values";
|
|
8
|
+
export type { SelectedValue } from "./selected-values";
|