@uniai-fe/uds-primitives 0.3.60 → 0.4.0
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 +209 -3
- package/dist/styles.css +20 -6
- package/package.json +1 -1
- package/src/components/alternate/index.tsx +7 -1
- package/src/components/alternate/markup/Label.tsx +10 -5
- package/src/components/alternate/markup/empty/Data.tsx +9 -6
- package/src/components/alternate/markup/index.tsx +8 -0
- package/src/components/alternate/markup/loading/Default.tsx +10 -6
- package/src/components/alternate/markup/loading/Icon.tsx +11 -4
- package/src/components/alternate/types/index.ts +75 -2
- package/src/components/badge/index.tsx +4 -1
- package/src/components/badge/markup/Badge.tsx +10 -8
- package/src/components/badge/types/index.ts +26 -2
- package/src/components/button/index.tsx +6 -1
- package/src/components/button/markup/Base.tsx +20 -18
- package/src/components/button/markup/Rounded.tsx +7 -4
- package/src/components/button/markup/Text.tsx +7 -4
- package/src/components/calendar/index.tsx +8 -0
- package/src/components/calendar/markup/index.tsx +7 -7
- package/src/components/carousel/index.tsx +8 -0
- package/src/components/carousel/markup/index.tsx +9 -0
- package/src/components/checkbox/index.tsx +7 -0
- package/src/components/chip/index.tsx +7 -1
- package/src/components/chip/markup/index.tsx +9 -0
- package/src/components/divider/index.tsx +4 -0
- package/src/components/divider/markup/Divider.tsx +11 -7
- package/src/components/divider/types/index.ts +1 -0
- package/src/components/divider/types/props.ts +27 -0
- package/src/components/drawer/index.tsx +7 -0
- package/src/components/drawer/markup/index.tsx +6 -0
- package/src/components/dropdown/index.tsx +7 -0
- package/src/components/dropdown/markup/Template.tsx +9 -2
- package/src/components/dropdown/markup/foundation/Container.tsx +30 -12
- package/src/components/dropdown/markup/index.tsx +9 -10
- package/src/components/dropdown/types/base.ts +13 -0
- package/src/components/dropdown/types/props.ts +19 -2
- package/src/components/form/index.tsx +7 -0
- package/src/components/form/markup/index.tsx +6 -2
- package/src/components/info-box/index.tsx +7 -0
- package/src/components/info-box/markup/InfoBox.tsx +1 -1
- package/src/components/info-box/markup/index.ts +6 -0
- package/src/components/info-box/types/props.ts +2 -2
- package/src/components/input/index.tsx +6 -1
- package/src/components/input/markup/foundation/Input.tsx +2 -2
- package/src/components/input/types/foundation.ts +1 -1
- package/src/components/navigation/index.tsx +7 -0
- package/src/components/navigation/markup/index.tsx +6 -0
- package/src/components/pagination/index.tsx +6 -1
- package/src/components/pagination/markup/index.tsx +7 -0
- package/src/components/pop-over/index.tsx +7 -0
- package/src/components/pop-over/markup/index.tsx +5 -4
- package/src/components/radio/index.tsx +5 -1
- package/src/components/scrollbar/hooks/index.ts +1 -1
- package/src/components/scrollbar/index.tsx +1 -1
- package/src/components/scrollbar/markup/index.tsx +1 -1
- package/src/components/scrollbar/types/index.ts +1 -1
- package/src/components/scrollbar/utils/index.ts +1 -1
- package/src/components/segmented-control/index.tsx +5 -1
- package/src/components/segmented-control/markup/index.ts +6 -0
- package/src/components/select/index.tsx +6 -1
- package/src/components/select/markup/Default.tsx +10 -13
- package/src/components/select/markup/foundation/Selected.tsx +31 -26
- package/src/components/select/markup/index.tsx +1 -1
- package/src/components/select/markup/multiple/Multiple.tsx +32 -15
- package/src/components/select/styles/select.scss +15 -6
- package/src/components/select/styles/variables.scss +4 -0
- package/src/components/select/types/multiple.ts +19 -0
- package/src/components/select/types/props.ts +19 -6
- package/src/components/select/utils/display.tsx +41 -0
- package/src/components/select/utils/index.ts +1 -4
- package/src/components/slot/index.tsx +7 -0
- package/src/components/slot/markup/index.tsx +6 -0
- package/src/components/spinner/hooks/index.ts +1 -1
- package/src/components/spinner/index.tsx +1 -1
- package/src/components/spinner/markup/index.tsx +1 -1
- package/src/components/spinner/types/index.ts +1 -1
- package/src/components/spinner/utils/index.ts +1 -1
- package/src/components/tab/index.tsx +5 -1
- package/src/components/tab/markup/index.tsx +8 -0
- package/src/components/table/index.tsx +3 -0
- package/src/components/tooltip/index.tsx +7 -0
- package/src/components/tooltip/markup/index.tsx +7 -6
|
@@ -102,7 +102,7 @@ export interface InputIcon {
|
|
|
102
102
|
* @property {ReactNode} [clear] input reset버튼 커스텀 컨텐츠
|
|
103
103
|
* @property {ReactNode} [success] input 입력상태 성공시 커스텀 컨텐츠
|
|
104
104
|
* @property {ReactNode} [error] input 입력상태 에러시 커스텀 컨텐츠
|
|
105
|
-
* @property {
|
|
105
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 옵션
|
|
106
106
|
* @property {InputState} [data-simulated-state] Storybook 시각 상태 강제용
|
|
107
107
|
*/
|
|
108
108
|
export interface InputProps extends Omit<NativeInputProps, "size">, InputIcon {
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation; mobile bottom navigation 카테고리 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `BottomNavigation`: 모바일 하단 네비게이션 루트다.
|
|
5
|
+
* - `NavigationItem`, `NavigationItemKey`: item/public type 계약이다.
|
|
6
|
+
* - `composeNavigationClassName`, `isHrefNavigationItem`: navigation util이다.
|
|
7
|
+
*/
|
|
1
8
|
import "./index.scss";
|
|
2
9
|
|
|
3
10
|
export * from "./markup";
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pagination; list/carousel/count indicator 카테고리 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `Pagination`: 숫자 기반 페이지 이동 컨트롤이다.
|
|
5
|
+
* - `PaginationCarousel`: dot 기반 step indicator다.
|
|
6
|
+
* - `PaginationCount`: 현재/전체 step 카운트 표시다.
|
|
7
|
+
* - `normalizePaginationState`, `createPaginationPages`: pagination util이다.
|
|
3
8
|
*/
|
|
4
9
|
import "./index.scss";
|
|
5
10
|
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination; list/carousel/count markup export 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `Pagination`: list variant다.
|
|
5
|
+
* - `PaginationCarousel`: carousel(dot) variant다.
|
|
6
|
+
* - `PaginationCount`: count variant다.
|
|
7
|
+
*/
|
|
1
8
|
export { Pagination } from "./Pagination";
|
|
2
9
|
export { PaginationCarousel } from "./Carousel";
|
|
3
10
|
export { PaginationCount } from "./Count";
|
|
@@ -5,10 +5,11 @@ import PopOverTrigger from "./Trigger";
|
|
|
5
5
|
export { PopOverRoot, PopOverTrigger, PopOverContent };
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* PopOver
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
8
|
+
* PopOver; 컴포넌트 모듈
|
|
9
|
+
* @namespace PopOver
|
|
10
|
+
* - `PopOver.Root`
|
|
11
|
+
* - `PopOver.Trigger`
|
|
12
|
+
* - `PopOver.Content`
|
|
12
13
|
*/
|
|
13
14
|
export const PopOver = {
|
|
14
15
|
Root: PopOverRoot,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Radio; namespace 배럴 export
|
|
3
|
+
* @desc
|
|
4
|
+
* - `Radio`, `RadioField`: base radio primitives
|
|
5
|
+
* - `RadioCard`, `RadioCardGroup`: card variation
|
|
6
|
+
* - hooks/types/utils는 별도 배럴로 함께 export한다
|
|
3
7
|
*/
|
|
4
8
|
import "./index.scss";
|
|
5
9
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* segmented
|
|
2
|
+
* SegmentedControl; single-select segmented control 카테고리 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `SegmentedControl`: 옵션 배열 기반 단일 선택 루트다.
|
|
5
|
+
* - `SegmentedControlLabel`: 옵션 텍스트 슬롯이다.
|
|
6
|
+
* - `SegmentedControlProps`, `SegmentedControlOption`: public contract 타입이다.
|
|
3
7
|
*/
|
|
4
8
|
import "./index.scss";
|
|
5
9
|
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SegmentedControl; single-select segmented control markup export 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `SegmentedControl`: 옵션 배열 기반 단일 선택 루트다.
|
|
5
|
+
* - `SegmentedControlLabel`: 옵션 텍스트 슬롯이다.
|
|
6
|
+
*/
|
|
1
7
|
export * from "./Container";
|
|
2
8
|
export { default as SegmentedControlLabel } from "./Label";
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Select; namespace 배럴 export
|
|
3
|
+
* @desc
|
|
4
|
+
* - `Select.Default`: single select
|
|
5
|
+
* - `Select.Multiple`: multi select
|
|
6
|
+
* - `Select.Container`, `Select.Trigger.Base`, `Select.Selected.*`: headless 조합 레이어
|
|
7
|
+
* - hooks/types/utils는 별도 배럴로 함께 export한다
|
|
3
8
|
*/
|
|
4
9
|
import "./index.scss";
|
|
5
10
|
|
|
@@ -10,20 +10,13 @@ import Container from "./foundation/Container";
|
|
|
10
10
|
import { useSelectDropdownOpenState } from "../hooks";
|
|
11
11
|
import type { SelectDropdownOption } from "../types/option";
|
|
12
12
|
import type { SelectDefaultComponentProps } from "../types/props";
|
|
13
|
+
import { toSelectInputText } from "../utils";
|
|
13
14
|
import {
|
|
14
15
|
isSameSelectedValue,
|
|
15
16
|
normalizeSingleSelectedValue,
|
|
16
17
|
toSelectedValueKey,
|
|
17
18
|
} from "../../../utils/selected-values";
|
|
18
19
|
|
|
19
|
-
const toInputText = (value?: ReactNode): string => {
|
|
20
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
21
|
-
return String(value);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return "";
|
|
25
|
-
};
|
|
26
|
-
|
|
27
20
|
const SELECT_CUSTOM_OPTION_BASE_ID = "__select_custom_input__";
|
|
28
21
|
const SELECT_CUSTOM_OPTION_VALUE = "CUSTOM";
|
|
29
22
|
|
|
@@ -44,8 +37,8 @@ const SELECT_CUSTOM_OPTION_VALUE = "CUSTOM";
|
|
|
44
37
|
* @param {boolean} [props.icon] chevron 아이콘 비활성화 여부
|
|
45
38
|
* @param {"button" | "submit" | "reset"} [props.buttonType] trigger button type
|
|
46
39
|
* @param {SelectDropdownOption[]} props.items dropdown 아이템 목록
|
|
47
|
-
* @param {SelectCallbackParams} [props.onSelectOption] option interaction 콜백
|
|
48
|
-
* @param {SelectCallbackParams} [props.onSelectChange]
|
|
40
|
+
* @param {SelectCallbackParams} [props.onSelectOption] option interaction 콜백(legacy)
|
|
41
|
+
* @param {SelectCallbackParams} [props.onSelectChange] 권장 선택값 변경 콜백
|
|
49
42
|
* @param {SelectDropdownExtension} [props.dropdownOptions] dropdown 확장 옵션
|
|
50
43
|
* @param {boolean} [props.open] controlled open 값
|
|
51
44
|
* @param {boolean} [props.defaultOpen] uncontrolled 초기 open 값
|
|
@@ -163,7 +156,9 @@ export function SelectDefault<OptionData = unknown>({
|
|
|
163
156
|
);
|
|
164
157
|
|
|
165
158
|
// 9) customOptions가 있는 경우 inputProps.value가 있고 option 매칭이 없으면 custom mode로 간주한다.
|
|
166
|
-
const forcedCustomLabelValue =
|
|
159
|
+
const forcedCustomLabelValue = toSelectInputText(
|
|
160
|
+
inputProps?.value as ReactNode,
|
|
161
|
+
);
|
|
167
162
|
const shouldForceCustomFromInput = Boolean(
|
|
168
163
|
customOptions && !selectedOption && forcedCustomLabelValue,
|
|
169
164
|
);
|
|
@@ -262,7 +257,7 @@ export function SelectDefault<OptionData = unknown>({
|
|
|
262
257
|
typeof inputProps?.value === "number"
|
|
263
258
|
? String(inputProps.value)
|
|
264
259
|
: customLabelValue
|
|
265
|
-
:
|
|
260
|
+
: toSelectInputText(resolvedDisplayLabel);
|
|
266
261
|
|
|
267
262
|
// 17) 렌더: Container → Dropdown.Root → Trigger → Menu.List 구조를 유지한다.
|
|
268
263
|
return (
|
|
@@ -297,7 +292,8 @@ export function SelectDefault<OptionData = unknown>({
|
|
|
297
292
|
label={resolvedDisplayLabel}
|
|
298
293
|
placeholder={
|
|
299
294
|
isCustomInputActive
|
|
300
|
-
? (customOptions?.placeholder ??
|
|
295
|
+
? (customOptions?.placeholder ??
|
|
296
|
+
toSelectInputText(placeholder))
|
|
301
297
|
: placeholder
|
|
302
298
|
}
|
|
303
299
|
isPlaceholder={
|
|
@@ -329,6 +325,7 @@ export function SelectDefault<OptionData = unknown>({
|
|
|
329
325
|
{...dropdownOptions?.containerProps}
|
|
330
326
|
size={dropdownOptions?.size ?? resolvedSize}
|
|
331
327
|
width={dropdownOptions?.width ?? "match"}
|
|
328
|
+
minWidth={dropdownOptions?.minWidth}
|
|
332
329
|
>
|
|
333
330
|
<Dropdown.Menu.List {...dropdownOptions?.menuListProps}>
|
|
334
331
|
{mergedOptions.length > 0 ? (
|
|
@@ -4,14 +4,7 @@ import clsx from "clsx";
|
|
|
4
4
|
import { useEffect, useRef } from "react";
|
|
5
5
|
import type { ReactNode } from "react";
|
|
6
6
|
import type { SelectSelectedProps } from "../../types/trigger";
|
|
7
|
-
|
|
8
|
-
const toInputText = (value?: ReactNode): string => {
|
|
9
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
10
|
-
return String(value);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return "";
|
|
14
|
-
};
|
|
7
|
+
import { toSelectInputText } from "../../utils";
|
|
15
8
|
|
|
16
9
|
/**
|
|
17
10
|
* Select trigger value renderer; trigger 내부 label/value input 렌더링 컴포넌트
|
|
@@ -56,31 +49,38 @@ const SelectTriggerSelected = ({
|
|
|
56
49
|
const labelInputRef = useRef<HTMLInputElement>(null);
|
|
57
50
|
|
|
58
51
|
// 2) placeholder/label은 text input에 들어갈 수 있는 문자열로 정규화한다.
|
|
59
|
-
const resolvedPlaceholder =
|
|
52
|
+
const resolvedPlaceholder = toSelectInputText(placeholder);
|
|
60
53
|
// 변경 설명: readOnly + customOptions 조합에서 valueText가 비어도 inputProps.value를 표시 문자열로 사용한다.
|
|
61
|
-
const resolvedInputValueText =
|
|
54
|
+
const resolvedInputValueText = toSelectInputText(
|
|
55
|
+
inputProps?.value as ReactNode,
|
|
56
|
+
);
|
|
62
57
|
const resolvedLabelText =
|
|
63
|
-
valueText || resolvedInputValueText ||
|
|
58
|
+
valueText || resolvedInputValueText || toSelectInputText(label);
|
|
64
59
|
// 변경 설명: 색상 계약은 hidden value 또는 label value 중 지정된 source를 기준으로 계산한다.
|
|
65
60
|
const resolvedHiddenValueText =
|
|
66
61
|
typeof valueFieldValue === "string" || typeof valueFieldValue === "number"
|
|
67
62
|
? String(valueFieldValue)
|
|
68
63
|
: "";
|
|
64
|
+
const hasReadonlyLabelNode =
|
|
65
|
+
readOnly &&
|
|
66
|
+
!isPlaceholder &&
|
|
67
|
+
label !== undefined &&
|
|
68
|
+
label !== null &&
|
|
69
|
+
!resolvedLabelText;
|
|
69
70
|
const hasDisplayValue =
|
|
70
71
|
valueStateSource === "hidden"
|
|
71
72
|
? resolvedHiddenValueText.length > 0
|
|
72
|
-
: resolvedLabelText.length > 0;
|
|
73
|
+
: resolvedLabelText.length > 0 || hasReadonlyLabelNode;
|
|
73
74
|
const isLabelTextLike =
|
|
74
75
|
typeof label === "string" || typeof label === "number";
|
|
75
76
|
const shouldUsePlaceholderStyle = !hasDisplayValue;
|
|
76
|
-
// 변경 설명: readOnly
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
!isLabelTextLike &&
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
!resolvedLabelText;
|
|
77
|
+
// 변경 설명: readOnly 경로는 input 대신 표시 노드를 직접 렌더링해 fit-content가 실제 콘텐츠 폭을 기준으로 계산되게 한다.
|
|
78
|
+
const shouldRenderReadonlyView = readOnly;
|
|
79
|
+
const readonlyContent = shouldUsePlaceholderStyle
|
|
80
|
+
? resolvedPlaceholder
|
|
81
|
+
: !isLabelTextLike && !resolvedLabelText
|
|
82
|
+
? label
|
|
83
|
+
: resolvedLabelText;
|
|
84
84
|
|
|
85
85
|
// 3) custom mode 활성 시에만 label input focus를 부여한다.
|
|
86
86
|
useEffect(() => {
|
|
@@ -96,15 +96,20 @@ const SelectTriggerSelected = ({
|
|
|
96
96
|
className="select-value"
|
|
97
97
|
data-has-value={hasDisplayValue ? "true" : "false"}
|
|
98
98
|
>
|
|
99
|
-
{
|
|
100
|
-
// 변경 설명:
|
|
99
|
+
{shouldRenderReadonlyView ? (
|
|
100
|
+
// 변경 설명: readOnly 표시 경로는 text/ReactNode를 그대로 유지해 trigger width가 실제 표시 콘텐츠에 맞춰지도록 한다.
|
|
101
101
|
<div
|
|
102
|
-
className={clsx(
|
|
103
|
-
"select-input-label
|
|
104
|
-
|
|
102
|
+
className={clsx(
|
|
103
|
+
"select-input-label",
|
|
104
|
+
"select-input-label-readonly",
|
|
105
|
+
inputProps?.className,
|
|
106
|
+
{
|
|
107
|
+
"select-input-label-placeholder": shouldUsePlaceholderStyle,
|
|
108
|
+
},
|
|
109
|
+
)}
|
|
105
110
|
data-has-value={hasDisplayValue ? "true" : "false"}
|
|
106
111
|
>
|
|
107
|
-
{
|
|
112
|
+
{readonlyContent}
|
|
108
113
|
</div>
|
|
109
114
|
) : (
|
|
110
115
|
<input
|
|
@@ -4,7 +4,7 @@ import { SelectTriggerBase, SelectTriggerSelected } from "./foundation";
|
|
|
4
4
|
import SelectContainer from "./foundation/Container";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Select; 컴포넌트 모듈
|
|
8
8
|
* @namespace Select
|
|
9
9
|
* - <Select.Default />: Select 기본
|
|
10
10
|
* - <Select.Multiple />: Select multi select
|
|
@@ -11,6 +11,7 @@ import type { SelectDropdownOption } from "../../types/option";
|
|
|
11
11
|
import { SelectMultipleSelectedChip } from "./SelectedChip";
|
|
12
12
|
import { SelectTriggerBase, SelectTriggerSelected } from "../foundation";
|
|
13
13
|
import { useSelectDropdownOpenState } from "../../hooks";
|
|
14
|
+
import { renderCommaSeparatedSelectLabels } from "../../utils";
|
|
14
15
|
import {
|
|
15
16
|
isSameSelectedValue,
|
|
16
17
|
isSameSelectedValueList,
|
|
@@ -26,8 +27,8 @@ const SELECT_MULTIPLE_ALL_OPTION_BASE_ID = "__select_multiple_all__";
|
|
|
26
27
|
* @param {string} [props.className] container className
|
|
27
28
|
* @param {SelectMultipleTag[]} [props.tags] 선택된 tag 리스트
|
|
28
29
|
* @param {SelectDropdownOption[]} [props.items] dropdown item 목록
|
|
29
|
-
* @param {SelectCallbackParams} [props.onSelectOption] option 선택 액션 콜백
|
|
30
|
-
* @param {SelectCallbackParams} [props.onSelectChange] 선택값 변경 콜백
|
|
30
|
+
* @param {SelectCallbackParams} [props.onSelectOption] option 선택 액션 콜백(legacy)
|
|
31
|
+
* @param {SelectCallbackParams} [props.onSelectChange] 권장 선택값 변경 콜백
|
|
31
32
|
* @param {React.ReactNode} [props.displayLabel] fallback 라벨
|
|
32
33
|
* @param {React.ReactNode} [props.placeholder] placeholder 텍스트
|
|
33
34
|
* @param {"primary" | "secondary" | "table"} [props.priority="primary"] priority scale
|
|
@@ -45,6 +46,7 @@ const SELECT_MULTIPLE_ALL_OPTION_BASE_ID = "__select_multiple_all__";
|
|
|
45
46
|
* @param {(open: boolean) => void} [props.onOpen] open 상태 변경 콜백
|
|
46
47
|
* @param {boolean} [props.showSelectAllOption] dropdown 첫 번째에 "전체" 옵션 노출 여부
|
|
47
48
|
* @param {React.ReactNode} [props.selectAllLabel="전체"] 전체 옵션 라벨
|
|
49
|
+
* @param {"chip" | "text"} [props.displayMode="chip"] 선택값 표시 방식
|
|
48
50
|
*/
|
|
49
51
|
export function SelectMultipleTrigger<OptionData = unknown>({
|
|
50
52
|
className,
|
|
@@ -67,6 +69,7 @@ export function SelectMultipleTrigger<OptionData = unknown>({
|
|
|
67
69
|
onOpen,
|
|
68
70
|
showSelectAllOption,
|
|
69
71
|
selectAllLabel = "전체",
|
|
72
|
+
displayMode = "chip",
|
|
70
73
|
triggerProps,
|
|
71
74
|
}: SelectMultipleComponentProps<OptionData>) {
|
|
72
75
|
/**
|
|
@@ -208,11 +211,22 @@ export function SelectMultipleTrigger<OptionData = unknown>({
|
|
|
208
211
|
* - tags가 비어 있을 때 fallback label 용도로 사용한다.
|
|
209
212
|
* - 외부 displayLabel 우선, 없으면 첫 선택 option label을 사용한다.
|
|
210
213
|
*/
|
|
214
|
+
const selectedOptions = useMemo(
|
|
215
|
+
() =>
|
|
216
|
+
resolvedSelectedValues
|
|
217
|
+
.map(selectedValue => optionMap.get(toSelectedValueKey(selectedValue)))
|
|
218
|
+
.filter((option): option is NonNullable<typeof option> =>
|
|
219
|
+
Boolean(option),
|
|
220
|
+
),
|
|
221
|
+
[optionMap, resolvedSelectedValues],
|
|
222
|
+
);
|
|
211
223
|
const resolvedDisplayLabel =
|
|
212
224
|
displayLabel ??
|
|
213
|
-
(
|
|
214
|
-
?
|
|
215
|
-
:
|
|
225
|
+
(displayMode === "text"
|
|
226
|
+
? renderCommaSeparatedSelectLabels(selectedOptions)
|
|
227
|
+
: selectedOptions.length > 0
|
|
228
|
+
? selectedOptions[0]?.label
|
|
229
|
+
: undefined);
|
|
216
230
|
|
|
217
231
|
/**
|
|
218
232
|
* 9) tag 파생 계산
|
|
@@ -224,24 +238,23 @@ export function SelectMultipleTrigger<OptionData = unknown>({
|
|
|
224
238
|
return tags;
|
|
225
239
|
}
|
|
226
240
|
|
|
227
|
-
if (
|
|
241
|
+
if (selectedOptions.length === 0) {
|
|
228
242
|
return [];
|
|
229
243
|
}
|
|
230
244
|
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
removable: false,
|
|
237
|
-
}));
|
|
238
|
-
}, [tags, resolvedSelectedValues, optionMap]);
|
|
245
|
+
return selectedOptions.map(option => ({
|
|
246
|
+
label: option.label,
|
|
247
|
+
removable: false,
|
|
248
|
+
}));
|
|
249
|
+
}, [tags, selectedOptions]);
|
|
239
250
|
|
|
240
251
|
/**
|
|
241
252
|
* 10) placeholder/label 표시 상태 계산
|
|
242
253
|
* - label 값이 비어 있으면 placeholder 표시로 간주한다.
|
|
243
254
|
*/
|
|
244
255
|
const hasTags = derivedTags.length > 0;
|
|
256
|
+
const shouldRenderTextDisplay =
|
|
257
|
+
displayMode === "text" && selectedOptions.length > 0;
|
|
245
258
|
|
|
246
259
|
/**
|
|
247
260
|
* 11) dropdown open 상태 관리
|
|
@@ -361,6 +374,9 @@ export function SelectMultipleTrigger<OptionData = unknown>({
|
|
|
361
374
|
className={clsx("select-trigger-multiple", className)}
|
|
362
375
|
block={resolvedBlock}
|
|
363
376
|
width={width}
|
|
377
|
+
priority={priority}
|
|
378
|
+
size={resolvedSize}
|
|
379
|
+
state={disabled ? "disabled" : state}
|
|
364
380
|
>
|
|
365
381
|
<Dropdown.Root
|
|
366
382
|
open={dropdownOpen}
|
|
@@ -382,7 +398,7 @@ export function SelectMultipleTrigger<OptionData = unknown>({
|
|
|
382
398
|
as="div"
|
|
383
399
|
{...triggerProps}
|
|
384
400
|
>
|
|
385
|
-
{hasTags ? (
|
|
401
|
+
{hasTags && !shouldRenderTextDisplay ? (
|
|
386
402
|
<div className="select-tags">
|
|
387
403
|
{visibleTags.map(
|
|
388
404
|
({ label, suffix, removable, onRemove }, index) => (
|
|
@@ -422,6 +438,7 @@ export function SelectMultipleTrigger<OptionData = unknown>({
|
|
|
422
438
|
{...dropdown?.containerProps}
|
|
423
439
|
size={dropdown?.size ?? resolvedSize}
|
|
424
440
|
width={dropdown?.width ?? "match"}
|
|
441
|
+
minWidth={dropdown?.minWidth}
|
|
425
442
|
>
|
|
426
443
|
<Dropdown.Menu.List {...dropdown?.menuListProps}>
|
|
427
444
|
{hasOptions ? (
|
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
--select-flex: 1 1 0%;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
.select[data-width="full"]
|
|
29
|
+
.select:where([data-width="full"]) {
|
|
30
|
+
--select-width: 100%;
|
|
31
|
+
--select-flex: 0 0 100%;
|
|
32
|
+
}
|
|
30
33
|
.select-block {
|
|
31
34
|
--select-width: 100%;
|
|
32
35
|
--select-flex: 0 0 100%;
|
|
@@ -177,13 +180,14 @@
|
|
|
177
180
|
border-color: var(--select-primary-color-border-disabled);
|
|
178
181
|
background-color: var(--select-primary-color-surface-disabled);
|
|
179
182
|
cursor: default;
|
|
180
|
-
--select-icon-fill: var(--select-icon-color-
|
|
183
|
+
--select-icon-fill: var(--select-icon-color-readonly);
|
|
181
184
|
}
|
|
182
185
|
|
|
183
186
|
&:where([data-priority="secondary"]):where([data-readonly="true"]) {
|
|
184
|
-
border-color:
|
|
185
|
-
background-color: var(--select-color-surface-secondary-
|
|
187
|
+
border-color: var(--select-secondary-color-border-readonly);
|
|
188
|
+
background-color: var(--select-color-surface-secondary-readonly);
|
|
186
189
|
cursor: default;
|
|
190
|
+
--select-icon-fill: var(--select-icon-color-readonly);
|
|
187
191
|
|
|
188
192
|
&::after {
|
|
189
193
|
background-color: var(--select-color-border-secondary-disabled);
|
|
@@ -195,7 +199,7 @@
|
|
|
195
199
|
border-color: var(--select-table-border-readonly-color);
|
|
196
200
|
background-color: var(--select-table-surface-readonly-color);
|
|
197
201
|
cursor: not-allowed;
|
|
198
|
-
--select-icon-fill: var(--select-table-icon-color-
|
|
202
|
+
--select-icon-fill: var(--select-table-icon-color-readonly);
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
// &:not([data-priority="secondary"]):hover:not(:disabled) {
|
|
@@ -211,7 +215,7 @@
|
|
|
211
215
|
|
|
212
216
|
&:where([data-priority="secondary"]):where([data-state="disabled"]),
|
|
213
217
|
&:where([data-priority="secondary"]):disabled {
|
|
214
|
-
border-color:
|
|
218
|
+
border-color: var(--select-secondary-color-border-disabled);
|
|
215
219
|
background-color: var(--select-color-surface-secondary-disabled);
|
|
216
220
|
|
|
217
221
|
&::after {
|
|
@@ -297,6 +301,11 @@
|
|
|
297
301
|
}
|
|
298
302
|
}
|
|
299
303
|
|
|
304
|
+
.select-input-label-readonly {
|
|
305
|
+
width: auto;
|
|
306
|
+
max-width: 100%;
|
|
307
|
+
}
|
|
308
|
+
|
|
300
309
|
.select-button:where(:not([data-state="disabled"]))
|
|
301
310
|
.select-value[data-has-value="true"]
|
|
302
311
|
.select-input-label {
|
|
@@ -120,6 +120,8 @@
|
|
|
120
120
|
--select-secondary-color-text-readonly: var(
|
|
121
121
|
--select-primary-color-text-readonly
|
|
122
122
|
);
|
|
123
|
+
--select-secondary-color-border-disabled: var(--color-label-disabled);
|
|
124
|
+
--select-secondary-color-border-readonly: transparent;
|
|
123
125
|
--select-secondary-color-placeholder: var(--color-label-alternative);
|
|
124
126
|
--select-secondary-color-placeholder-disabled: var(--color-label-disabled);
|
|
125
127
|
--select-secondary-color-placeholder-readonly: var(
|
|
@@ -129,10 +131,12 @@
|
|
|
129
131
|
--select-color-surface-secondary-hover: var(--color-surface-static-cool-gray);
|
|
130
132
|
--select-color-surface-secondary-active: var(--color-surface-standard);
|
|
131
133
|
--select-color-surface-secondary-disabled: var(--color-surface-neutral);
|
|
134
|
+
--select-color-surface-secondary-readonly: transparent;
|
|
132
135
|
|
|
133
136
|
--select-icon-color-default: var(--color-cool-gray-75);
|
|
134
137
|
--select-icon-color-focused: var(--color-cool-gray-20);
|
|
135
138
|
--select-icon-color-disabled: var(--color-cool-gray-85);
|
|
139
|
+
--select-icon-color-readonly: transparent;
|
|
136
140
|
|
|
137
141
|
/* Multi select chip */
|
|
138
142
|
--select-multiple-chip-gap: var(--spacing-gap-2);
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Select multiple display mode
|
|
5
|
+
* @typedef {"chip" | "text"} SelectMultipleDisplayMode
|
|
6
|
+
*/
|
|
7
|
+
export type SelectMultipleDisplayMode = "chip" | "text";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Select multiple display props
|
|
11
|
+
* @property {SelectMultipleDisplayMode} [displayMode] 선택값 표시 방식
|
|
12
|
+
*/
|
|
13
|
+
export interface SelectMultipleDisplayProps {
|
|
14
|
+
/**
|
|
15
|
+
* 선택값 표시 방식
|
|
16
|
+
* - chip: 기존 chip list 표시
|
|
17
|
+
* - text: comma-separated text 표시
|
|
18
|
+
*/
|
|
19
|
+
displayMode?: SelectMultipleDisplayMode;
|
|
20
|
+
}
|
|
21
|
+
|
|
3
22
|
/**
|
|
4
23
|
* Select multiple chip props
|
|
5
24
|
* @property {ReactNode} label 표시할 라벨
|
|
@@ -9,6 +9,7 @@ import type { UseFormRegisterReturn } from "react-hook-form";
|
|
|
9
9
|
import type {
|
|
10
10
|
DropdownContainerProps,
|
|
11
11
|
DropdownMenuListProps,
|
|
12
|
+
DropdownPanelMinWidth,
|
|
12
13
|
DropdownPanelWidth,
|
|
13
14
|
} from "../../dropdown/types";
|
|
14
15
|
import type { FormFieldWidth } from "../../form/types";
|
|
@@ -19,7 +20,7 @@ import type {
|
|
|
19
20
|
SelectState,
|
|
20
21
|
SelectTriggerButtonType,
|
|
21
22
|
} from "./base";
|
|
22
|
-
import type { SelectMultipleTag } from "./multiple";
|
|
23
|
+
import type { SelectMultipleDisplayProps, SelectMultipleTag } from "./multiple";
|
|
23
24
|
import type { SelectDropdownOption } from "./option";
|
|
24
25
|
import type {
|
|
25
26
|
SelectTriggerBaseFoundationProps,
|
|
@@ -192,8 +193,9 @@ export type SelectCallbackParams<
|
|
|
192
193
|
* Select dropdown 확장 props
|
|
193
194
|
* @property {SelectSize} [size] dropdown surface size 스케일
|
|
194
195
|
* @property {DropdownPanelWidth} [width] dropdown panel width 옵션
|
|
196
|
+
* @property {DropdownPanelMinWidth} [minWidth] dropdown panel 최소 너비 옵션
|
|
195
197
|
* @property {Omit<DropdownMenuProps, "open" | "defaultOpen" | "onOpenChange">} [rootProps] Dropdown.Root 전달 props
|
|
196
|
-
* @property {Omit<DropdownContainerProps, "children" | "size" | "width">} [containerProps] Dropdown.Container 전달 props
|
|
198
|
+
* @property {Omit<DropdownContainerProps, "children" | "size" | "width" | "minWidth">} [containerProps] Dropdown.Container 전달 props
|
|
197
199
|
* @property {DropdownMenuListProps} [menuListProps] Dropdown.Menu.List 전달 props
|
|
198
200
|
* @property {ReactNode} [alt] option이 비어 있을 때 렌더링할 alternate 콘텐츠
|
|
199
201
|
*/
|
|
@@ -206,14 +208,21 @@ export interface SelectDropdownExtension {
|
|
|
206
208
|
* dropdown panel width 옵션
|
|
207
209
|
*/
|
|
208
210
|
width?: DropdownPanelWidth;
|
|
211
|
+
/**
|
|
212
|
+
* dropdown panel 최소 너비 옵션
|
|
213
|
+
*/
|
|
214
|
+
minWidth?: DropdownPanelMinWidth;
|
|
209
215
|
/**
|
|
210
216
|
* Dropdown.Root 전달 props(제어 props 제외)
|
|
211
217
|
*/
|
|
212
218
|
rootProps?: Omit<DropdownMenuProps, "open" | "defaultOpen" | "onOpenChange">;
|
|
213
219
|
/**
|
|
214
|
-
* Dropdown.Container 전달 props(children/size/width 제외)
|
|
220
|
+
* Dropdown.Container 전달 props(children/size/width/minWidth 제외)
|
|
215
221
|
*/
|
|
216
|
-
containerProps?: Omit<
|
|
222
|
+
containerProps?: Omit<
|
|
223
|
+
DropdownContainerProps,
|
|
224
|
+
"children" | "size" | "width" | "minWidth"
|
|
225
|
+
>;
|
|
217
226
|
/**
|
|
218
227
|
* Dropdown.Menu.List 전달 props
|
|
219
228
|
*/
|
|
@@ -227,7 +236,7 @@ export interface SelectDropdownExtension {
|
|
|
227
236
|
/**
|
|
228
237
|
* Select dropdown 옵션 구성 props
|
|
229
238
|
* @property {SelectDropdownOption<OptionData, DataOptionality>[]} [items] dropdown item 리스트(권장: items[].selected 초기값)
|
|
230
|
-
* @property {SelectCallbackParams<OptionData, DataOptionality>} [onSelectOption] option 선택 액션 콜백
|
|
239
|
+
* @property {SelectCallbackParams<OptionData, DataOptionality>} [onSelectOption] option 선택 액션 콜백(legacy)
|
|
231
240
|
* @property {SelectCallbackParams<OptionData, DataOptionality>} [onSelectChange] 선택값 변경 콜백
|
|
232
241
|
* @property {SelectDropdownExtension} [dropdown] dropdown 확장 옵션
|
|
233
242
|
* @example
|
|
@@ -244,6 +253,8 @@ export interface SelectDropdownConfigProps<
|
|
|
244
253
|
items?: SelectDropdownOption<OptionData, DataOptionality>[];
|
|
245
254
|
/**
|
|
246
255
|
* option 선택 액션 콜백
|
|
256
|
+
* @deprecated
|
|
257
|
+
* legacy 호환용이며, 신규 계약은 `onSelectChange`를 권장한다.
|
|
247
258
|
*/
|
|
248
259
|
onSelectOption?: SelectCallbackParams<OptionData, DataOptionality>;
|
|
249
260
|
/**
|
|
@@ -447,6 +458,7 @@ export interface SelectMultipleAllOptionProps {
|
|
|
447
458
|
* @property {(open: boolean) => void} [onOpen] open state change 콜백
|
|
448
459
|
* @property {boolean} [showSelectAllOption] dropdown 첫 행에 "전체" 옵션 노출 여부
|
|
449
460
|
* @property {ReactNode} [selectAllLabel] "전체" 옵션 라벨 커스터마이징
|
|
461
|
+
* @property {"chip" | "text"} [displayMode] 선택값 표시 방식
|
|
450
462
|
* @example
|
|
451
463
|
* type MultipleProps = SelectMultipleComponentProps<{ metaKey: number }>;
|
|
452
464
|
* type RequiredChange = SelectCallbackParams<{ metaKey: number }, "required">;
|
|
@@ -458,4 +470,5 @@ export type SelectMultipleComponentProps<OptionData = unknown> =
|
|
|
458
470
|
SelectDropdownConfigProps<OptionData> &
|
|
459
471
|
SelectDropdownBehaviorProps &
|
|
460
472
|
SelectWidthOption &
|
|
461
|
-
SelectMultipleAllOptionProps
|
|
473
|
+
SelectMultipleAllOptionProps &
|
|
474
|
+
SelectMultipleDisplayProps;
|