@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
@@ -6,28 +6,28 @@
6
6
  flex: var(--input-flex);
7
7
  min-width: 0;
8
8
 
9
- &[data-width="auto"] {
9
+ &:where([data-width="auto"]) {
10
10
  --input-width: auto;
11
11
  --input-flex: 0 1 auto;
12
12
  }
13
13
 
14
- &[data-width="fill"] {
14
+ &:where([data-width="fill"]) {
15
15
  --input-width: auto;
16
16
  --input-flex: 1 1 0%;
17
17
  }
18
18
 
19
- &[data-width="full"],
20
- &[data-block="true"] {
19
+ &:where([data-width="full"]),
20
+ &:where([data-block="true"]) {
21
21
  --input-width: 100%;
22
22
  --input-flex: 0 0 100%;
23
23
  }
24
24
 
25
- &[data-width="fit"] {
25
+ &:where([data-width="fit"]) {
26
26
  --input-width: fit-content;
27
27
  --input-flex: 0 0 auto;
28
28
  }
29
29
 
30
- &[data-width="custom"] {
30
+ &:where([data-width="custom"]) {
31
31
  --input-flex: 0 0 auto;
32
32
  }
33
33
 
@@ -35,7 +35,7 @@
35
35
  width: 100%;
36
36
  }
37
37
 
38
- &[data-priority="table"] {
38
+ &:where([data-priority="table"]) {
39
39
  display: block;
40
40
  --input-width: 100%;
41
41
  height: 100%;
@@ -87,7 +87,7 @@
87
87
  outline: none;
88
88
  box-shadow: none;
89
89
 
90
- &[data-size="small"] {
90
+ &:where([data-size="small"]) {
91
91
  min-height: var(--input-default-height-small);
92
92
  // Figma small 규격(40px/15px/1.4/0.2px)을 size token으로 고정한다.
93
93
  --input-font-size: var(--input-text-small-size);
@@ -96,7 +96,7 @@
96
96
  --input-letter-spacing: var(--input-text-small-letter-spacing);
97
97
  }
98
98
 
99
- &[data-size="large"] {
99
+ &:where([data-size="large"]) {
100
100
  min-height: var(--input-default-height-large);
101
101
  // Figma large 규격(56px/19px)을 size token으로 고정한다.
102
102
  --input-font-size: var(--input-text-large-size);
@@ -105,7 +105,7 @@
105
105
  --input-letter-spacing: var(--input-text-large-letter-spacing);
106
106
  }
107
107
 
108
- &[data-priority="secondary"] {
108
+ &:where([data-priority="secondary"]) {
109
109
  border: none;
110
110
  border-bottom: var(--input-border-width-default) solid
111
111
  var(--input-border-color);
@@ -114,29 +114,29 @@
114
114
  padding-block: var(--input-secondary-padding-block);
115
115
  background-color: var(--input-secondary-surface-color);
116
116
 
117
- &[data-state="active"],
118
- &[data-state="focused"] {
117
+ &:where([data-state="active"]),
118
+ &:where([data-state="focused"]) {
119
119
  border-bottom-color: var(--input-border-active-color);
120
120
  border-bottom-width: var(--input-border-width-emphasis);
121
121
  }
122
122
 
123
- &[data-state="success"] {
123
+ &:where([data-state="success"]) {
124
124
  border-bottom-color: var(--input-border-success-color);
125
125
  border-bottom-width: var(--input-border-width-emphasis);
126
126
  }
127
127
 
128
- &[data-state="error"] {
128
+ &:where([data-state="error"]) {
129
129
  border-bottom-color: var(--input-border-error-color);
130
130
  border-bottom-width: var(--input-border-width-emphasis);
131
131
  }
132
132
 
133
- &[data-state="disabled"] {
133
+ &:where([data-state="disabled"]) {
134
134
  border-bottom-color: var(--input-border-underline-disabled-color);
135
135
  border-bottom-width: var(--input-border-width-default);
136
136
  }
137
137
  }
138
138
 
139
- &[data-priority="tertiary"] {
139
+ &:where([data-priority="tertiary"]) {
140
140
  border-radius: var(--input-tertiary-radius-base);
141
141
  background-color: var(--input-surface-color);
142
142
  min-height: var(--input-tertiary-height-base);
@@ -178,42 +178,42 @@
178
178
  }
179
179
  }
180
180
 
181
- &[data-priority="table"] {
181
+ &:where([data-priority="table"]) {
182
182
  height: 100%;
183
183
 
184
184
  border-radius: var(--input-table-radius-base);
185
185
  border-color: var(--input-border-table-default-color);
186
186
  background-color: var(--input-table-surface-color);
187
187
 
188
- &[data-state="disabled"] {
188
+ &:where([data-state="disabled"]) {
189
189
  border-color: var(--input-border-table-disabled-color);
190
190
  background-color: var(--input-table-surface-disabled-color);
191
191
  }
192
192
 
193
- &[data-readonly="true"] {
193
+ &:where([data-readonly="true"]) {
194
194
  border-color: var(--input-border-table-readonly-color);
195
195
  background-color: var(--input-table-surface-readonly-color);
196
196
  }
197
197
  }
198
198
 
199
199
  &:not([data-priority="secondary"]) {
200
- &[data-state="active"],
201
- &[data-state="focused"] {
200
+ &:where([data-state="active"]),
201
+ &:where([data-state="focused"]) {
202
202
  border-color: var(--input-border-active-color);
203
203
  border-width: var(--input-border-width-emphasis);
204
204
  }
205
205
 
206
- &[data-state="success"] {
206
+ &:where([data-state="success"]) {
207
207
  border-color: var(--input-border-success-color);
208
208
  border-width: var(--input-border-width-emphasis);
209
209
  }
210
210
 
211
- &[data-state="error"] {
211
+ &:where([data-state="error"]) {
212
212
  border-color: var(--input-border-error-color);
213
213
  border-width: var(--input-border-width-emphasis);
214
214
  }
215
215
 
216
- &[data-state="disabled"] {
216
+ &:where([data-state="disabled"]) {
217
217
  border-color: var(--input-border-disabled-color);
218
218
  border-width: var(--input-border-width-default);
219
219
  background-color: var(
@@ -221,13 +221,13 @@
221
221
  ); /* disabled 배경 토큰 */
222
222
  }
223
223
 
224
- &[data-readonly="true"] {
224
+ &:where([data-readonly="true"]) {
225
225
  border-color: var(--input-border-disabled-color);
226
226
  border-width: var(--input-border-width-default);
227
227
  background-color: var(--input-surface-disabled-color);
228
228
  }
229
229
 
230
- &[data-state="error"][data-readonly="true"] {
230
+ &:where([data-state="error"][data-readonly="true"]) {
231
231
  // 변경: calendar trigger처럼 readOnly input에서도 error state를 우선 노출한다.
232
232
  border-color: var(--input-border-error-color);
233
233
  border-width: var(--input-border-width-emphasis);
@@ -279,7 +279,7 @@
279
279
  }
280
280
  }
281
281
 
282
- .input-field[data-has-value="true"] .input-element:not(:disabled) {
282
+ .input-field:where([data-has-value="true"]) .input-element:not(:disabled) {
283
283
  color: var(--input-text-color);
284
284
  caret-color: var(--input-text-color);
285
285
  }
@@ -292,7 +292,7 @@
292
292
  min-width: 0;
293
293
  }
294
294
 
295
- .input[data-input-type="textarea"] .input-field {
295
+ .input:where([data-input-type="textarea"]) .input-field {
296
296
  // TextArea는 size별로 radius/spacing을 분기해 input과 동일한 스케일 감각을 유지한다.
297
297
  --input-textarea-radius: var(--input-textarea-radius-medium);
298
298
  --input-textarea-padding-inline: var(--input-textarea-padding-inline-medium);
@@ -305,14 +305,14 @@
305
305
  padding: var(--input-textarea-padding-block)
306
306
  var(--input-textarea-padding-inline);
307
307
 
308
- &[data-size="small"] {
308
+ &:where([data-size="small"]) {
309
309
  --input-textarea-radius: var(--input-textarea-radius-small);
310
310
  --input-textarea-padding-inline: var(--input-textarea-padding-inline-small);
311
311
  --input-textarea-padding-block: var(--input-textarea-padding-block-small);
312
312
  --input-textarea-gap: var(--input-textarea-gap-small);
313
313
  }
314
314
 
315
- &[data-size="large"] {
315
+ &:where([data-size="large"]) {
316
316
  --input-textarea-radius: var(--input-textarea-radius-large);
317
317
  --input-textarea-padding-inline: var(--input-textarea-padding-inline-large);
318
318
  --input-textarea-padding-block: var(--input-textarea-padding-block-large);
@@ -321,7 +321,7 @@
321
321
  }
322
322
 
323
323
  // 변경: textarea 높이 토큰은 native element가 아니라 root layout이 소유하고 내부는 남은 높이를 채운다.
324
- .input[data-input-type="textarea"] {
324
+ .input:where([data-input-type="textarea"]) {
325
325
  min-height: var(--input-textarea-height);
326
326
 
327
327
  .input-box {
@@ -376,27 +376,30 @@
376
376
  color: var(--input-label-color);
377
377
  }
378
378
 
379
- .input-field[data-priority="secondary"] .input-element {
379
+ .input-field:where([data-priority="secondary"]) .input-element {
380
380
  padding-inline: 0;
381
381
  }
382
382
 
383
- .input-field[data-priority="tertiary"] .input-element {
383
+ .input-field:where([data-priority="tertiary"]) .input-element {
384
384
  min-height: var(--input-tertiary-element-min-height);
385
385
  }
386
386
 
387
- .input-field[data-priority="table"][data-size="small"] .input-element {
387
+ .input-field:where([data-priority="table"]):where([data-size="small"])
388
+ .input-element {
388
389
  font-size: var(--input-table-text-small-size);
389
390
  line-height: var(--input-table-text-small-line-height);
390
391
  font-weight: var(--input-table-text-small-weight);
391
392
  }
392
393
 
393
- .input-field[data-priority="table"][data-size="medium"] .input-element {
394
+ .input-field:where([data-priority="table"]):where([data-size="medium"])
395
+ .input-element {
394
396
  font-size: var(--input-table-text-medium-size);
395
397
  line-height: var(--input-table-text-medium-line-height);
396
398
  font-weight: var(--input-table-text-medium-weight);
397
399
  }
398
400
 
399
- .input-field[data-priority="table"][data-size="large"] .input-element {
401
+ .input-field:where([data-priority="table"]):where([data-size="large"])
402
+ .input-element {
400
403
  font-size: var(--input-table-text-large-size);
401
404
  line-height: var(--input-table-text-large-line-height);
402
405
  font-weight: var(--input-table-text-large-weight);
@@ -407,7 +410,7 @@
407
410
  font-size: var(--input-helper-font-size);
408
411
  line-height: var(--input-helper-line-height);
409
412
 
410
- [data-state="error"] & {
413
+ :where([data-state="error"]) & {
411
414
  color: var(--input-border-error-color);
412
415
  }
413
416
 
@@ -415,7 +418,7 @@
415
418
  // color: var(--input-helper-success-color);
416
419
  // }
417
420
 
418
- [data-state="disabled"] & {
421
+ :where([data-state="disabled"]) & {
419
422
  color: var(--input-helper-disabled-color);
420
423
  }
421
424
  }
@@ -444,54 +447,54 @@
444
447
  }
445
448
 
446
449
  // 상태별 아이콘 컬러는 디자인 토큰으로 직접 지정한다.
447
- &-status[data-state="error"] {
450
+ &-status:where([data-state="error"]) {
448
451
  color: var(--input-status-error-color);
449
452
  }
450
453
 
451
- &-status[data-state="success"] {
454
+ &-status:where([data-state="success"]) {
452
455
  color: var(--input-status-success-color);
453
456
  }
454
457
  }
455
- .input-field[data-priority="secondary"] {
458
+ .input-field:where([data-priority="secondary"]) {
456
459
  border-bottom-width: var(--input-border-width-default);
457
460
 
458
- &[data-state="active"],
459
- &[data-state="focused"] {
461
+ :where([data-state="active"]),
462
+ :where([data-state="focused"]) {
460
463
  border-bottom-color: var(--input-border-active-color);
461
464
  border-bottom-width: var(--input-border-width-emphasis);
462
465
  }
463
466
 
464
- &[data-state="success"] {
467
+ :where([data-state="success"]) {
465
468
  border-bottom-color: var(--input-border-success-color);
466
469
  border-bottom-width: var(--input-border-width-emphasis);
467
470
  }
468
471
 
469
- &[data-state="error"] {
472
+ :where([data-state="error"]) {
470
473
  border-bottom-color: var(--input-border-error-color);
471
474
  border-bottom-width: var(--input-border-width-emphasis);
472
475
  }
473
476
 
474
- &[data-state="disabled"] {
477
+ :where([data-state="disabled"]) {
475
478
  border-bottom-color: var(--input-border-underline-disabled-color);
476
479
  border-bottom-width: var(--input-border-width-default);
477
480
  }
478
481
 
479
- &[data-readonly="true"] {
482
+ :where([data-readonly="true"]) {
480
483
  border-bottom-color: var(--input-border-underline-disabled-color);
481
484
  border-bottom-width: var(--input-border-width-default);
482
485
  }
483
486
  }
484
487
 
485
- .input[data-state="active"],
486
- .input[data-state="focused"],
487
- .input[data-state="success"] {
488
+ .input:where([data-state="active"]),
489
+ .input:where([data-state="focused"]),
490
+ .input:where([data-state="success"]) {
488
491
  .input-label,
489
492
  .input-inline-label {
490
493
  color: var(--input-label-accent-color);
491
494
  }
492
495
  }
493
496
 
494
- .input[data-state="error"] {
497
+ .input:where([data-state="error"]) {
495
498
  .input-label,
496
499
  .input-inline-label {
497
500
  color: var(--input-label-error-color);
@@ -506,7 +509,7 @@
506
509
  }
507
510
  }
508
511
 
509
- .input[data-state="disabled"] {
512
+ .input:where([data-state="disabled"]) {
510
513
  .input-label,
511
514
  .input-inline-label,
512
515
  .input-helper-text,
@@ -532,8 +535,8 @@
532
535
  }
533
536
  }
534
537
 
535
- .input[data-readonly="true"] {
536
- .input-field[data-priority="table"] {
538
+ .input:where([data-readonly="true"]) {
539
+ .input-field:where([data-priority="table"]) {
537
540
  // 변경: table readonly는 disabled와 동일한 박스 스타일을 사용하고 텍스트 색만 분리한다.
538
541
  background-color: var(--input-table-surface-readonly-color);
539
542
  border-color: var(--input-border-table-readonly-color);
@@ -102,7 +102,7 @@ export interface InputIcon {
102
102
  * @property {ReactNode} [clear] input reset버튼 커스텀 컨텐츠
103
103
  * @property {ReactNode} [success] input 입력상태 성공시 커스텀 컨텐츠
104
104
  * @property {ReactNode} [error] input 입력상태 에러시 커스텀 컨텐츠
105
- * @property {FormFieldWidth} [width] width preset 옵션
105
+ * @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 옵션
106
106
  * @property {InputState} [data-simulated-state] Storybook 시각 상태 강제용
107
107
  */
108
108
  export interface InputProps extends Omit<NativeInputProps, "size">, InputIcon {
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Navigation; mobile bottom navigation 카테고리 배럴
3
+ * @desc
4
+ * - `BottomNavigation`: 모바일 하단 네비게이션 루트다.
5
+ * - `NavigationItem`, `NavigationItemKey`: item/public type 계약이다.
6
+ * - `composeNavigationClassName`, `isHrefNavigationItem`: navigation util이다.
7
+ */
1
8
  import "./index.scss";
2
9
 
3
10
  export * from "./markup";
@@ -1,2 +1,8 @@
1
+ /**
2
+ * Navigation; mobile/web navigation markup export 배럴
3
+ * @desc
4
+ * - `BottomNavigation`: 현재 mobile 구현에서 제공되는 하단 네비게이션 루트다.
5
+ * - `web/*`: 플랫폼 확정 전까지 placeholder export를 유지한다.
6
+ */
1
7
  export * from "./mobile";
2
8
  export * from "./web";
@@ -1,5 +1,10 @@
1
1
  /**
2
- * pagination 카테고리 배럴 placeholder: 실제 구현은 markup/ 하위에 추가한다.
2
+ * Pagination; list/carousel/count indicator 카테고리 배럴
3
+ * @desc
4
+ * - `Pagination`: 숫자 기반 페이지 이동 컨트롤이다.
5
+ * - `PaginationCarousel`: dot 기반 step indicator다.
6
+ * - `PaginationCount`: 현재/전체 step 카운트 표시다.
7
+ * - `normalizePaginationState`, `createPaginationPages`: pagination util이다.
3
8
  */
4
9
  import "./index.scss";
5
10
 
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Pagination; list/carousel/count markup export 배럴
3
+ * @desc
4
+ * - `Pagination`: list variant다.
5
+ * - `PaginationCarousel`: carousel(dot) variant다.
6
+ * - `PaginationCount`: count variant다.
7
+ */
1
8
  export { Pagination } from "./Pagination";
2
9
  export { PaginationCarousel } from "./Carousel";
3
10
  export { PaginationCount } from "./Count";
@@ -1,3 +1,10 @@
1
+ /**
2
+ * PopOver; namespace 배럴 export
3
+ * @desc
4
+ * - `PopOver.Root`: open 상태 계약
5
+ * - `PopOver.Trigger`: asChild trigger 연결
6
+ * - `PopOver.Content`: overlay shell + 위치/폭 계약
7
+ */
1
8
  import "./index.scss";
2
9
 
3
10
  export * from "./markup";
@@ -5,10 +5,11 @@ import PopOverTrigger from "./Trigger";
5
5
  export { PopOverRoot, PopOverTrigger, PopOverContent };
6
6
 
7
7
  /**
8
- * PopOver namespace.
9
- * - Root
10
- * - Trigger
11
- * - Content
8
+ * PopOver; 컴포넌트 모듈
9
+ * @namespace PopOver
10
+ * - `PopOver.Root`
11
+ * - `PopOver.Trigger`
12
+ * - `PopOver.Content`
12
13
  */
13
14
  export const PopOver = {
14
15
  Root: PopOverRoot,
@@ -1,5 +1,9 @@
1
1
  /**
2
- * radio 카테고리 배럴; markup과 타입 exports를 노출한다.
2
+ * Radio; namespace 배럴 export
3
+ * @desc
4
+ * - `Radio`, `RadioField`: base radio primitives
5
+ * - `RadioCard`, `RadioCardGroup`: card variation
6
+ * - hooks/types/utils는 별도 배럴로 함께 export한다
3
7
  */
4
8
  import "./index.scss";
5
9
 
@@ -1,4 +1,4 @@
1
1
  /**
2
- * TODO(scrollbar): 접근성/상태 계산 hook을 정의한다.
2
+ * Scrollbar Hooks; 현재 공개된 훅이 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,4 +1,4 @@
1
1
  /**
2
- * scrollbar 카테고리 배럴 placeholder: 실제 구현은 markup/ 하위에 추가한다.
2
+ * Scrollbar; 현재 public export가 없는 placeholder 카테고리 배럴
3
3
  */
4
4
  export * from "./markup";
@@ -1,4 +1,4 @@
1
1
  /**
2
- * TODO(scrollbar): SOT 사용자 제약에 따라 컴포넌트를 구현한다.
2
+ * Scrollbar Markup; 현재 공개된 컴포넌트가 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,4 +1,4 @@
1
1
  /**
2
- * TODO(scrollbar): variant/slot 타입 정의를 작성한다.
2
+ * Scrollbar Types; 현재 공개된 타입이 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,4 +1,4 @@
1
1
  /**
2
- * TODO(scrollbar): 토큰 매핑과 클래스명 유틸을 구현한다.
2
+ * Scrollbar Utils; 현재 공개된 유틸이 없는 placeholder 엔트리
3
3
  */
4
4
  export {};
@@ -1,5 +1,9 @@
1
1
  /**
2
- * segmented-control 카테고리 배럴
2
+ * SegmentedControl; single-select segmented control 카테고리 배럴
3
+ * @desc
4
+ * - `SegmentedControl`: 옵션 배열 기반 단일 선택 루트다.
5
+ * - `SegmentedControlLabel`: 옵션 텍스트 슬롯이다.
6
+ * - `SegmentedControlProps`, `SegmentedControlOption`: public contract 타입이다.
3
7
  */
4
8
  import "./index.scss";
5
9
 
@@ -1,2 +1,8 @@
1
+ /**
2
+ * SegmentedControl; single-select segmented control markup export 배럴
3
+ * @desc
4
+ * - `SegmentedControl`: 옵션 배열 기반 단일 선택 루트다.
5
+ * - `SegmentedControlLabel`: 옵션 텍스트 슬롯이다.
6
+ */
1
7
  export * from "./Container";
2
8
  export { default as SegmentedControlLabel } from "./Label";
@@ -1,5 +1,10 @@
1
1
  /**
2
- * select 카테고리 배럴 placeholder: 실제 구현은 markup/ 하위에 추가한다.
2
+ * Select; namespace 배럴 export
3
+ * @desc
4
+ * - `Select.Default`: single select
5
+ * - `Select.Multiple`: multi select
6
+ * - `Select.Container`, `Select.Trigger.Base`, `Select.Selected.*`: headless 조합 레이어
7
+ * - hooks/types/utils는 별도 배럴로 함께 export한다
3
8
  */
4
9
  import "./index.scss";
5
10
 
@@ -10,20 +10,13 @@ import Container from "./foundation/Container";
10
10
  import { useSelectDropdownOpenState } from "../hooks";
11
11
  import type { SelectDropdownOption } from "../types/option";
12
12
  import type { SelectDefaultComponentProps } from "../types/props";
13
+ import { toSelectInputText } from "../utils";
13
14
  import {
14
15
  isSameSelectedValue,
15
16
  normalizeSingleSelectedValue,
16
17
  toSelectedValueKey,
17
18
  } from "../../../utils/selected-values";
18
19
 
19
- const toInputText = (value?: ReactNode): string => {
20
- if (typeof value === "string" || typeof value === "number") {
21
- return String(value);
22
- }
23
-
24
- return "";
25
- };
26
-
27
20
  const SELECT_CUSTOM_OPTION_BASE_ID = "__select_custom_input__";
28
21
  const SELECT_CUSTOM_OPTION_VALUE = "CUSTOM";
29
22
 
@@ -44,8 +37,8 @@ const SELECT_CUSTOM_OPTION_VALUE = "CUSTOM";
44
37
  * @param {boolean} [props.icon] chevron 아이콘 비활성화 여부
45
38
  * @param {"button" | "submit" | "reset"} [props.buttonType] trigger button type
46
39
  * @param {SelectDropdownOption[]} props.items dropdown 아이템 목록
47
- * @param {SelectCallbackParams} [props.onSelectOption] option interaction 콜백
48
- * @param {SelectCallbackParams} [props.onSelectChange] selection change 콜백
40
+ * @param {SelectCallbackParams} [props.onSelectOption] option interaction 콜백(legacy)
41
+ * @param {SelectCallbackParams} [props.onSelectChange] 권장 선택값 변경 콜백
49
42
  * @param {SelectDropdownExtension} [props.dropdownOptions] dropdown 확장 옵션
50
43
  * @param {boolean} [props.open] controlled open 값
51
44
  * @param {boolean} [props.defaultOpen] uncontrolled 초기 open 값
@@ -163,7 +156,9 @@ export function SelectDefault<OptionData = unknown>({
163
156
  );
164
157
 
165
158
  // 9) customOptions가 있는 경우 inputProps.value가 있고 option 매칭이 없으면 custom mode로 간주한다.
166
- const forcedCustomLabelValue = toInputText(inputProps?.value as ReactNode);
159
+ const forcedCustomLabelValue = toSelectInputText(
160
+ inputProps?.value as ReactNode,
161
+ );
167
162
  const shouldForceCustomFromInput = Boolean(
168
163
  customOptions && !selectedOption && forcedCustomLabelValue,
169
164
  );
@@ -262,7 +257,7 @@ export function SelectDefault<OptionData = unknown>({
262
257
  typeof inputProps?.value === "number"
263
258
  ? String(inputProps.value)
264
259
  : customLabelValue
265
- : toInputText(resolvedDisplayLabel);
260
+ : toSelectInputText(resolvedDisplayLabel);
266
261
 
267
262
  // 17) 렌더: Container → Dropdown.Root → Trigger → Menu.List 구조를 유지한다.
268
263
  return (
@@ -297,7 +292,8 @@ export function SelectDefault<OptionData = unknown>({
297
292
  label={resolvedDisplayLabel}
298
293
  placeholder={
299
294
  isCustomInputActive
300
- ? (customOptions?.placeholder ?? toInputText(placeholder))
295
+ ? (customOptions?.placeholder ??
296
+ toSelectInputText(placeholder))
301
297
  : placeholder
302
298
  }
303
299
  isPlaceholder={
@@ -329,6 +325,7 @@ export function SelectDefault<OptionData = unknown>({
329
325
  {...dropdownOptions?.containerProps}
330
326
  size={dropdownOptions?.size ?? resolvedSize}
331
327
  width={dropdownOptions?.width ?? "match"}
328
+ minWidth={dropdownOptions?.minWidth}
332
329
  >
333
330
  <Dropdown.Menu.List {...dropdownOptions?.menuListProps}>
334
331
  {mergedOptions.length > 0 ? (