@uniai-fe/uds-primitives 0.3.25 → 0.3.26
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
import {
|
|
4
|
+
import { useEffect, useMemo, useState } from "react";
|
|
5
5
|
import type { ReactNode } from "react";
|
|
6
6
|
|
|
7
7
|
import { Dropdown } from "../../dropdown/markup";
|
|
@@ -62,270 +62,260 @@ const SELECT_CUSTOM_OPTION_VALUE = "CUSTOM";
|
|
|
62
62
|
* placeholder="품목을 선택하세요"
|
|
63
63
|
* />
|
|
64
64
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
65
|
+
export function SelectDefault<OptionData = unknown>({
|
|
66
|
+
className,
|
|
67
|
+
displayLabel,
|
|
68
|
+
placeholder,
|
|
69
|
+
priority = "primary",
|
|
70
|
+
size = "medium",
|
|
71
|
+
state = "default",
|
|
72
|
+
block,
|
|
73
|
+
width,
|
|
74
|
+
disabled,
|
|
75
|
+
readOnly,
|
|
76
|
+
buttonType,
|
|
77
|
+
items,
|
|
78
|
+
onSelectOption,
|
|
79
|
+
onSelectChange,
|
|
80
|
+
dropdownOptions,
|
|
81
|
+
open,
|
|
82
|
+
defaultOpen,
|
|
83
|
+
onOpen,
|
|
84
|
+
triggerProps,
|
|
85
|
+
register,
|
|
86
|
+
customRegister,
|
|
87
|
+
customOptions,
|
|
88
|
+
inputProps,
|
|
89
|
+
}: SelectDefaultComponentProps<OptionData>) {
|
|
90
|
+
// 1) table priority + width 미지정 조합에서는 block을 기본 동작으로 사용한다.
|
|
91
|
+
const resolvedBlock = block || (priority === "table" && width === undefined);
|
|
92
|
+
// 1-1) xsmall은 primary 전용이므로 secondary/table에서는 small로 fallback한다.
|
|
93
|
+
const resolvedSize =
|
|
94
|
+
priority !== "primary" && size === "xsmall" ? "small" : size;
|
|
95
|
+
|
|
96
|
+
// 2) custom mode의 입력값은 내부 state로 유지한다.
|
|
97
|
+
const [customLabelValue, setCustomLabelValue] = useState("");
|
|
98
|
+
|
|
99
|
+
// 3) custom option id가 기존 item id와 충돌하지 않도록 안전한 id를 만든다.
|
|
100
|
+
const customOptionId = useMemo(() => {
|
|
101
|
+
const existingIdSet = new Set(items.map(option => option.id));
|
|
102
|
+
if (!existingIdSet.has(SELECT_CUSTOM_OPTION_BASE_ID)) {
|
|
103
|
+
return SELECT_CUSTOM_OPTION_BASE_ID;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let offset = 1;
|
|
107
|
+
let nextId = `${SELECT_CUSTOM_OPTION_BASE_ID}_${offset}`;
|
|
108
|
+
while (existingIdSet.has(nextId)) {
|
|
109
|
+
offset += 1;
|
|
110
|
+
nextId = `${SELECT_CUSTOM_OPTION_BASE_ID}_${offset}`;
|
|
111
|
+
}
|
|
112
|
+
return nextId;
|
|
113
|
+
}, [items]);
|
|
114
|
+
|
|
115
|
+
// 4) customOptions가 있으면 dropdown 마지막에 "직접 입력" 항목을 주입한다.
|
|
116
|
+
const mergedOptions = useMemo<SelectDropdownOption<OptionData>[]>(() => {
|
|
117
|
+
if (!customOptions) {
|
|
118
|
+
return items;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return [
|
|
122
|
+
...items,
|
|
123
|
+
{
|
|
124
|
+
id: customOptionId,
|
|
125
|
+
value: SELECT_CUSTOM_OPTION_VALUE,
|
|
126
|
+
label: customOptions.optionName ?? "직접 입력",
|
|
127
|
+
// 변경 설명: custom option sentinel은 id/value로만 판별해 OptionData 제네릭을 오염시키지 않는다.
|
|
128
|
+
} satisfies SelectDropdownOption<OptionData>,
|
|
129
|
+
];
|
|
130
|
+
}, [customOptionId, customOptions, items]);
|
|
131
|
+
|
|
132
|
+
// 5) 선택 id로 option을 즉시 조회할 수 있도록 map을 만든다.
|
|
133
|
+
const optionMap = useMemo(
|
|
134
|
+
() => new Map(mergedOptions.map(option => [option.id, option])),
|
|
135
|
+
[mergedOptions],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// 6) uncontrolled 초기 선택값은 items[].selected의 첫 번째 항목만 사용한다.
|
|
139
|
+
const selectedIdsFromItems = useMemo(() => {
|
|
140
|
+
const initialSelectedIds = items
|
|
141
|
+
.filter(option => option.selected)
|
|
142
|
+
.map(option => option.id);
|
|
143
|
+
return normalizeSingleSelectedIds(initialSelectedIds);
|
|
144
|
+
}, [items]);
|
|
145
|
+
|
|
146
|
+
// 7) 내부 선택 state를 선언한다.
|
|
147
|
+
const [selectedOptionIds, setSelectedOptionIds] = useState<string[]>(
|
|
148
|
+
() => selectedIdsFromItems,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// 8) 외부 items 변경 시 동일 규칙으로 내부 선택 state를 재동기화한다.
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
setSelectedOptionIds(previousSelectedIds => {
|
|
154
|
+
if (isSameIdList(previousSelectedIds, selectedIdsFromItems)) {
|
|
155
|
+
return previousSelectedIds;
|
|
116
156
|
}
|
|
117
|
-
return
|
|
118
|
-
}, [items]);
|
|
119
|
-
|
|
120
|
-
// 4) customOptions가 있으면 dropdown 마지막에 "직접 입력" 항목을 주입한다.
|
|
121
|
-
const mergedOptions = useMemo(() => {
|
|
122
|
-
if (!customOptions) {
|
|
123
|
-
return items;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return [
|
|
127
|
-
...items,
|
|
128
|
-
{
|
|
129
|
-
id: customOptionId,
|
|
130
|
-
value: SELECT_CUSTOM_OPTION_VALUE,
|
|
131
|
-
label: customOptions.optionName ?? "직접 입력",
|
|
132
|
-
data: { isCustomInput: true },
|
|
133
|
-
} satisfies SelectDropdownOption,
|
|
134
|
-
];
|
|
135
|
-
}, [customOptionId, customOptions, items]);
|
|
136
|
-
|
|
137
|
-
// 5) 선택 id로 option을 즉시 조회할 수 있도록 map을 만든다.
|
|
138
|
-
const optionMap = useMemo(
|
|
139
|
-
() => new Map(mergedOptions.map(option => [option.id, option])),
|
|
140
|
-
[mergedOptions],
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// 6) uncontrolled 초기 선택값은 items[].selected의 첫 번째 항목만 사용한다.
|
|
144
|
-
const selectedIdsFromItems = useMemo(() => {
|
|
145
|
-
const initialSelectedIds = items
|
|
146
|
-
.filter(option => option.selected)
|
|
147
|
-
.map(option => option.id);
|
|
148
|
-
return normalizeSingleSelectedIds(initialSelectedIds);
|
|
149
|
-
}, [items]);
|
|
150
|
-
|
|
151
|
-
// 7) 내부 선택 state를 선언한다.
|
|
152
|
-
const [selectedOptionIds, setSelectedOptionIds] = useState<string[]>(
|
|
153
|
-
() => selectedIdsFromItems,
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
// 8) 외부 items 변경 시 동일 규칙으로 내부 선택 state를 재동기화한다.
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
setSelectedOptionIds(previousSelectedIds => {
|
|
159
|
-
if (isSameIdList(previousSelectedIds, selectedIdsFromItems)) {
|
|
160
|
-
return previousSelectedIds;
|
|
161
|
-
}
|
|
162
|
-
return selectedIdsFromItems;
|
|
163
|
-
});
|
|
164
|
-
}, [selectedIdsFromItems]);
|
|
165
|
-
|
|
166
|
-
// 9) 단일 선택이므로 첫 번째 id를 현재 선택 option으로 본다.
|
|
167
|
-
const selectedOption =
|
|
168
|
-
selectedOptionIds.length > 0
|
|
169
|
-
? optionMap.get(selectedOptionIds[0])
|
|
170
|
-
: undefined;
|
|
171
|
-
|
|
172
|
-
// 10) custom mode는 customOptions 존재 + custom option 선택 상태로 판단한다.
|
|
173
|
-
const isCustomInputActive = Boolean(
|
|
174
|
-
customOptions && selectedOption?.id === customOptionId,
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
// 11) 외부 displayLabel이 있으면 우선 사용하고, 없으면 선택 option label을 사용한다.
|
|
178
|
-
const resolvedDisplayLabel =
|
|
179
|
-
displayLabel ?? (isCustomInputActive ? undefined : selectedOption?.label);
|
|
180
|
-
|
|
181
|
-
// 12) open 제어형/비제어형 계약은 공용 hook으로 통합 처리한다.
|
|
182
|
-
const { open: dropdownOpen, setOpen } = useSelectDropdownOpenState({
|
|
183
|
-
open,
|
|
184
|
-
defaultOpen,
|
|
185
|
-
onOpen,
|
|
157
|
+
return selectedIdsFromItems;
|
|
186
158
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
159
|
+
}, [selectedIdsFromItems]);
|
|
160
|
+
|
|
161
|
+
// 9) 단일 선택이므로 첫 번째 id를 현재 선택 option으로 본다.
|
|
162
|
+
const selectedOption =
|
|
163
|
+
selectedOptionIds.length > 0
|
|
164
|
+
? optionMap.get(selectedOptionIds[0])
|
|
165
|
+
: undefined;
|
|
166
|
+
|
|
167
|
+
// 10) custom mode는 customOptions 존재 + custom option 선택 상태로 판단한다.
|
|
168
|
+
const isCustomInputActive = Boolean(
|
|
169
|
+
customOptions && selectedOption?.id === customOptionId,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// 11) 외부 displayLabel이 있으면 우선 사용하고, 없으면 선택 option label을 사용한다.
|
|
173
|
+
const resolvedDisplayLabel =
|
|
174
|
+
displayLabel ?? (isCustomInputActive ? undefined : selectedOption?.label);
|
|
175
|
+
|
|
176
|
+
// 12) open 제어형/비제어형 계약은 공용 hook으로 통합 처리한다.
|
|
177
|
+
const { open: dropdownOpen, setOpen } = useSelectDropdownOpenState({
|
|
178
|
+
open,
|
|
179
|
+
defaultOpen,
|
|
180
|
+
onOpen,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// 13) disabled/readOnly 상태에서는 open/option 선택 인터랙션을 모두 차단한다.
|
|
184
|
+
const isInteractionBlocked = disabled || readOnly;
|
|
185
|
+
|
|
186
|
+
// 14) open 상태 변경 처리: 차단 상태면 닫힘 고정, 아니면 nextOpen 반영.
|
|
187
|
+
const handleOpenChange = (nextOpen: boolean) => {
|
|
188
|
+
if (isInteractionBlocked) {
|
|
216
189
|
setOpen(false);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
setOpen(nextOpen);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// 15) option 선택 처리: onSelectOption 호출 후, 변경이 있을 때만 onSelectChange를 호출한다.
|
|
196
|
+
const handleOptionSelect = (
|
|
197
|
+
option: SelectDropdownOption<OptionData>,
|
|
198
|
+
event: Event,
|
|
199
|
+
) => {
|
|
200
|
+
if (isInteractionBlocked) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const previousOption = selectedOption;
|
|
205
|
+
const hasChanged = previousOption?.id !== option.id;
|
|
206
|
+
|
|
207
|
+
onSelectOption?.(option, previousOption, event);
|
|
208
|
+
|
|
209
|
+
if (hasChanged) {
|
|
210
|
+
setSelectedOptionIds([option.id]);
|
|
211
|
+
onSelectChange?.(option, previousOption, event);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
setOpen(false);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// 16) label input 값은 custom mode에서만 사용자 입력값/controlled inputProps.value를 사용한다.
|
|
218
|
+
const labelInputValue = isCustomInputActive
|
|
219
|
+
? typeof inputProps?.value === "string" ||
|
|
220
|
+
typeof inputProps?.value === "number"
|
|
221
|
+
? String(inputProps.value)
|
|
222
|
+
: customLabelValue
|
|
223
|
+
: toInputText(resolvedDisplayLabel);
|
|
224
|
+
|
|
225
|
+
// 17) 렌더: Container → Dropdown.Root → Trigger → Menu.List 구조를 유지한다.
|
|
226
|
+
return (
|
|
227
|
+
<Container
|
|
228
|
+
className={clsx("select-trigger-container", className)}
|
|
229
|
+
block={resolvedBlock}
|
|
230
|
+
width={width}
|
|
231
|
+
>
|
|
232
|
+
<Dropdown.Root
|
|
233
|
+
open={dropdownOpen}
|
|
234
|
+
onOpenChange={handleOpenChange}
|
|
235
|
+
modal={false}
|
|
236
|
+
{...dropdownOptions?.rootProps}
|
|
233
237
|
>
|
|
234
|
-
<Dropdown.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
238
|
+
<Dropdown.Trigger asChild>
|
|
239
|
+
<SelectTriggerBase
|
|
240
|
+
priority={priority}
|
|
241
|
+
size={resolvedSize}
|
|
242
|
+
state={disabled ? "disabled" : state}
|
|
243
|
+
block={resolvedBlock}
|
|
244
|
+
open={dropdownOpen}
|
|
245
|
+
disabled={disabled}
|
|
246
|
+
readOnly={readOnly}
|
|
247
|
+
buttonType={buttonType}
|
|
248
|
+
{...triggerProps}
|
|
249
|
+
>
|
|
250
|
+
<SelectTriggerSelected
|
|
251
|
+
label={resolvedDisplayLabel}
|
|
252
|
+
placeholder={
|
|
253
|
+
isCustomInputActive
|
|
254
|
+
? (customOptions?.placeholder ?? toInputText(placeholder))
|
|
255
|
+
: placeholder
|
|
256
|
+
}
|
|
257
|
+
isPlaceholder={
|
|
258
|
+
isCustomInputActive
|
|
259
|
+
? false
|
|
260
|
+
: resolvedDisplayLabel === undefined ||
|
|
261
|
+
resolvedDisplayLabel === null ||
|
|
262
|
+
resolvedDisplayLabel === ""
|
|
263
|
+
}
|
|
264
|
+
// 변경: custom mode가 아닐 때는 label input을 읽기 전용으로 고정한다.
|
|
265
|
+
readOnly={readOnly || !isCustomInputActive}
|
|
266
|
+
register={register}
|
|
267
|
+
customRegister={isCustomInputActive ? customRegister : undefined}
|
|
268
|
+
inputProps={inputProps}
|
|
269
|
+
valueText={labelInputValue}
|
|
270
|
+
valueFieldValue={
|
|
271
|
+
isCustomInputActive && !customRegister
|
|
272
|
+
? labelInputValue
|
|
273
|
+
: (selectedOption?.value ?? "")
|
|
274
|
+
}
|
|
275
|
+
// 변경: custom mode 진입 시 label input에 focus를 연결한다.
|
|
276
|
+
shouldFocusInput={isCustomInputActive}
|
|
277
|
+
onLabelChange={setCustomLabelValue}
|
|
278
|
+
/>
|
|
279
|
+
</SelectTriggerBase>
|
|
280
|
+
</Dropdown.Trigger>
|
|
281
|
+
<Dropdown.Container
|
|
282
|
+
{...dropdownOptions?.containerProps}
|
|
283
|
+
size={dropdownOptions?.size ?? resolvedSize}
|
|
284
|
+
width={dropdownOptions?.width ?? "match"}
|
|
239
285
|
>
|
|
240
|
-
<Dropdown.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
readOnly={readOnly || !isCustomInputActive}
|
|
269
|
-
register={register}
|
|
270
|
-
customRegister={
|
|
271
|
-
isCustomInputActive ? customRegister : undefined
|
|
272
|
-
}
|
|
273
|
-
inputProps={inputProps}
|
|
274
|
-
valueText={labelInputValue}
|
|
275
|
-
valueFieldValue={
|
|
276
|
-
isCustomInputActive && !customRegister
|
|
277
|
-
? labelInputValue
|
|
278
|
-
: (selectedOption?.value ?? "")
|
|
279
|
-
}
|
|
280
|
-
// 변경: custom mode 진입 시 label input에 focus를 연결한다.
|
|
281
|
-
shouldFocusInput={isCustomInputActive}
|
|
282
|
-
onLabelChange={setCustomLabelValue}
|
|
286
|
+
<Dropdown.Menu.List {...dropdownOptions?.menuListProps}>
|
|
287
|
+
{mergedOptions.length > 0 ? (
|
|
288
|
+
<>
|
|
289
|
+
{mergedOptions.map(option => (
|
|
290
|
+
<Dropdown.Menu.Item
|
|
291
|
+
key={option.id}
|
|
292
|
+
label={option.label}
|
|
293
|
+
description={option.description}
|
|
294
|
+
disabled={option.disabled}
|
|
295
|
+
left={option.left}
|
|
296
|
+
right={option.right}
|
|
297
|
+
multiple={Boolean(option.multiple)}
|
|
298
|
+
isSelected={selectedOption?.id === option.id}
|
|
299
|
+
onSelect={event => {
|
|
300
|
+
if (option.disabled) {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
handleOptionSelect(option, event);
|
|
305
|
+
}}
|
|
306
|
+
/>
|
|
307
|
+
))}
|
|
308
|
+
</>
|
|
309
|
+
) : (
|
|
310
|
+
<Dropdown.Menu.Item
|
|
311
|
+
label={dropdownOptions?.alt ?? "선택할 항목이 없습니다."}
|
|
312
|
+
disabled
|
|
313
|
+
className="dropdown-menu-alt"
|
|
283
314
|
/>
|
|
284
|
-
|
|
285
|
-
</Dropdown.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
<Dropdown.Menu.List {...dropdownOptions?.menuListProps}>
|
|
292
|
-
{mergedOptions.length > 0 ? (
|
|
293
|
-
<>
|
|
294
|
-
{mergedOptions.map(option => (
|
|
295
|
-
<Dropdown.Menu.Item
|
|
296
|
-
key={option.id}
|
|
297
|
-
label={option.label}
|
|
298
|
-
description={option.description}
|
|
299
|
-
disabled={option.disabled}
|
|
300
|
-
left={option.left}
|
|
301
|
-
right={option.right}
|
|
302
|
-
multiple={Boolean(option.multiple)}
|
|
303
|
-
isSelected={selectedOption?.id === option.id}
|
|
304
|
-
onSelect={event => {
|
|
305
|
-
if (option.disabled) {
|
|
306
|
-
event.preventDefault();
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
handleOptionSelect(option, event);
|
|
310
|
-
}}
|
|
311
|
-
/>
|
|
312
|
-
))}
|
|
313
|
-
</>
|
|
314
|
-
) : (
|
|
315
|
-
<Dropdown.Menu.Item
|
|
316
|
-
label={dropdownOptions?.alt ?? "선택할 항목이 없습니다."}
|
|
317
|
-
disabled
|
|
318
|
-
className="dropdown-menu-alt"
|
|
319
|
-
/>
|
|
320
|
-
)}
|
|
321
|
-
</Dropdown.Menu.List>
|
|
322
|
-
</Dropdown.Container>
|
|
323
|
-
</Dropdown.Root>
|
|
324
|
-
</Container>
|
|
325
|
-
);
|
|
326
|
-
},
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
SelectDefault.displayName = "SelectDefault";
|
|
330
|
-
|
|
331
|
-
export { SelectDefault };
|
|
315
|
+
)}
|
|
316
|
+
</Dropdown.Menu.List>
|
|
317
|
+
</Dropdown.Container>
|
|
318
|
+
</Dropdown.Root>
|
|
319
|
+
</Container>
|
|
320
|
+
);
|
|
321
|
+
}
|