@uniai-fe/uds-primitives 0.2.7 → 0.2.9

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/dist/styles.css CHANGED
@@ -1782,6 +1782,10 @@ figure.chip {
1782
1782
  margin: 0;
1783
1783
  }
1784
1784
 
1785
+ .dropdown-menu-item-trigger.dropdown-menu-alt[data-disabled] {
1786
+ cursor: default;
1787
+ }
1788
+
1785
1789
  .dropdown-menu-item {
1786
1790
  width: 100%;
1787
1791
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -12,6 +12,16 @@ import DropdownTrigger from "./foundation/Trigger";
12
12
  * Dropdown reference template; trigger/panel/menu 조합을 제공한다.
13
13
  * @component
14
14
  * @param {DropdownTemplateProps} props Dropdown template props
15
+ * @param {ReactNode} props.trigger trigger 요소
16
+ * @param {DropdownTemplateItem[]} props.items 렌더링할 menu item 리스트
17
+ * @param {string[]} [props.selectedIds] 선택된 item id 배열
18
+ * @param {(item: DropdownTemplateItem) => void} [props.onSelect] item 선택 콜백
19
+ * @param {"small" | "medium" | "large"} [props.size="medium"] menu size scale
20
+ * @param {"match" | "fit-content" | "max-content" | string | number} [props.width="match"] panel width 옵션
21
+ * @param {DropdownMenuProps} [props.rootProps] Dropdown.Root 전달 props
22
+ * @param {DropdownContainerProps} [props.containerProps] Dropdown.Container 전달 props
23
+ * @param {DropdownMenuListProps} [props.menuListProps] Dropdown.Menu.List 전달 props
24
+ * @param {ReactNode} [props.alt] empty 상태 대체 콘텐츠
15
25
  */
16
26
  const DropdownTemplate = ({
17
27
  trigger,
@@ -23,31 +33,45 @@ const DropdownTemplate = ({
23
33
  rootProps,
24
34
  containerProps,
25
35
  menuListProps,
36
+ alt,
26
37
  }: DropdownTemplateProps) => {
38
+ const hasItems = items.length > 0;
39
+
27
40
  return (
28
41
  <DropdownRoot {...rootProps}>
29
42
  <DropdownTrigger asChild>{trigger}</DropdownTrigger>
30
43
  <DropdownContainer {...containerProps} size={size} width={width}>
31
44
  <DropdownMenuList {...menuListProps}>
32
- {items.map(item => (
45
+ {hasItems ? (
46
+ <>
47
+ {items.map(item => (
48
+ <DropdownMenuItem
49
+ key={item.id}
50
+ label={item.label}
51
+ description={item.description}
52
+ disabled={item.disabled}
53
+ left={item.left}
54
+ right={item.right}
55
+ multiple={item.multiple}
56
+ isSelected={selectedIds?.includes(item.id)}
57
+ onSelect={event => {
58
+ if (item.disabled) {
59
+ event.preventDefault();
60
+ return;
61
+ }
62
+ onSelect?.(item);
63
+ }}
64
+ />
65
+ ))}
66
+ </>
67
+ ) : (
33
68
  <DropdownMenuItem
34
- key={item.id}
35
- label={item.label}
36
- description={item.description}
37
- disabled={item.disabled}
38
- left={item.left}
39
- right={item.right}
40
- multiple={item.multiple}
41
- isSelected={selectedIds?.includes(item.id)}
42
- onSelect={event => {
43
- if (item.disabled) {
44
- event.preventDefault();
45
- return;
46
- }
47
- onSelect?.(item);
48
- }}
69
+ // 변경: 사용처 1회 상수 대신 인라인 fallback으로 empty label을 처리한다.
70
+ label={alt ?? "데이터가 없습니다."}
71
+ disabled
72
+ className="dropdown-menu-alt"
49
73
  />
50
- ))}
74
+ )}
51
75
  </DropdownMenuList>
52
76
  </DropdownContainer>
53
77
  </DropdownRoot>
@@ -8,11 +8,23 @@ import type { DropdownContainerProps } from "../../types/props";
8
8
  import { useDropdownContext } from "./Provider";
9
9
 
10
10
  /**
11
- * Dropdown container; trigger width 동기화 및 portal 관리
11
+ * Dropdown Foundation; Container 패널 렌더링 컴포넌트
12
12
  * @component
13
13
  * @param {DropdownContainerProps} props Dropdown container props
14
- * @param {DropdownPanelWidth} [props.width="match"] panel width 옵션
14
+ * @param {React.ReactNode} props.children dropdown panel 콘텐츠
15
+ * @param {string} [props.className] panel className
15
16
  * @param {DropdownSize} [props.size="medium"] option height scale
17
+ * @param {DropdownPanelWidth} [props.width="match"] panel width 옵션
18
+ * @param {HTMLElement | null} [props.portalContainer] portal 컨테이너
19
+ * @param {"start" | "center" | "end"} [props.align="start"] 정렬 기준
20
+ * @param {"top" | "right" | "bottom" | "left"} [props.side="bottom"] 패널 위치
21
+ * @param {number} [props.sideOffset=4] trigger 와 패널 사이 간격
22
+ * @param {number} [props.alignOffset] 정렬 보정값
23
+ * @param {React.CSSProperties} [props.style] 인라인 스타일
24
+ * @example
25
+ * <DropdownContainer width="match">
26
+ * <Dropdown.Menu.List />
27
+ * </DropdownContainer>
16
28
  */
17
29
  const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
18
30
  (
@@ -9,11 +9,20 @@ import { Checkbox } from "../../../checkbox/markup/Checkbox";
9
9
  import type { CheckboxProps } from "../../../checkbox/types";
10
10
 
11
11
  /**
12
- * Dropdown menu item; label/description/slot 구성을 처리한다.
12
+ * Dropdown Foundation; Menu Item 옵션 렌더링 컴포넌트
13
13
  * @component
14
14
  * @param {DropdownMenuItemProps} props dropdown menu option props
15
- * @param {boolean} [props.isSelected] 선택 상태
16
- * @param {boolean} [props.multiple] multi select 스타일 여부
15
+ * @param {React.ReactNode} [props.label] 옵션 라벨
16
+ * @param {React.ReactNode} [props.description] 보조 텍스트
17
+ * @param {React.ReactNode} [props.left] 좌측 콘텐츠
18
+ * @param {React.ReactNode} [props.right] 우측 콘텐츠
19
+ * @param {boolean} [props.isSelected=false] 선택 상태
20
+ * @param {boolean} [props.multiple=false] multi select 스타일 여부
21
+ * @param {CheckboxProps} [props.checkboxProps] multiple 시 checkbox props
22
+ * @param {React.ReactNode} [props.children] label 미지정 시 fallback 콘텐츠
23
+ * @param {string} [props.className] Dropdown item className
24
+ * @example
25
+ * <DropdownMenuItem label="옵션 A" isSelected />
17
26
  */
18
27
  const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
19
28
  (
@@ -33,6 +42,13 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
33
42
  ref,
34
43
  ) => {
35
44
  const labelContent = label ?? children;
45
+ // 변경: label/children이 string|number일 때만 준비된 label span으로 매핑하고, 그 외 ReactNode는 그대로 렌더링한다.
46
+ const resolvedLabelContent =
47
+ typeof labelContent === "string" || typeof labelContent === "number" ? (
48
+ <span className="dropdown-menu-item-label">{labelContent}</span>
49
+ ) : (
50
+ labelContent
51
+ );
36
52
  const hasDescription = Boolean(description);
37
53
  const shouldRenderCheckbox = multiple && !left;
38
54
 
@@ -84,9 +100,7 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
84
100
  >
85
101
  {renderLeft()}
86
102
  <span className="dropdown-menu-item-body">
87
- {labelContent ? (
88
- <span className="dropdown-menu-item-label">{labelContent}</span>
89
- ) : null}
103
+ {resolvedLabelContent}
90
104
  {description ? (
91
105
  <span className="dropdown-menu-item-description">
92
106
  {description}
@@ -1,23 +1,30 @@
1
1
  "use client";
2
2
 
3
3
  import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
4
+ import type { DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
4
5
  import type { ReactNode } from "react";
5
6
 
6
- import type { DropdownRootProps } from "../../types/base";
7
7
  import { DropdownProvider } from "./Provider";
8
8
 
9
9
  /**
10
- * Dropdown root; Provider Radix Root를 래핑한다.
10
+ * Dropdown Foundation; Root Provider 래핑 컴포넌트
11
11
  * @component
12
- * @param {DropdownRootProps} props Dropdown Root props
12
+ * @param {DropdownMenuProps} props Dropdown Root props
13
13
  * @param {ReactNode} props.children Dropdown 하위 node
14
14
  * @param {boolean} [props.modal=false] Radix modal 모드
15
+ * @param {boolean} [props.open] 제어형 open 상태
16
+ * @param {boolean} [props.defaultOpen] 비제어형 초기 open 상태
17
+ * @param {(open: boolean) => void} [props.onOpenChange] open 변경 콜백
18
+ * @example
19
+ * <DropdownRoot>
20
+ * <Dropdown.Trigger>열기</Dropdown.Trigger>
21
+ * </DropdownRoot>
15
22
  */
16
23
  const DropdownRoot = ({
17
24
  children,
18
25
  modal = false,
19
26
  ...rootProps
20
- }: DropdownRootProps & { children: ReactNode }) => {
27
+ }: DropdownMenuProps & { children: ReactNode }) => {
21
28
  return (
22
29
  <DropdownProvider>
23
30
  <DropdownMenu.Root modal={modal} {...rootProps}>
@@ -1,19 +1,25 @@
1
1
  "use client";
2
2
 
3
3
  import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
4
+ import type { DropdownMenuTriggerProps } from "@radix-ui/react-dropdown-menu";
4
5
  import { forwardRef } from "react";
5
6
 
6
- import type { DropdownTriggerProps } from "../../types/props";
7
7
  import { mergeRefs } from "../../utils";
8
8
  import { useDropdownContext } from "./Provider";
9
9
 
10
10
  /**
11
- * Dropdown trigger; trigger ref context에 공유한다.
11
+ * Dropdown Foundation; Trigger ref 공유 컴포넌트
12
12
  * @component
13
- * @param {DropdownTriggerProps} props Dropdown trigger props
13
+ * @param {DropdownMenuTriggerProps} props Dropdown trigger props
14
14
  * @param {boolean} [props.asChild=true] asChild 패턴 유지 여부
15
+ * @param {React.ReactNode} props.children Trigger 하위 node
16
+ * @param {string} [props.className] Trigger className
17
+ * @example
18
+ * <DropdownTrigger asChild>
19
+ * <button type="button">열기</button>
20
+ * </DropdownTrigger>
15
21
  */
16
- const DropdownTrigger = forwardRef<HTMLElement, DropdownTriggerProps>(
22
+ const DropdownTrigger = forwardRef<HTMLElement, DropdownMenuTriggerProps>(
17
23
  ({ asChild = true, children, ...rest }, ref) => {
18
24
  const { triggerRef } = useDropdownContext("Dropdown.Trigger");
19
25
 
@@ -47,6 +47,10 @@
47
47
  margin: 0;
48
48
  }
49
49
 
50
+ .dropdown-menu-item-trigger.dropdown-menu-alt[data-disabled] {
51
+ cursor: default;
52
+ }
53
+
50
54
  .dropdown-menu-item {
51
55
  width: 100%;
52
56
  }
@@ -1,5 +1,3 @@
1
- import type { DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
2
-
3
1
  /**
4
2
  * Dropdown size scale
5
3
  * @typedef {"small" | "medium" | "large"} DropdownSize
@@ -18,14 +16,3 @@ export type DropdownPanelWidth =
18
16
  | "max-content"
19
17
  | string
20
18
  | number;
21
-
22
- /**
23
- * Dropdown root props
24
- * @property {boolean} [modal] Radix modal 모드 여부
25
- */
26
- export interface DropdownRootProps extends DropdownMenuProps {
27
- /**
28
- * Radix modal 모드 여부
29
- */
30
- modal?: DropdownMenuProps["modal"];
31
- }
@@ -1,22 +1,12 @@
1
1
  import type {
2
+ DropdownMenuProps,
2
3
  DropdownMenuContentProps,
3
4
  DropdownMenuItemProps as RadixDropdownMenuItemProps,
4
- DropdownMenuTriggerProps,
5
5
  } from "@radix-ui/react-dropdown-menu";
6
6
  import type { HTMLAttributes, MutableRefObject, ReactNode } from "react";
7
7
 
8
8
  import type { CheckboxProps } from "../../checkbox/types";
9
- import type {
10
- DropdownPanelWidth,
11
- DropdownRootProps,
12
- DropdownSize,
13
- } from "./base";
14
-
15
- /**
16
- * Dropdown trigger props
17
- * @property {boolean} [asChild=true] trigger를 asChild 패턴으로 감쌀지 여부
18
- */
19
- export type DropdownTriggerProps = DropdownMenuTriggerProps;
9
+ import type { DropdownPanelWidth, DropdownSize } from "./base";
20
10
 
21
11
  /**
22
12
  * Dropdown Container props
@@ -142,11 +132,13 @@ export interface DropdownTemplateItem {
142
132
  * @property {ReactNode} trigger trigger 요소
143
133
  * @property {DropdownTemplateItem[]} items 렌더링할 menu item 리스트
144
134
  * @property {string[]} [selectedIds] 선택된 item id 배열
135
+ * @property {(item: DropdownTemplateItem) => void} [onSelect] item 선택 콜백
145
136
  * @property {DropdownSize} [size="medium"] surface height scale
146
137
  * @property {DropdownPanelWidth} [width="match"] panel width 옵션
147
- * @property {DropdownRootProps} [rootProps] Root 에 전달할 props
138
+ * @property {DropdownMenuProps} [rootProps] Root 에 전달할 props
148
139
  * @property {DropdownContainerProps} [containerProps] Container 에 전달할 props
149
140
  * @property {DropdownMenuListProps} [menuListProps] MenuList 에 전달할 props
141
+ * @property {ReactNode} [alt] item이 비어 있을 때 렌더링할 alternate 콘텐츠
150
142
  */
151
143
  export interface DropdownTemplateProps {
152
144
  trigger: ReactNode;
@@ -157,8 +149,9 @@ export interface DropdownTemplateProps {
157
149
  width?: DropdownPanelWidth;
158
150
  /**
159
151
  * Root 에 전달할 props
152
+ * - 타입 출처를 명확히 하기 위해 Radix 원본 타입을 직접 사용한다.
160
153
  */
161
- rootProps?: DropdownRootProps;
154
+ rootProps?: DropdownMenuProps;
162
155
  /**
163
156
  * Container 에 전달할 props
164
157
  */
@@ -167,4 +160,8 @@ export interface DropdownTemplateProps {
167
160
  * MenuList 에 전달할 props
168
161
  */
169
162
  menuListProps?: DropdownMenuListProps;
163
+ /**
164
+ * item이 비어 있을 때 렌더링할 alternate 콘텐츠
165
+ */
166
+ alt?: ReactNode;
170
167
  }
@@ -1,45 +1 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
2
-
3
- import type { SelectDropdownBehaviorProps } from "../types/props";
4
-
5
- /**
6
- * Select dropdown open 상태를 제어하는 hook
7
- * @hook
8
- * @param {SelectDropdownBehaviorProps} props open 제어 옵션
9
- * @returns {{
10
- * open: boolean;
11
- * setOpen: (next: boolean) => void;
12
- * isControlled: boolean;
13
- * }} resolved open state
14
- */
15
- export const useSelectDropdownOpenState = ({
16
- open,
17
- defaultOpen,
18
- onOpenChange,
19
- }: SelectDropdownBehaviorProps) => {
20
- const isControlled = useMemo(() => typeof open === "boolean", [open]);
21
- const [uncontrolledOpen, setUncontrolledOpen] = useState(
22
- defaultOpen ?? false,
23
- );
24
-
25
- useEffect(() => {
26
- if (isControlled) {
27
- return;
28
- }
29
- setUncontrolledOpen(defaultOpen ?? false);
30
- }, [defaultOpen, isControlled]);
31
-
32
- const resolvedOpen = isControlled ? (open as boolean) : uncontrolledOpen;
33
-
34
- const setOpen = useCallback(
35
- (nextOpen: boolean) => {
36
- if (!isControlled) {
37
- setUncontrolledOpen(nextOpen);
38
- }
39
- onOpenChange?.(nextOpen);
40
- },
41
- [isControlled, onOpenChange],
42
- );
43
-
44
- return { open: resolvedOpen, setOpen, isControlled };
45
- };
1
+ export * from "./interaction";
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+
5
+ import type {
6
+ UseSelectDropdownOpenStateParams,
7
+ UseSelectDropdownOpenStateReturn,
8
+ } from "../types/interaction";
9
+
10
+ /**
11
+ * Select Hook; Dropdown open 상태 제어 Hook
12
+ * @hook
13
+ * @param {UseSelectDropdownOpenStateParams} params open 제어 옵션
14
+ * @param {boolean} [params.open] 외부 제어형 open 상태
15
+ * @param {boolean} [params.defaultOpen] 비제어형 초기 open 상태
16
+ * @param {(open: boolean) => void} [params.onOpenChange] open 상태 변경 콜백
17
+ * @returns {{
18
+ * open: boolean;
19
+ * setOpen: (next: boolean) => void;
20
+ * isControlled: boolean;
21
+ * }} resolved open state
22
+ * @example
23
+ * const { open, setOpen } = useSelectDropdownOpenState({
24
+ * open: controlledOpen,
25
+ * defaultOpen: false,
26
+ * onOpenChange: onOpenChangeHandler,
27
+ * });
28
+ */
29
+ export const useSelectDropdownOpenState = ({
30
+ open,
31
+ defaultOpen,
32
+ onOpenChange,
33
+ }: UseSelectDropdownOpenStateParams): UseSelectDropdownOpenStateReturn => {
34
+ // 1) 제어형/비제어형 분기 기준을 먼저 확정한다.
35
+ const isControlled = useMemo(() => typeof open === "boolean", [open]);
36
+ // 2) 비제어형일 때만 내부 open state를 소유한다.
37
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(
38
+ defaultOpen ?? false,
39
+ );
40
+
41
+ // 3) defaultOpen 변경 시, 비제어형일 때만 내부 state를 동기화한다.
42
+ useEffect(() => {
43
+ if (isControlled) return;
44
+
45
+ setUncontrolledOpen(defaultOpen ?? false);
46
+ }, [defaultOpen, isControlled]);
47
+
48
+ // 4) 최종 open state는 제어형 우선, 아니면 내부 state를 사용한다.
49
+ const resolvedOpen = isControlled ? (open as boolean) : uncontrolledOpen;
50
+
51
+ // 5) setOpen은 내부 state 갱신 + 외부 콜백 브릿지를 동시에 담당한다.
52
+ const setOpen = useCallback(
53
+ (nextOpen: boolean) => {
54
+ if (!isControlled) setUncontrolledOpen(nextOpen);
55
+
56
+ onOpenChange?.(nextOpen);
57
+ },
58
+ [isControlled, onOpenChange],
59
+ );
60
+
61
+ return { open: resolvedOpen, setOpen, isControlled };
62
+ };
@@ -8,10 +8,8 @@ import { Dropdown } from "../../dropdown/markup";
8
8
  import { SelectTriggerBase, SelectTriggerSelected } from "./foundation";
9
9
  import Container from "./foundation/Container";
10
10
  import { useSelectDropdownOpenState } from "../hooks";
11
- import type {
12
- SelectDefaultComponentProps,
13
- SelectDropdownOption,
14
- } from "../types/props";
11
+ import type { SelectDropdownOption } from "../types/option";
12
+ import type { SelectDefaultComponentProps } from "../types/props";
15
13
 
16
14
  /**
17
15
  * Select default trigger; 단일 선택 드롭다운을 렌더링한다.
@@ -24,7 +22,18 @@ import type {
24
22
  * @param {"small" | "medium" | "large"} [props.size="medium"] size 스케일
25
23
  * @param {"default" | "focused" | "disabled"} [props.state="default"] 시각 상태
26
24
  * @param {boolean} [props.block] block 여부
25
+ * @param {FormFieldWidth} [props.width] container width preset
27
26
  * @param {boolean} [props.disabled] disabled 여부
27
+ * @param {SelectTriggerButtonType} [props.buttonType] trigger button type
28
+ * @param {"small" | "medium" | "large"} [props.dropdownSize] dropdown panel size
29
+ * @param {"match" | "fit-content" | "max-content" | string | number} [props.dropdownWidth="match"] dropdown panel width
30
+ * @param {Omit<DropdownMenuProps, "open" | "defaultOpen" | "onOpenChange">} [props.dropdownRootProps] Dropdown.Root 전달 props
31
+ * @param {Omit<DropdownContainerProps, "children" | "size" | "width">} [props.dropdownContainerProps] Dropdown.Container 전달 props
32
+ * @param {DropdownMenuListProps} [props.dropdownMenuListProps] Dropdown.Menu.List 전달 props
33
+ * @param {ReactNode} [props.alt] empty 상태 대체 콘텐츠
34
+ * @param {boolean} [props.open] controlled open 상태
35
+ * @param {boolean} [props.defaultOpen] uncontrolled 초기 open 상태
36
+ * @param {(open: boolean) => void} [props.onOpenChange] open 상태 변경 콜백
28
37
  */
29
38
  const SelectDefault = forwardRef<HTMLElement, SelectDefaultComponentProps>(
30
39
  (
@@ -48,6 +57,7 @@ const SelectDefault = forwardRef<HTMLElement, SelectDefaultComponentProps>(
48
57
  dropdownRootProps,
49
58
  dropdownContainerProps,
50
59
  dropdownMenuListProps,
60
+ alt,
51
61
  open,
52
62
  defaultOpen,
53
63
  onOpenChange,
@@ -73,7 +83,7 @@ const SelectDefault = forwardRef<HTMLElement, SelectDefaultComponentProps>(
73
83
  defaultOpen,
74
84
  onOpenChange,
75
85
  });
76
- // Dropdown open 상태를 trigger data-state와 동기화한다.
86
+ // 변경: outside close는 Radix onOpenChange 기본 동작을 사용한다.
77
87
 
78
88
  const handleOptionSelect = (option: SelectDropdownOption) => {
79
89
  onOptionSelect?.(option);
@@ -81,7 +91,7 @@ const SelectDefault = forwardRef<HTMLElement, SelectDefaultComponentProps>(
81
91
  };
82
92
 
83
93
  const panelSize = (dropdownSize ?? size) as DropdownSize;
84
- const shouldRenderDropdown = options.length > 0;
94
+ const hasOptions = options.length > 0;
85
95
 
86
96
  return (
87
97
  <Container
@@ -115,36 +125,45 @@ const SelectDefault = forwardRef<HTMLElement, SelectDefaultComponentProps>(
115
125
  />
116
126
  </SelectTriggerBase>
117
127
  </Dropdown.Trigger>
118
- {shouldRenderDropdown ? (
119
- <Dropdown.Container
120
- {...dropdownContainerProps}
121
- size={panelSize}
122
- width={dropdownWidth}
123
- >
124
- <Dropdown.Menu.List {...dropdownMenuListProps}>
125
- {/* Dropdown menu option들을 그대로 매핑해 선택 이벤트를 전달한다. */}
126
- {options.map(option => (
127
- <Dropdown.Menu.Item
128
- key={option.id}
129
- label={option.label}
130
- description={option.description}
131
- disabled={option.disabled}
132
- left={option.left}
133
- right={option.right}
134
- multiple={Boolean(option.multiple)}
135
- isSelected={resolvedSelectedIds.includes(option.id)}
136
- onSelect={event => {
137
- if (option.disabled) {
138
- event.preventDefault();
139
- return;
140
- }
141
- handleOptionSelect(option);
142
- }}
143
- />
144
- ))}
145
- </Dropdown.Menu.List>
146
- </Dropdown.Container>
147
- ) : null}
128
+ <Dropdown.Container
129
+ {...dropdownContainerProps}
130
+ size={panelSize}
131
+ width={dropdownWidth}
132
+ >
133
+ <Dropdown.Menu.List {...dropdownMenuListProps}>
134
+ {hasOptions ? (
135
+ <>
136
+ {/* Dropdown menu option들을 그대로 매핑해 선택 이벤트를 전달한다. */}
137
+ {options.map(option => (
138
+ <Dropdown.Menu.Item
139
+ key={option.id}
140
+ label={option.label}
141
+ description={option.description}
142
+ disabled={option.disabled}
143
+ left={option.left}
144
+ right={option.right}
145
+ multiple={Boolean(option.multiple)}
146
+ isSelected={resolvedSelectedIds.includes(option.id)}
147
+ onSelect={event => {
148
+ if (option.disabled) {
149
+ event.preventDefault();
150
+ return;
151
+ }
152
+ handleOptionSelect(option);
153
+ }}
154
+ />
155
+ ))}
156
+ </>
157
+ ) : (
158
+ <Dropdown.Menu.Item
159
+ // 변경: 사용처 1회 상수 대신 인라인 fallback으로 empty label을 처리한다.
160
+ label={alt ?? "선택할 항목이 없습니다."}
161
+ disabled
162
+ className="dropdown-menu-alt"
163
+ />
164
+ )}
165
+ </Dropdown.Menu.List>
166
+ </Dropdown.Container>
148
167
  </Dropdown.Root>
149
168
  </Container>
150
169
  );
@@ -8,16 +8,24 @@ import { SelectIcon } from "./Icon";
8
8
  import type { SelectTriggerBaseProps } from "../../types/trigger";
9
9
 
10
10
  /**
11
- * Select trigger foundation; priority/size/state를 data attribute로 노출하고
12
- * Chevron 아이콘을 자동 연결하는 기본 요소다.
11
+ * Select Foundation; Trigger Base 슬롯 렌더링 컴포넌트
13
12
  * @component
14
13
  * @param {SelectTriggerBaseProps} props trigger base props
15
14
  * @param {"primary" | "secondary"} [props.priority="primary"] 스타일 우선순위
16
15
  * @param {"small" | "medium" | "large"} [props.size="medium"] 높이 스케일
17
16
  * @param {"default" | "focused" | "disabled"} [props.state="default"] 시각 상태
18
- * @param {boolean} [props.multiple] multi select 여부
17
+ * @param {boolean} [props.open=false] dropdown open 상태
18
+ * @param {boolean} [props.block=false] block 레이아웃 여부
19
+ * @param {boolean} [props.multiple=false] multi select 여부
20
+ * @param {boolean} [props.disabled=false] disabled 여부
19
21
  * @param {ElementType} [props.as="button"] polymorphic 태그
20
22
  * @param {"button" | "submit" | "reset"} [props.buttonType="button"] native button type
23
+ * @param {string} [props.className] trigger className
24
+ * @param {React.ReactNode} props.children trigger 콘텐츠
25
+ * @example
26
+ * <SelectTriggerBase open={false} size="medium">
27
+ * <span>옵션 선택</span>
28
+ * </SelectTriggerBase>
21
29
  */
22
30
  const SelectTriggerBase = forwardRef<HTMLElement, SelectTriggerBaseProps>(
23
31
  (
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import clsx from "clsx";
4
+ import { forwardRef } from "react";
4
5
 
5
6
  import type { SelectContainerProps } from "../../types/props";
6
7
  import {
@@ -14,40 +15,42 @@ import {
14
15
  * @param {SelectContainerProps} props Select container props
15
16
  * @param {string} [props.className] 사용자 정의 className
16
17
  * @param {boolean} [props.block] wrapper 전체 폭 확장 여부
18
+ * @param {FormFieldWidth} [props.width] Form.Field width preset
19
+ * @param {CSSProperties} [props.style] wrapper inline style
17
20
  * @param {React.ReactNode} props.children trigger 및 dropdown 콘텐츠
18
21
  */
19
- export default function SelectContainer({
20
- className,
21
- children,
22
- block = false,
23
- width,
24
- style,
25
- ...restProps
26
- }: SelectContainerProps) {
27
- const widthAttr =
28
- width !== undefined
29
- ? getFormFieldWidthAttr(width)
30
- : block
31
- ? "full"
32
- : undefined;
33
- const widthValue =
34
- width !== undefined ? getFormFieldWidthValue(width) : undefined;
35
- const mergedStyle =
36
- widthValue !== undefined
37
- ? { ...(style ?? {}), ["--select-width" as const]: widthValue }
38
- : style;
22
+ const SelectContainer = forwardRef<HTMLDivElement, SelectContainerProps>(
23
+ ({ className, children, block = false, width, style, ...restProps }, ref) => {
24
+ const widthAttr =
25
+ width !== undefined
26
+ ? getFormFieldWidthAttr(width)
27
+ : block
28
+ ? "full"
29
+ : undefined;
30
+ const widthValue =
31
+ width !== undefined ? getFormFieldWidthValue(width) : undefined;
32
+ const mergedStyle =
33
+ widthValue !== undefined
34
+ ? { ...(style ?? {}), ["--select-width" as const]: widthValue }
35
+ : style;
39
36
 
40
- return (
41
- <div
42
- className={clsx("select select-container", className, {
43
- "select-block": block,
44
- })}
45
- data-width={widthAttr}
46
- style={mergedStyle}
47
- {...restProps}
48
- >
49
- {/** dropdown root 및 dropdown menu 등 포함 예정 */}
50
- {children}
51
- </div>
52
- );
53
- }
37
+ return (
38
+ <div
39
+ ref={ref}
40
+ className={clsx("select select-container", className, {
41
+ "select-block": block,
42
+ })}
43
+ data-width={widthAttr}
44
+ style={mergedStyle}
45
+ {...restProps}
46
+ >
47
+ {/** dropdown root 및 dropdown menu 등 포함 예정 */}
48
+ {children}
49
+ </div>
50
+ );
51
+ },
52
+ );
53
+
54
+ SelectContainer.displayName = "SelectContainer";
55
+
56
+ export default SelectContainer;
@@ -17,13 +17,27 @@ import { useSelectDropdownOpenState } from "../../hooks";
17
17
  * @component
18
18
  * @param {SelectMultipleComponentProps} props multi trigger props
19
19
  * @param {SelectMultipleTag[]} [props.tags] 선택된 tag 리스트
20
+ * @param {SelectDropdownOption[]} [props.options] dropdown option 목록
21
+ * @param {string[]} [props.selectedOptionIds] 선택된 option id 리스트
22
+ * @param {(option: SelectDropdownOption) => void} [props.onOptionSelect] option 선택 콜백
20
23
  * @param {React.ReactNode} [props.displayLabel] fallback 라벨
21
24
  * @param {React.ReactNode} [props.placeholder] placeholder 텍스트
22
25
  * @param {"primary" | "secondary"} [props.priority="primary"] priority scale
23
26
  * @param {"small" | "medium" | "large"} [props.size="medium"] size scale
27
+ * @param {"default" | "focused" | "disabled"} [props.state="default"] 시각 상태
24
28
  * @param {boolean} [props.block] block 여부
29
+ * @param {FormFieldWidth} [props.width] container width preset
25
30
  * @param {boolean} [props.isOpen] dropdown open 여부
26
31
  * @param {boolean} [props.disabled] disabled 여부
32
+ * @param {"small" | "medium" | "large"} [props.dropdownSize] dropdown panel size
33
+ * @param {"match" | "fit-content" | "max-content" | string | number} [props.dropdownWidth="match"] dropdown panel width
34
+ * @param {Omit<DropdownMenuProps, "open" | "defaultOpen" | "onOpenChange">} [props.dropdownRootProps] Dropdown.Root 전달 props
35
+ * @param {Omit<DropdownContainerProps, "children" | "size" | "width">} [props.dropdownContainerProps] Dropdown.Container 전달 props
36
+ * @param {DropdownMenuListProps} [props.dropdownMenuListProps] Dropdown.Menu.List 전달 props
37
+ * @param {ReactNode} [props.alt] empty 상태 대체 콘텐츠
38
+ * @param {boolean} [props.open] controlled open 상태
39
+ * @param {boolean} [props.defaultOpen] uncontrolled 초기 open 상태
40
+ * @param {(open: boolean) => void} [props.onOpenChange] open 상태 변경 콜백
27
41
  */
28
42
  const SelectMultipleTrigger = forwardRef<
29
43
  HTMLElement,
@@ -50,6 +64,7 @@ const SelectMultipleTrigger = forwardRef<
50
64
  dropdownRootProps,
51
65
  dropdownContainerProps,
52
66
  dropdownMenuListProps,
67
+ alt,
53
68
  open,
54
69
  defaultOpen,
55
70
  onOpenChange,
@@ -100,10 +115,10 @@ const SelectMultipleTrigger = forwardRef<
100
115
  defaultOpen,
101
116
  onOpenChange,
102
117
  });
103
- // multi select에서도 동일한 open 상태를 유지하기 위해 공통 hook을 사용한다.
118
+ // 변경: outside close는 Radix onOpenChange 기본 동작을 사용한다.
104
119
 
105
120
  const panelSize = (dropdownSize ?? size) as DropdownSize;
106
- const shouldRenderDropdown = options.length > 0;
121
+ const hasOptions = options.length > 0;
107
122
  const MAX_VISIBLE_TAGS = 3;
108
123
  const visibleTags = hasTags ? derivedTags.slice(0, MAX_VISIBLE_TAGS) : [];
109
124
  const overflowCount = hasTags
@@ -166,36 +181,45 @@ const SelectMultipleTrigger = forwardRef<
166
181
  )}
167
182
  </SelectTriggerBase>
168
183
  </Dropdown.Trigger>
169
- {shouldRenderDropdown ? (
170
- <Dropdown.Container
171
- {...dropdownContainerProps}
172
- size={panelSize}
173
- width={dropdownWidth}
174
- >
175
- <Dropdown.Menu.List {...dropdownMenuListProps}>
176
- {/* multi select 전용 옵션을 Dropdown.Menu.Item으로 노출한다. */}
177
- {options.map(option => (
178
- <Dropdown.Menu.Item
179
- key={option.id}
180
- label={option.label}
181
- description={option.description}
182
- disabled={option.disabled}
183
- left={option.left}
184
- right={option.right}
185
- multiple
186
- isSelected={resolvedSelectedIds.includes(option.id)}
187
- onSelect={event => {
188
- if (option.disabled) {
189
- event.preventDefault();
190
- return;
191
- }
192
- onOptionSelect?.(option);
193
- }}
194
- />
195
- ))}
196
- </Dropdown.Menu.List>
197
- </Dropdown.Container>
198
- ) : null}
184
+ <Dropdown.Container
185
+ {...dropdownContainerProps}
186
+ size={panelSize}
187
+ width={dropdownWidth}
188
+ >
189
+ <Dropdown.Menu.List {...dropdownMenuListProps}>
190
+ {hasOptions ? (
191
+ <>
192
+ {/* multi select 전용 옵션을 Dropdown.Menu.Item으로 노출한다. */}
193
+ {options.map(option => (
194
+ <Dropdown.Menu.Item
195
+ key={option.id}
196
+ label={option.label}
197
+ description={option.description}
198
+ disabled={option.disabled}
199
+ left={option.left}
200
+ right={option.right}
201
+ multiple
202
+ isSelected={resolvedSelectedIds.includes(option.id)}
203
+ onSelect={event => {
204
+ if (option.disabled) {
205
+ event.preventDefault();
206
+ return;
207
+ }
208
+ onOptionSelect?.(option);
209
+ }}
210
+ />
211
+ ))}
212
+ </>
213
+ ) : (
214
+ <Dropdown.Menu.Item
215
+ // 변경: 사용처 1회 상수 대신 인라인 fallback으로 empty label을 처리한다.
216
+ label={alt ?? "선택할 항목이 없습니다."}
217
+ disabled
218
+ className="dropdown-menu-alt"
219
+ />
220
+ )}
221
+ </Dropdown.Menu.List>
222
+ </Dropdown.Container>
199
223
  </Dropdown.Root>
200
224
  </Container>
201
225
  );
@@ -6,13 +6,16 @@ import RemoveIcon from "../../img/remove.svg";
6
6
  import type { SelectMultipleChipProps } from "../../types/multiple";
7
7
 
8
8
  /**
9
- * Select multi chip; 선택된 값을 chip 형태로 표시하고 필요 시 제거 버튼을 노출한다.
9
+ * Select Markup; Multiple 선택값 Chip 렌더링 컴포넌트
10
10
  * @component
11
11
  * @param {SelectMultipleChipProps} props chip props
12
12
  * @param {React.ReactNode} props.label chip 라벨
13
13
  * @param {React.ReactNode} [props.suffix] 라벨 뒤에 붙는 서브 라벨
14
- * @param {boolean} [props.removable] remove 버튼 노출 여부
14
+ * @param {boolean} [props.removable=true] remove 버튼 노출 여부
15
15
  * @param {() => void} [props.onRemove] remove 클릭 핸들러
16
+ * @param {"value" | "summary"} [props.kind="value"] chip 용도 구분
17
+ * @example
18
+ * <SelectMultipleSelectedChip label="Apple" removable onRemove={() => {}} />
16
19
  */
17
20
  export function SelectMultipleSelectedChip({
18
21
  label,
@@ -1,5 +1,6 @@
1
1
  export type * from "./base";
2
2
  export type * from "./icon";
3
+ export type * from "./interaction";
3
4
  export type * from "./props";
4
5
  export type * from "./trigger";
5
6
  export type * from "./multiple";
@@ -0,0 +1,30 @@
1
+ import type { SelectDropdownBehaviorProps } from "./props";
2
+
3
+ /**
4
+ * Select Hook Types; Dropdown open state hook 입력 파라미터
5
+ * @property {boolean} [open] 외부 제어형 open 상태
6
+ * @property {boolean} [defaultOpen] 비제어형 초기 open 상태
7
+ * @property {(open: boolean) => void} [onOpenChange] open 상태 변경 콜백
8
+ */
9
+ export interface UseSelectDropdownOpenStateParams extends SelectDropdownBehaviorProps {}
10
+
11
+ /**
12
+ * Select Hook Types; Dropdown open state hook 반환값
13
+ * @property {boolean} open 최종 open 상태
14
+ * @property {(nextOpen: boolean) => void} setOpen open 상태 업데이트 함수
15
+ * @property {boolean} isControlled open prop 기반 제어형 여부
16
+ */
17
+ export interface UseSelectDropdownOpenStateReturn {
18
+ /**
19
+ * 최종 open 상태
20
+ */
21
+ open: boolean;
22
+ /**
23
+ * open 상태 업데이트 함수
24
+ */
25
+ setOpen: (nextOpen: boolean) => void;
26
+ /**
27
+ * open prop 기반 제어형 여부
28
+ */
29
+ isControlled: boolean;
30
+ }
@@ -1,13 +1,87 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
3
  /**
4
- * Select option 타입
4
+ * Select option value 타입
5
5
  * @typedef {string | number} SelectOptionValue
6
6
  */
7
7
  export type SelectOptionValue = string | number;
8
8
 
9
9
  /**
10
- * Select option 데이터 구조; legacy SelectDataType과 호환되는 구조다.
10
+ * Select option data; form 상태와 직결되는 데이터 계약
11
+ * @property {string} id 렌더링/선택 추적용 고유 id
12
+ * @property {SelectOptionValue} value 실제 form value
13
+ * @property {ReactNode} label 사용자 노출 라벨
14
+ * @property {boolean} [disabled] 비활성 여부
15
+ * @property {OptionData} [data] 추가 데이터 payload
16
+ */
17
+ export interface SelectOptionData<OptionData = unknown> {
18
+ /**
19
+ * 렌더링/선택 추적용 고유 id
20
+ */
21
+ id: string;
22
+ /**
23
+ * 실제 form value
24
+ */
25
+ value: SelectOptionValue;
26
+ /**
27
+ * 사용자 노출 라벨
28
+ */
29
+ label: ReactNode;
30
+ /**
31
+ * 비활성 여부
32
+ */
33
+ disabled?: boolean;
34
+ /**
35
+ * 추가 데이터 payload
36
+ */
37
+ data?: OptionData;
38
+ }
39
+
40
+ /**
41
+ * Select option render data; Dropdown.Menu.Item 렌더링 보조 정보
42
+ * @property {ReactNode} [description] 보조 텍스트
43
+ * @property {ReactNode} [left] 좌측 콘텐츠
44
+ * @property {ReactNode} [right] 우측 콘텐츠
45
+ * @property {boolean} [multiple] multi select 스타일 여부
46
+ */
47
+ export interface SelectOptionRenderData {
48
+ /**
49
+ * 보조 텍스트
50
+ */
51
+ description?: ReactNode;
52
+ /**
53
+ * 좌측 콘텐츠
54
+ */
55
+ left?: ReactNode;
56
+ /**
57
+ * 우측 콘텐츠
58
+ */
59
+ right?: ReactNode;
60
+ /**
61
+ * multi select 스타일 여부
62
+ */
63
+ multiple?: boolean;
64
+ }
65
+
66
+ /**
67
+ * Select dropdown option; data 계약과 render 계약을 합쳐 Select 입력 모델을 구성한다.
68
+ * @extends SelectOptionData
69
+ * @extends SelectOptionRenderData
70
+ * @property {string} id 렌더링/선택 추적용 고유 id
71
+ * @property {SelectOptionValue} value 실제 form value
72
+ * @property {ReactNode} label 사용자 노출 라벨
73
+ * @property {boolean} [disabled] 비활성 여부
74
+ * @property {OptionData} [data] 추가 데이터 payload
75
+ * @property {ReactNode} [description] 보조 텍스트
76
+ * @property {ReactNode} [left] 좌측 콘텐츠
77
+ * @property {ReactNode} [right] 우측 콘텐츠
78
+ * @property {boolean} [multiple] multi select 스타일 여부
79
+ */
80
+ export interface SelectDropdownOption<OptionData = unknown>
81
+ extends SelectOptionData<OptionData>, SelectOptionRenderData {}
82
+
83
+ /**
84
+ * Select legacy option 데이터 구조; 과거 key/optionName 스키마 호환을 위해 유지한다.
11
85
  * @property {string} key 렌더링 키
12
86
  * @property {SelectOptionValue} value 실제 값
13
87
  * @property {ReactNode} optionName 사용자 노출 라벨
@@ -15,7 +89,7 @@ export type SelectOptionValue = string | number;
15
89
  * @property {boolean} [disabled] 비활성 여부
16
90
  * @property {OptionData} [data] 추가 데이터 payload
17
91
  */
18
- export interface SelectOption<OptionData = unknown> {
92
+ export interface SelectLegacyOption<OptionData = unknown> {
19
93
  /**
20
94
  * 렌더링 키
21
95
  */
@@ -1,14 +1,14 @@
1
1
  import type { HTMLAttributes, ReactNode } from "react";
2
+ import type { DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
2
3
 
3
4
  import type {
4
5
  DropdownContainerProps,
5
6
  DropdownMenuListProps,
6
7
  DropdownPanelWidth,
7
- DropdownRootProps,
8
- DropdownTemplateItem,
9
8
  } from "../../dropdown/types";
10
9
  import type { FormFieldWidth } from "../../form/types";
11
10
  import type { SelectPriority, SelectSize, SelectState } from "./base";
11
+ import type { SelectDropdownOption } from "./option";
12
12
  import type {
13
13
  SelectTriggerDefaultProps,
14
14
  SelectTriggerMultipleProps,
@@ -116,18 +116,22 @@ export interface SelectWidthOption {
116
116
  /**
117
117
  * Select root props
118
118
  * @typedef {SelectStyleOptions & SelectValueOptions & SelectComponentState} SelectProps
119
+ * @property {SelectPriority} [priority] priority scale
120
+ * @property {SelectSize} [size] size scale
121
+ * @property {SelectState} [state] visual state
122
+ * @property {boolean} [block] block 여부
123
+ * @property {ReactNode} [displayLabel] 선택된 라벨
124
+ * @property {ReactNode} [placeholder] placeholder 텍스트
125
+ * @property {ReactNode[]} [tags] multi select 태그 리스트
126
+ * @property {boolean} [multiple] multi select 여부
127
+ * @property {boolean} [isOpen] dropdown open 여부
128
+ * @property {FormFieldWidth} [width] width preset 옵션
119
129
  */
120
130
  export type SelectProps = SelectStyleOptions &
121
131
  SelectValueOptions &
122
132
  SelectComponentState &
123
133
  SelectWidthOption;
124
134
 
125
- /**
126
- * Select dropdown option; Dropdown.Template item 계약을 그대로 따른다.
127
- * @extends DropdownTemplateItem
128
- */
129
- export interface SelectDropdownOption extends DropdownTemplateItem {}
130
-
131
135
  /**
132
136
  * Select dropdown 옵션 구성 props
133
137
  * @property {SelectDropdownOption[]} [options] dropdown option 리스트
@@ -135,9 +139,10 @@ export interface SelectDropdownOption extends DropdownTemplateItem {}
135
139
  * @property {(option: SelectDropdownOption) => void} [onOptionSelect] option 선택 콜백
136
140
  * @property {SelectSize} [dropdownSize] dropdown surface size 스케일
137
141
  * @property {DropdownPanelWidth} [dropdownWidth="match"] dropdown panel width 옵션
138
- * @property {DropdownRootProps} [dropdownRootProps] Dropdown.Root 전달 props(제어 props 제외)
142
+ * @property {DropdownMenuProps} [dropdownRootProps] Dropdown.Root 전달 props(제어 props 제외)
139
143
  * @property {DropdownContainerProps} [dropdownContainerProps] Dropdown.Container 전달 props(children/size 제외)
140
144
  * @property {DropdownMenuListProps} [dropdownMenuListProps] Dropdown.Menu.List 전달 props
145
+ * @property {ReactNode} [alt] option이 비어 있을 때 렌더링할 alternate 콘텐츠
141
146
  */
142
147
  export interface SelectDropdownConfigProps {
143
148
  /**
@@ -162,9 +167,10 @@ export interface SelectDropdownConfigProps {
162
167
  dropdownWidth?: DropdownPanelWidth;
163
168
  /**
164
169
  * Dropdown.Root 전달 props(제어 props 제외)
170
+ * - 타입 출처를 명확히 하기 위해 Radix 원본 타입을 직접 사용한다.
165
171
  */
166
172
  dropdownRootProps?: Omit<
167
- DropdownRootProps,
173
+ DropdownMenuProps,
168
174
  "open" | "defaultOpen" | "onOpenChange"
169
175
  >;
170
176
  /**
@@ -178,6 +184,10 @@ export interface SelectDropdownConfigProps {
178
184
  * Dropdown.Menu.List 전달 props
179
185
  */
180
186
  dropdownMenuListProps?: DropdownMenuListProps;
187
+ /**
188
+ * option이 비어 있을 때 렌더링할 alternate 콘텐츠
189
+ */
190
+ alt?: ReactNode;
181
191
  }
182
192
 
183
193
  /**
@@ -203,26 +213,62 @@ export interface SelectDropdownBehaviorProps {
203
213
 
204
214
  /**
205
215
  * Select.Default 컴포넌트 props
206
- * @extends SelectTriggerDefaultProps
207
- * @extends SelectDropdownConfigProps
208
- * @extends SelectDropdownBehaviorProps
216
+ * @typedef {SelectTriggerDefaultProps & SelectDropdownConfigProps & SelectDropdownBehaviorProps & SelectWidthOption} SelectDefaultComponentProps
217
+ * @property {ReactNode} [displayLabel] 선택된 라벨
218
+ * @property {ReactNode} [placeholder] placeholder 텍스트
219
+ * @property {SelectPriority} [priority] priority scale
220
+ * @property {SelectSize} [size] size scale
221
+ * @property {SelectState} [state] visual state
222
+ * @property {boolean} [block] block 여부
223
+ * @property {boolean} [isOpen] dropdown open 여부
224
+ * @property {boolean} [disabled] disabled 여부
225
+ * @property {SelectTriggerButtonType} [buttonType] button type
226
+ * @property {FormFieldWidth} [width] width preset 옵션
227
+ * @property {SelectDropdownOption[]} [options] dropdown option 리스트
228
+ * @property {string[]} [selectedOptionIds] 선택된 option id 리스트
229
+ * @property {(option: SelectDropdownOption) => void} [onOptionSelect] option 선택 콜백
230
+ * @property {SelectSize} [dropdownSize] dropdown surface size 스케일
231
+ * @property {DropdownPanelWidth} [dropdownWidth="match"] dropdown panel width 옵션
232
+ * @property {Omit<DropdownMenuProps, "open" | "defaultOpen" | "onOpenChange">} [dropdownRootProps] Dropdown.Root 전달 props
233
+ * @property {Omit<DropdownContainerProps, "children" | "size" | "width">} [dropdownContainerProps] Dropdown.Container 전달 props
234
+ * @property {DropdownMenuListProps} [dropdownMenuListProps] Dropdown.Menu.List 전달 props
235
+ * @property {ReactNode} [alt] option이 비어 있을 때 렌더링할 alternate 콘텐츠
236
+ * @property {boolean} [open] dropdown open 상태
237
+ * @property {boolean} [defaultOpen] uncontrolled 초기 open 상태
238
+ * @property {(open: boolean) => void} [onOpenChange] open state change 콜백
209
239
  */
210
- export interface SelectDefaultComponentProps
211
- extends
212
- SelectTriggerDefaultProps,
213
- SelectDropdownConfigProps,
214
- SelectDropdownBehaviorProps,
215
- SelectWidthOption {}
240
+ export type SelectDefaultComponentProps = SelectTriggerDefaultProps &
241
+ SelectDropdownConfigProps &
242
+ SelectDropdownBehaviorProps &
243
+ SelectWidthOption;
216
244
 
217
245
  /**
218
246
  * Select.Multiple 컴포넌트 props
219
- * @extends SelectTriggerMultipleProps
220
- * @extends SelectDropdownConfigProps
221
- * @extends SelectDropdownBehaviorProps
247
+ * @typedef {SelectTriggerMultipleProps & SelectDropdownConfigProps & SelectDropdownBehaviorProps & SelectWidthOption} SelectMultipleComponentProps
248
+ * @property {ReactNode} [displayLabel] 선택된 라벨
249
+ * @property {ReactNode} [placeholder] placeholder 텍스트
250
+ * @property {SelectMultipleTag[]} [tags] multi select tag 리스트
251
+ * @property {SelectPriority} [priority] priority scale
252
+ * @property {SelectSize} [size] size scale
253
+ * @property {SelectState} [state] visual state
254
+ * @property {boolean} [block] block 여부
255
+ * @property {boolean} [isOpen] dropdown open 여부
256
+ * @property {boolean} [disabled] disabled 여부
257
+ * @property {FormFieldWidth} [width] width preset 옵션
258
+ * @property {SelectDropdownOption[]} [options] dropdown option 리스트
259
+ * @property {string[]} [selectedOptionIds] 선택된 option id 리스트
260
+ * @property {(option: SelectDropdownOption) => void} [onOptionSelect] option 선택 콜백
261
+ * @property {SelectSize} [dropdownSize] dropdown surface size 스케일
262
+ * @property {DropdownPanelWidth} [dropdownWidth="match"] dropdown panel width 옵션
263
+ * @property {Omit<DropdownMenuProps, "open" | "defaultOpen" | "onOpenChange">} [dropdownRootProps] Dropdown.Root 전달 props
264
+ * @property {Omit<DropdownContainerProps, "children" | "size" | "width">} [dropdownContainerProps] Dropdown.Container 전달 props
265
+ * @property {DropdownMenuListProps} [dropdownMenuListProps] Dropdown.Menu.List 전달 props
266
+ * @property {ReactNode} [alt] option이 비어 있을 때 렌더링할 alternate 콘텐츠
267
+ * @property {boolean} [open] dropdown open 상태
268
+ * @property {boolean} [defaultOpen] uncontrolled 초기 open 상태
269
+ * @property {(open: boolean) => void} [onOpenChange] open state change 콜백
222
270
  */
223
- export interface SelectMultipleComponentProps
224
- extends
225
- SelectTriggerMultipleProps,
226
- SelectDropdownConfigProps,
227
- SelectDropdownBehaviorProps,
228
- SelectWidthOption {}
271
+ export type SelectMultipleComponentProps = SelectTriggerMultipleProps &
272
+ SelectDropdownConfigProps &
273
+ SelectDropdownBehaviorProps &
274
+ SelectWidthOption;