@uniai-fe/uds-primitives 0.3.59 → 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 +91 -76
- 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/styles/foundation.scss +57 -54
- 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
|
@@ -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;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Fragment, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import type { SelectDropdownOption } from "../types/option";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Select Utils; 표시 텍스트 정규화 유틸
|
|
7
|
+
* @param {ReactNode} [value] 문자열화 가능한 표시값
|
|
8
|
+
* @returns {string} input/value 경로에서 사용할 문자열
|
|
9
|
+
* @example
|
|
10
|
+
* toSelectInputText("농장 A");
|
|
11
|
+
* toSelectInputText(12);
|
|
12
|
+
*/
|
|
13
|
+
export const toSelectInputText = (value?: ReactNode): string => {
|
|
14
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
15
|
+
return String(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return "";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Select Utils; comma-separated label 렌더 유틸
|
|
23
|
+
* @template OptionData
|
|
24
|
+
* @param {SelectDropdownOption<OptionData>[]} options 선택된 option 목록
|
|
25
|
+
* @returns {ReactNode} comma-separated label node
|
|
26
|
+
* @example
|
|
27
|
+
* renderCommaSeparatedSelectLabels([
|
|
28
|
+
* { id: "a", value: "A", label: "1동" },
|
|
29
|
+
* { id: "b", value: "B", label: "2동" },
|
|
30
|
+
* ]);
|
|
31
|
+
*/
|
|
32
|
+
export const renderCommaSeparatedSelectLabels = <OptionData,>(
|
|
33
|
+
options: SelectDropdownOption<OptionData>[],
|
|
34
|
+
): ReactNode => {
|
|
35
|
+
return options.map((option, index) => (
|
|
36
|
+
<Fragment key={`${option.id}/selected`}>
|
|
37
|
+
{option.label}
|
|
38
|
+
{index < options.length - 1 ? ", " : null}
|
|
39
|
+
</Fragment>
|
|
40
|
+
));
|
|
41
|
+
};
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot; polymorphic/text slot 카테고리 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `Slot.Base`: as 기반 polymorphic 마크업 래퍼다.
|
|
5
|
+
* - `Slot.Text`: 텍스트 children만 래핑하는 공통 슬롯이다.
|
|
6
|
+
* - `SlotComponentProps`, `SlotTextProps`: public type 계약이다.
|
|
7
|
+
*/
|
|
1
8
|
export * from "./markup";
|
|
2
9
|
export type * from "./types";
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import SlotBase from "./Base";
|
|
2
2
|
import SlotText from "./Text";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Slot; polymorphic/text slot namespace
|
|
6
|
+
* @desc
|
|
7
|
+
* - `Slot.Base`: as 기반 polymorphic 마크업 래퍼다.
|
|
8
|
+
* - `Slot.Text`: 텍스트 children만 래핑하는 공통 슬롯이다.
|
|
9
|
+
*/
|
|
4
10
|
export const Slot = {
|
|
5
11
|
Base: SlotBase,
|
|
6
12
|
Text: SlotText,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tab; line/fill tabs 카테고리 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `TabRoot`: tabs value 상태를 관리하는 루트다.
|
|
5
|
+
* - `TabList`, `TabTrigger`, `TabContent`: tabs anatomy 도구다.
|
|
6
|
+
* - `TabContext`, `useTabContext`: tab 스타일 context util이다.
|
|
3
7
|
*/
|
|
4
8
|
import "./index.scss";
|
|
5
9
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab; line/fill tabs markup export 배럴
|
|
3
|
+
* @desc
|
|
4
|
+
* - `TabRoot`: tabs value 상태를 관리하는 루트다.
|
|
5
|
+
* - `TabList`: trigger 리스트 컨테이너다.
|
|
6
|
+
* - `TabTrigger`: 개별 탭 버튼이다.
|
|
7
|
+
* - `TabContent`: 선택된 탭 패널이다.
|
|
8
|
+
*/
|
|
1
9
|
export { TabRoot } from "./TabRoot";
|
|
2
10
|
export { TabList } from "./TabList";
|
|
3
11
|
export { TabTrigger } from "./TabTrigger";
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tooltip; namespace 배럴 export
|
|
3
|
+
* @desc
|
|
4
|
+
* - `Tooltip.Root`, `Tooltip.Trigger`, `Tooltip.Message`: foundation 레이어
|
|
5
|
+
* - `Tooltip.Text`: 메시지 텍스트 wrapper
|
|
6
|
+
* - `Tooltip.Template`: info icon preset
|
|
7
|
+
*/
|
|
1
8
|
import "./index.scss";
|
|
2
9
|
|
|
3
10
|
export * from "./markup";
|
|
@@ -5,12 +5,13 @@ import TooltipText from "./Text";
|
|
|
5
5
|
import TooltipTrigger from "./Trigger";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Tooltip
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
8
|
+
* Tooltip; 컴포넌트 모듈
|
|
9
|
+
* @namespace Tooltip
|
|
10
|
+
* - `Tooltip.Message`
|
|
11
|
+
* - `Tooltip.Text`
|
|
12
|
+
* - `Tooltip.Trigger`
|
|
13
|
+
* - `Tooltip.Root`
|
|
14
|
+
* - `Tooltip.Template`
|
|
14
15
|
*/
|
|
15
16
|
export const Tooltip = {
|
|
16
17
|
Message: TooltipMessage,
|