@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.
Files changed (83) hide show
  1. package/README.md +209 -3
  2. package/dist/styles.css +91 -76
  3. package/package.json +1 -1
  4. package/src/components/alternate/index.tsx +7 -1
  5. package/src/components/alternate/markup/Label.tsx +10 -5
  6. package/src/components/alternate/markup/empty/Data.tsx +9 -6
  7. package/src/components/alternate/markup/index.tsx +8 -0
  8. package/src/components/alternate/markup/loading/Default.tsx +10 -6
  9. package/src/components/alternate/markup/loading/Icon.tsx +11 -4
  10. package/src/components/alternate/types/index.ts +75 -2
  11. package/src/components/badge/index.tsx +4 -1
  12. package/src/components/badge/markup/Badge.tsx +10 -8
  13. package/src/components/badge/types/index.ts +26 -2
  14. package/src/components/button/index.tsx +6 -1
  15. package/src/components/button/markup/Base.tsx +20 -18
  16. package/src/components/button/markup/Rounded.tsx +7 -4
  17. package/src/components/button/markup/Text.tsx +7 -4
  18. package/src/components/calendar/index.tsx +8 -0
  19. package/src/components/calendar/markup/index.tsx +7 -7
  20. package/src/components/carousel/index.tsx +8 -0
  21. package/src/components/carousel/markup/index.tsx +9 -0
  22. package/src/components/checkbox/index.tsx +7 -0
  23. package/src/components/chip/index.tsx +7 -1
  24. package/src/components/chip/markup/index.tsx +9 -0
  25. package/src/components/divider/index.tsx +4 -0
  26. package/src/components/divider/markup/Divider.tsx +11 -7
  27. package/src/components/divider/types/index.ts +1 -0
  28. package/src/components/divider/types/props.ts +27 -0
  29. package/src/components/drawer/index.tsx +7 -0
  30. package/src/components/drawer/markup/index.tsx +6 -0
  31. package/src/components/dropdown/index.tsx +7 -0
  32. package/src/components/dropdown/markup/Template.tsx +9 -2
  33. package/src/components/dropdown/markup/foundation/Container.tsx +30 -12
  34. package/src/components/dropdown/markup/index.tsx +9 -10
  35. package/src/components/dropdown/types/base.ts +13 -0
  36. package/src/components/dropdown/types/props.ts +19 -2
  37. package/src/components/form/index.tsx +7 -0
  38. package/src/components/form/markup/index.tsx +6 -2
  39. package/src/components/info-box/index.tsx +7 -0
  40. package/src/components/info-box/markup/InfoBox.tsx +1 -1
  41. package/src/components/info-box/markup/index.ts +6 -0
  42. package/src/components/info-box/types/props.ts +2 -2
  43. package/src/components/input/index.tsx +6 -1
  44. package/src/components/input/markup/foundation/Input.tsx +2 -2
  45. package/src/components/input/styles/foundation.scss +57 -54
  46. package/src/components/input/types/foundation.ts +1 -1
  47. package/src/components/navigation/index.tsx +7 -0
  48. package/src/components/navigation/markup/index.tsx +6 -0
  49. package/src/components/pagination/index.tsx +6 -1
  50. package/src/components/pagination/markup/index.tsx +7 -0
  51. package/src/components/pop-over/index.tsx +7 -0
  52. package/src/components/pop-over/markup/index.tsx +5 -4
  53. package/src/components/radio/index.tsx +5 -1
  54. package/src/components/scrollbar/hooks/index.ts +1 -1
  55. package/src/components/scrollbar/index.tsx +1 -1
  56. package/src/components/scrollbar/markup/index.tsx +1 -1
  57. package/src/components/scrollbar/types/index.ts +1 -1
  58. package/src/components/scrollbar/utils/index.ts +1 -1
  59. package/src/components/segmented-control/index.tsx +5 -1
  60. package/src/components/segmented-control/markup/index.ts +6 -0
  61. package/src/components/select/index.tsx +6 -1
  62. package/src/components/select/markup/Default.tsx +10 -13
  63. package/src/components/select/markup/foundation/Selected.tsx +31 -26
  64. package/src/components/select/markup/index.tsx +1 -1
  65. package/src/components/select/markup/multiple/Multiple.tsx +32 -15
  66. package/src/components/select/styles/select.scss +15 -6
  67. package/src/components/select/styles/variables.scss +4 -0
  68. package/src/components/select/types/multiple.ts +19 -0
  69. package/src/components/select/types/props.ts +19 -6
  70. package/src/components/select/utils/display.tsx +41 -0
  71. package/src/components/select/utils/index.ts +1 -4
  72. package/src/components/slot/index.tsx +7 -0
  73. package/src/components/slot/markup/index.tsx +6 -0
  74. package/src/components/spinner/hooks/index.ts +1 -1
  75. package/src/components/spinner/index.tsx +1 -1
  76. package/src/components/spinner/markup/index.tsx +1 -1
  77. package/src/components/spinner/types/index.ts +1 -1
  78. package/src/components/spinner/utils/index.ts +1 -1
  79. package/src/components/tab/index.tsx +5 -1
  80. package/src/components/tab/markup/index.tsx +8 -0
  81. package/src/components/table/index.tsx +3 -0
  82. package/src/components/tooltip/index.tsx +7 -0
  83. 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 = toInputText(placeholder);
52
+ const resolvedPlaceholder = toSelectInputText(placeholder);
60
53
  // 변경 설명: readOnly + customOptions 조합에서 valueText가 비어도 inputProps.value를 표시 문자열로 사용한다.
61
- const resolvedInputValueText = toInputText(inputProps?.value as ReactNode);
54
+ const resolvedInputValueText = toSelectInputText(
55
+ inputProps?.value as ReactNode,
56
+ );
62
57
  const resolvedLabelText =
63
- valueText || resolvedInputValueText || toInputText(label);
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에서 non-text label을 직접 렌더링하되, placeholder 상태일 때는 input placeholder 경로를 유지한다.
77
- // custom mode처럼 valueText가 있는 경우에는 input 렌더 경로를 유지해 값 표시를 보장한다.
78
- const shouldRenderReadonlyLabelNode =
79
- readOnly &&
80
- !isLabelTextLike &&
81
- !isPlaceholder &&
82
- !shouldUsePlaceholderStyle &&
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
- {shouldRenderReadonlyLabelNode ? (
100
- // 변경 설명: readonly + ReactNode label일 때는 문자열 변환 없이 label 노드를 직접 렌더링한다.
99
+ {shouldRenderReadonlyView ? (
100
+ // 변경 설명: readOnly 표시 경로는 text/ReactNode 그대로 유지해 trigger width가 실제 표시 콘텐츠에 맞춰지도록 한다.
101
101
  <div
102
- className={clsx("select-input-label", inputProps?.className, {
103
- "select-input-label-placeholder": shouldUsePlaceholderStyle,
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
- {label}
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
- * Selec; 컴포넌트 모듈
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
- (resolvedSelectedValues.length > 0
214
- ? optionMap.get(toSelectedValueKey(resolvedSelectedValues[0]))?.label
215
- : undefined);
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 (optionMap.size === 0 || resolvedSelectedValues.length === 0) {
241
+ if (selectedOptions.length === 0) {
228
242
  return [];
229
243
  }
230
244
 
231
- return resolvedSelectedValues
232
- .map(selectedValue => optionMap.get(toSelectedValueKey(selectedValue)))
233
- .filter((option): option is NonNullable<typeof option> => Boolean(option))
234
- .map(option => ({
235
- label: option.label,
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-disabled);
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: transparent;
185
- background-color: var(--select-color-surface-secondary-disabled);
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-disabled);
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: transparent;
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<DropdownContainerProps, "children" | "size" | "width">;
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,4 +1 @@
1
- /**
2
- * TODO(select): 토큰 매핑과 클래스명 유틸을 구현한다.
3
- */
4
- export {};
1
+ export { renderCommaSeparatedSelectLabels, toSelectInputText } from "./display";
@@ -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,4 +1,4 @@
1
1
  /**
2
- * TODO(spinner): 접근성/상태 계산 hook을 정의한다.
2
+ * Spinner Hooks; 현재 공개된 훅이 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,4 +1,4 @@
1
1
  /**
2
- * spinner 카테고리 배럴 placeholder: 실제 구현은 markup/ 하위에 추가한다.
2
+ * Spinner; 현재 public export가 없는 placeholder 카테고리 배럴
3
3
  */
4
4
  export * from "./markup";
@@ -1,4 +1,4 @@
1
1
  /**
2
- * TODO(spinner): SOT 사용자 제약에 따라 컴포넌트를 구현한다.
2
+ * Spinner Markup; 현재 공개된 컴포넌트가 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,4 +1,4 @@
1
1
  /**
2
- * TODO(spinner): variant/slot 타입 정의를 작성한다.
2
+ * Spinner Types; 현재 공개된 타입이 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,4 +1,4 @@
1
1
  /**
2
- * TODO(spinner): 토큰 매핑과 클래스명 유틸을 구현한다.
2
+ * Spinner Utils; 현재 공개된 유틸이 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,5 +1,9 @@
1
1
  /**
2
- * tab 카테고리 배럴: 실제 구현은 markup/ 하위에서 관리한다.
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,5 +1,8 @@
1
1
  import "./index.scss";
2
2
 
3
+ /**
4
+ * Table; foundation 조립과 container preset을 함께 제공하는 table 엔트리
5
+ */
3
6
  export * from "./markup";
4
7
  export * from "./hooks";
5
8
  export type * from "./types";
@@ -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 namespace.
9
- * - Message
10
- * - Text
11
- * - Trigger
12
- * - Root
13
- * - Template
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,