@uniai-fe/uds-primitives 0.3.54 → 0.3.56

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
@@ -428,8 +428,7 @@
428
428
  --input-text-disabled-color: var(--color-label-disabled);
429
429
  --input-placeholder-color: var(--color-label-alternative);
430
430
  --input-placeholder-disabled-color: var(--color-label-disabled);
431
- /* 변경: readonly 입력은 placeholder를 숨겨 value 텍스트 대비를 고정한다. */
432
- --input-placeholder-readonly-color: transparent;
431
+ --input-placeholder-readonly-color: var(--input-placeholder-color);
433
432
  --input-font-size: var(--input-text-medium-size);
434
433
  --input-line-height: var(--input-text-medium-line-height);
435
434
  --input-font-weight: var(--input-text-medium-weight);
@@ -590,10 +589,11 @@
590
589
  /* 변경: placeholder disabled/readonly 토큰을 분리해 상태별 제어 지점을 고정한다. */
591
590
  --select-primary-color-placeholder-disabled: var(--color-label-disabled);
592
591
  --select-primary-color-placeholder-readonly: var(
593
- --select-primary-color-placeholder-disabled
592
+ --select-primary-color-placeholder
594
593
  );
595
594
  --select-primary-color-surface: var(--input-surface-color);
596
595
  --select-primary-color-text: var(--color-label-alternative);
596
+ --select-color-text-value: var(--color-label-strong);
597
597
  --select-primary-color-text-focused: var(--color-label-strong);
598
598
  --select-primary-color-text-disabled: var(--color-label-disabled);
599
599
  --select-primary-color-text-readonly: var(--color-label-strong);
@@ -617,7 +617,7 @@
617
617
  --select-secondary-color-placeholder: var(--color-label-alternative);
618
618
  --select-secondary-color-placeholder-disabled: var(--color-label-disabled);
619
619
  --select-secondary-color-placeholder-readonly: var(
620
- --select-secondary-color-placeholder-disabled
620
+ --select-secondary-color-placeholder
621
621
  );
622
622
  --select-color-surface-secondary: transparent;
623
623
  --select-color-surface-secondary-hover: var(--color-surface-static-cool-gray);
@@ -2777,6 +2777,11 @@ figure.chip {
2777
2777
  box-shadow: none;
2778
2778
  }
2779
2779
 
2780
+ .input-field[data-has-value=true] .input-element:not(:disabled) {
2781
+ color: var(--input-text-color);
2782
+ caret-color: var(--input-text-color);
2783
+ }
2784
+
2780
2785
  .input-field-control {
2781
2786
  display: flex;
2782
2787
  align-items: center;
@@ -2809,9 +2814,24 @@ figure.chip {
2809
2814
  --input-textarea-gap: var(--input-textarea-gap-large);
2810
2815
  }
2811
2816
 
2817
+ .input[data-input-type=textarea] {
2818
+ min-height: var(--input-textarea-height);
2819
+ }
2820
+ .input[data-input-type=textarea] .input-box {
2821
+ height: 100%;
2822
+ }
2823
+ .input[data-input-type=textarea] .input-field {
2824
+ height: 100%;
2825
+ }
2826
+ .input[data-input-type=textarea] .input-field-control {
2827
+ flex: 1 1 auto;
2828
+ width: 100%;
2829
+ align-items: stretch;
2830
+ }
2831
+
2812
2832
  .input-textarea-element {
2813
2833
  min-height: var(--input-textarea-min-height);
2814
- height: var(--input-textarea-height);
2834
+ height: 100%;
2815
2835
  resize: none;
2816
2836
  margin: 0;
2817
2837
  }
@@ -4046,9 +4066,6 @@ figure.chip {
4046
4066
  color: var(--select-primary-color-text-readonly);
4047
4067
  caret-color: var(--select-primary-color-text-readonly);
4048
4068
  }
4049
- .select-button:where([data-state=focused]) .select-input-label, .select-button:where(:focus-within) .select-input-label {
4050
- color: var(--color-label-strong);
4051
- }
4052
4069
  .select-button:where([data-priority=secondary]):where([data-state=disabled]) .select-input-label {
4053
4070
  color: var(--select-secondary-color-text-disabled);
4054
4071
  }
@@ -4056,6 +4073,11 @@ figure.chip {
4056
4073
  color: var(--select-secondary-color-text-readonly);
4057
4074
  }
4058
4075
 
4076
+ .select-button:where(:not([data-state=disabled])) .select-value[data-has-value=true] .select-input-label {
4077
+ color: var(--select-color-text-value);
4078
+ caret-color: var(--select-color-text-value);
4079
+ }
4080
+
4059
4081
  .select-input-label-placeholder {
4060
4082
  color: var(--select-primary-color-placeholder);
4061
4083
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.3.54",
3
+ "version": "0.3.56",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -15,7 +15,7 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "packageManager": "pnpm@10.30.3",
18
+ "packageManager": "pnpm@10.32.0",
19
19
  "engines": {
20
20
  "node": ">=24",
21
21
  "pnpm": ">=10"
@@ -89,9 +89,9 @@
89
89
  "react-daum-postcode": "^4.0.0"
90
90
  },
91
91
  "devDependencies": {
92
- "@mantine/core": "^8.3.15",
93
- "@mantine/dates": "^8.3.15",
94
- "@mantine/hooks": "^8.3.15",
92
+ "@mantine/core": "^8.3.16",
93
+ "@mantine/dates": "^8.3.16",
94
+ "@mantine/hooks": "^8.3.16",
95
95
  "@svgr/webpack": "^8.1.0",
96
96
  "@types/node": "^24.10.2",
97
97
  "@types/react": "^19.2.14",
@@ -243,6 +243,8 @@ const InputBase = forwardRef<HTMLInputElement, InputProps>(
243
243
  data-state={visualState}
244
244
  data-priority={priority}
245
245
  data-size={resolvedSize}
246
+ // 변경: 값 존재 여부를 DOM state로 노출해 상태 색상과 분리된 텍스트 규칙을 적용한다.
247
+ data-has-value={hasValue ? "true" : "false"}
246
248
  data-readonly={isReadOnly ? "true" : undefined}
247
249
  data-block={resolvedBlock ? "true" : undefined}
248
250
  >
@@ -192,6 +192,7 @@ const InputTextArea = forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
192
192
  data-state={visualState}
193
193
  data-priority={priority}
194
194
  data-size={resolvedSize}
195
+ data-has-value={currentLength > 0 ? "true" : "false"}
195
196
  data-readonly={isReadOnly ? "true" : undefined}
196
197
  data-block={resolvedBlock ? "true" : undefined}
197
198
  >
@@ -279,6 +279,11 @@
279
279
  }
280
280
  }
281
281
 
282
+ .input-field[data-has-value="true"] .input-element:not(:disabled) {
283
+ color: var(--input-text-color);
284
+ caret-color: var(--input-text-color);
285
+ }
286
+
282
287
  .input-field-control {
283
288
  display: flex;
284
289
  align-items: center;
@@ -315,9 +320,28 @@
315
320
  }
316
321
  }
317
322
 
323
+ // 변경: textarea 높이 토큰은 native element가 아니라 root layout이 소유하고 내부는 남은 높이를 채운다.
324
+ .input[data-input-type="textarea"] {
325
+ min-height: var(--input-textarea-height);
326
+
327
+ .input-box {
328
+ height: 100%;
329
+ }
330
+
331
+ .input-field {
332
+ height: 100%;
333
+ }
334
+
335
+ .input-field-control {
336
+ flex: 1 1 auto;
337
+ width: 100%;
338
+ align-items: stretch;
339
+ }
340
+ }
341
+
318
342
  .input-textarea-element {
319
343
  min-height: var(--input-textarea-min-height);
320
- height: var(--input-textarea-height);
344
+ height: 100%;
321
345
  resize: none;
322
346
  margin: 0;
323
347
  }
@@ -85,8 +85,7 @@
85
85
  --input-text-disabled-color: var(--color-label-disabled);
86
86
  --input-placeholder-color: var(--color-label-alternative);
87
87
  --input-placeholder-disabled-color: var(--color-label-disabled);
88
- /* 변경: readonly 입력은 placeholder를 숨겨 value 텍스트 대비를 고정한다. */
89
- --input-placeholder-readonly-color: transparent;
88
+ --input-placeholder-readonly-color: var(--input-placeholder-color);
90
89
  --input-font-size: var(--input-text-medium-size);
91
90
  --input-line-height: var(--input-text-medium-line-height);
92
91
  --input-font-weight: var(--input-text-medium-weight);
@@ -302,6 +302,7 @@ export function SelectDefault<OptionData = unknown>({
302
302
  ? labelInputValue
303
303
  : (resolvedSelectedOption?.value ?? "")
304
304
  }
305
+ valueStateSource={isCustomInputActive ? "label" : "hidden"}
305
306
  // 변경: custom mode 진입 시 label input에 focus를 연결한다.
306
307
  shouldFocusInput={isCustomInputActive}
307
308
  onLabelChange={setCustomLabelValue}
@@ -48,6 +48,7 @@ const SelectTriggerSelected = ({
48
48
  inputProps,
49
49
  valueText = "",
50
50
  valueFieldValue = "",
51
+ valueStateSource = "label",
51
52
  shouldFocusInput = false,
52
53
  onLabelChange,
53
54
  }: SelectSelectedProps) => {
@@ -60,12 +61,26 @@ const SelectTriggerSelected = ({
60
61
  const resolvedInputValueText = toInputText(inputProps?.value as ReactNode);
61
62
  const resolvedLabelText =
62
63
  valueText || resolvedInputValueText || toInputText(label);
64
+ // 변경 설명: 색상 계약은 hidden value 또는 label value 중 지정된 source를 기준으로 계산한다.
65
+ const resolvedHiddenValueText =
66
+ typeof valueFieldValue === "string" || typeof valueFieldValue === "number"
67
+ ? String(valueFieldValue)
68
+ : "";
69
+ const hasDisplayValue =
70
+ valueStateSource === "hidden"
71
+ ? resolvedHiddenValueText.length > 0
72
+ : resolvedLabelText.length > 0;
63
73
  const isLabelTextLike =
64
74
  typeof label === "string" || typeof label === "number";
75
+ const shouldUsePlaceholderStyle = !hasDisplayValue;
65
76
  // 변경 설명: readOnly에서 non-text label을 직접 렌더링하되, placeholder 상태일 때는 input placeholder 경로를 유지한다.
66
77
  // custom mode처럼 valueText가 있는 경우에는 input 렌더 경로를 유지해 값 표시를 보장한다.
67
78
  const shouldRenderReadonlyLabelNode =
68
- readOnly && !isLabelTextLike && !isPlaceholder && !resolvedLabelText;
79
+ readOnly &&
80
+ !isLabelTextLike &&
81
+ !isPlaceholder &&
82
+ !shouldUsePlaceholderStyle &&
83
+ !resolvedLabelText;
69
84
 
70
85
  // 3) custom mode 활성 시에만 label input focus를 부여한다.
71
86
  useEffect(() => {
@@ -77,13 +92,17 @@ const SelectTriggerSelected = ({
77
92
  }, [readOnly, shouldFocusInput]);
78
93
 
79
94
  return (
80
- <div className="select-value">
95
+ <div
96
+ className="select-value"
97
+ data-has-value={hasDisplayValue ? "true" : "false"}
98
+ >
81
99
  {shouldRenderReadonlyLabelNode ? (
82
100
  // 변경 설명: readonly + ReactNode label일 때는 문자열 변환 없이 label 노드를 직접 렌더링한다.
83
101
  <div
84
102
  className={clsx("select-input-label", inputProps?.className, {
85
- "select-input-label-placeholder": isPlaceholder,
103
+ "select-input-label-placeholder": shouldUsePlaceholderStyle,
86
104
  })}
105
+ data-has-value={hasDisplayValue ? "true" : "false"}
87
106
  >
88
107
  {label}
89
108
  </div>
@@ -95,8 +114,9 @@ const SelectTriggerSelected = ({
95
114
  // 내부 계약값을 마지막에 고정해 동작 우선순위를 명확히 한다.
96
115
  {...inputProps}
97
116
  className={clsx("select-input-label", inputProps?.className, {
98
- "select-input-label-placeholder": isPlaceholder,
117
+ "select-input-label-placeholder": shouldUsePlaceholderStyle,
99
118
  })}
119
+ data-has-value={hasDisplayValue ? "true" : "false"}
100
120
  placeholder={resolvedPlaceholder}
101
121
  value={resolvedLabelText}
102
122
  readOnly={readOnly}
@@ -151,6 +171,7 @@ const SelectTriggerSelected = ({
151
171
  type="hidden"
152
172
  className="select-input-value"
153
173
  value={valueFieldValue}
174
+ data-has-value={resolvedHiddenValueText.length > 0 ? "true" : "false"}
154
175
  {...register}
155
176
  />
156
177
  </div>
@@ -281,11 +281,6 @@
281
281
  color: var(--select-primary-color-text-readonly);
282
282
  caret-color: var(--select-primary-color-text-readonly);
283
283
  }
284
- // 변경: legacy label 텍스트 색상 규칙을 input 렌더 구조로 이관한다.
285
- .select-button:where([data-state="focused"]) &,
286
- .select-button:where(:focus-within) & {
287
- color: var(--color-label-strong);
288
- }
289
284
 
290
285
  .select-button:where([data-priority="secondary"]):where(
291
286
  [data-state="disabled"]
@@ -302,6 +297,13 @@
302
297
  }
303
298
  }
304
299
 
300
+ .select-button:where(:not([data-state="disabled"]))
301
+ .select-value[data-has-value="true"]
302
+ .select-input-label {
303
+ color: var(--select-color-text-value);
304
+ caret-color: var(--select-color-text-value);
305
+ }
306
+
305
307
  .select-input-label-placeholder {
306
308
  color: var(--select-primary-color-placeholder);
307
309
 
@@ -95,10 +95,11 @@
95
95
  /* 변경: placeholder disabled/readonly 토큰을 분리해 상태별 제어 지점을 고정한다. */
96
96
  --select-primary-color-placeholder-disabled: var(--color-label-disabled);
97
97
  --select-primary-color-placeholder-readonly: var(
98
- --select-primary-color-placeholder-disabled
98
+ --select-primary-color-placeholder
99
99
  );
100
100
  --select-primary-color-surface: var(--input-surface-color);
101
101
  --select-primary-color-text: var(--color-label-alternative);
102
+ --select-color-text-value: var(--color-label-strong);
102
103
  --select-primary-color-text-focused: var(--color-label-strong);
103
104
  --select-primary-color-text-disabled: var(--color-label-disabled);
104
105
  --select-primary-color-text-readonly: var(--color-label-strong);
@@ -122,7 +123,7 @@
122
123
  --select-secondary-color-placeholder: var(--color-label-alternative);
123
124
  --select-secondary-color-placeholder-disabled: var(--color-label-disabled);
124
125
  --select-secondary-color-placeholder-readonly: var(
125
- --select-secondary-color-placeholder-disabled
126
+ --select-secondary-color-placeholder
126
127
  );
127
128
  --select-color-surface-secondary: transparent;
128
129
  --select-color-surface-secondary-hover: var(--color-surface-static-cool-gray);
@@ -159,6 +159,7 @@ export interface SelectTriggerMultipleProps extends SelectTriggerBaseFoundationP
159
159
  * @property {ComponentPropsWithoutRef<"input">} [inputProps] label input native 속성
160
160
  * @property {string} [valueText] label input 표시 문자열
161
161
  * @property {SelectOptionValue | ""} [valueFieldValue] hidden value input 값
162
+ * @property {"label" | "hidden"} [valueStateSource] 표시값 존재 여부를 판정할 source
162
163
  * @property {boolean} [shouldFocusInput] custom mode 진입 시 label input focus 여부
163
164
  * @property {(value: string) => void} [onLabelChange] label 입력 변경 콜백
164
165
  */
@@ -200,6 +201,12 @@ export interface SelectSelectedProps {
200
201
  * hidden value input 값
201
202
  */
202
203
  valueFieldValue?: SelectOptionValue | "";
204
+ /**
205
+ * 표시값 존재 여부를 판정할 source
206
+ * - label: select-input-label value 기준
207
+ * - hidden: select-input-value value 기준
208
+ */
209
+ valueStateSource?: "label" | "hidden";
203
210
  /**
204
211
  * custom mode 진입 시 label input focus 여부
205
212
  */